The world's most over-engineered permissions system.
Supports net5.0.
- Generic nodes (not tied to a resource)
- Resource-bound nodes, evaluated with respect to a particular resource marked
IPermissionManaged - Simple chains allow/deny grants with respect to an identifier (e.g. user ID)
- Evaluated one after each other in order of provided
Indexto come up with a final pass/fail answer
- Evaluated one after each other in order of provided
- Default disallow policy if no matching grants
- Complex resource-bound grants using a DSL which is compiled into AST transformation at startup and then evaluated at runtime
- AspNetMvc (Core) extensions for registering services, logging permissions for debugging etc
- Node: A permission entry. Something that can be done that needs its access controlled.
- These have a key which by convention is represented as
EntityName.[SubGroup].Action.
- These have a key which by convention is represented as
- Grants: Allow/deny rules that cover a single permission node. Can be typed as
GenericorResourceBound. - ResourceBound grant: Evaluation of the grant must be performed in the context pf a resource.
- For example,
Product.Editprobably requires the product in question to be considered when checking to see if the user should have the permission or not.
- For example,
- Generic grant: Does not require a resource when evaluating. E.g.
Product.Create. - Chain: grants are organised into chains and have identifiers within a chain. When nodes are checked, all
grants that match the identifier in the specified chain are considered.
- Example chains:
GroupsorUsers - Example identifiers:
Group IDorUser ID
- Example chains:
- Condition: An additional requirement on top of a ResourceBound grant. Written in a DSL for this permissions system.
new Cat
{
Breed = CatBreed.Bengal,
Age = 10,
Name = "Felix"
}You could write some conditions for a resource bound grant on node Cat.Adopt:
resource.Name == "Felix" || resource.Age < 10
resource.Age != 5
Supported operators: <=, >=, <, >, &&, ||, ., ==, !=, !, ~= (regex)
From an ASP.NET MVC Core project, in ConfigureServices in Startup.cs:
services.AddScoped<IPermissionGrantProvider, SomePermissionGrantProvider>();
services.AddGranularPermissions(typeof(Permissions));SomePermissionGrantProvider must implement IPermissionGrantProvider. Its role is to
return all grants (which implement IPermissionGrantSerialized) persisted in the system.
You may wish to retrieve them from a database, for instance.
The Permissions class must define all permission nodes you wish to exist in your project:
public static class Permissions
{
public static class Product
{
public static readonly ResourceNode<ProductModel> View =
new ResourceNode<ProductModel>("Product.View", "View an individual product");
public static readonly GenericNode Create =
new GenericNode("Product.Create", "Create a product");
public static readonly ResourceNode<ProductModel> Buy =
new ResourceNode<ProductModel>("Product.Purchase", "Purchase an individual product");
}
public static class Cat
{
public static readonly ResourceNode<ProductModel> Pet =
new ResourceNode<ProductModel>("Cat.Pet", "Pet the cat without being bitten/scratched");
public static readonly GenericNode Adopt =
new GenericNode("Cat.Adopt", "Be adopted by a cat");
}
}To check a permission within a specified chain, call GetResultUsingChain on the IPermissionsService instance which will return a PermissionResult (Unset, Allow or Deny).
It is possible to reload the grants at runtime by calling ReplaceAllGrants with a new IEnumerable<IPermissionGrantSerialized>.
A web application has two groups which have different levels of access. Group 1 is for "Everyone" and includes a basic level of permission grants. Group 2, "Administrators" includes a higher level of access for a subset of application users who are in this group.
Within GranularPermissions, you'd have:
- One chain,
"Groups" - A set of
IPermissionGrantSerializedfor the "Groups" chain with Identifier=1. These will apply to all users in the Everyone group. - A set of
IPermissionGrantSerializedfor the "Groups" chain with Identifier=2. These will apply to all users in the Administrators group. - Some
IPermissionGrantProviderwhich returns the aforementionedIPermissionGrantSerializedinstances from DB
A simple permissions checker would call GetResultUsingChain("Groups", Permissions.Node.ToCheck, groupId) for each of the user's groups and then aggregate the results to come up with a final allowed/denied result. For example, the administrators group may be considered a higher priority than the everyone group.
For resource bound nodes, it's simply a case of passing an additional argument GetResultUsingChain("Groups", Permissions.Node.ToCheckWithResource, groupId, resourceToCheckAgainst).
- Add ability to register DSL functions/identifiers