Skip to content

Permissions

Arsenty Politov edited this page Mar 15, 2019 · 1 revision

Permissions services

Provides infrastructure for defining and verifying permissions.

Related packages

  • DevGuild.AspNetCore.Services.Permissions
  • DevGuild.AspNetCore.Services.Permissions.Entity

Permissions model

Security scope

Security scope defines hierarchical path to protected entities. It can be a constant string or a string, parameterized with protected entity.

Examples

  • / - root security scope. Useful for defining global administrator permissions to manage entire service.
  • /Domain - security scope for entire service domain model. Useful for defining global administrator permissions to manage service domain model.
  • /Domain/Order - security scope for all orders.
  • /Domain/Order/Entities/{entity:Order} - security scope for a specific single order.
  • /Domain/Order/Properties/{property:String} - security scope for specific property of orders.

For each security scope a permissions manager should be registered:

  • Security scope without parameters should use IPermissionsManager interface.
  • Security scope with a single parameter should use IPermissionsManager<T> interface.
  • Security scope with more than one parameter is not supported at the moment.

Permissions and permission namespaces

Permission is defined for a possible action that can be performed by some user against some entity. Every possible permission that needs to be secured should be defined as an instance of Permissions class and combined into namespace. Namespace is defined by a class that is inherited from provided PermissionsNamespace class.

Example of permissions defined within a permissions namespace:

public class EntityPermissionsNamespace : PermissionsNamespace
{
    public Permission Read { get; } = new Permission("{2E8D96B5-2B42-4969-9EF2-13A2525E9D6C}", "Read", 1);
    public Permission Update { get; } = new Permission("{1E236CD6-670C-4498-8A47-7436C5673D7B}", "Update", 2);
    public Permission Delete { get; } = new Permission("{5B4DBD8F-45B6-4318-B42B-2F587A66126E}", "Delete", 4);
}

For each permission 3 types of identifier are defined:

  • Guid identifier that should be globally unique.
  • String identifier that should be unique within namespace.
  • Integer identifier that should be unique within namespace and be a power of 2 (so they can be combined into bit mask).

Its possible to define permissions namespace that is inherited from existing one. In that case, all permissions defined within parent namespace will be inherited as well:

public class HierarchicalEntityPermissionsNamespace : EntityPermissionsNamespace
{
    public Permission CreateChild { get; } = new Permission("{ACCAAF0F-9DBD-4F17-AFFB-C1F772EE4C37}", "CreateChild", 8);
}

Using permissions from parent security scope

In some cases, parent security scope can define permissions, that override permissions of children scopes. This approach provides an ability to avoid fine-grained permissions check if permissions can be resolved at a higher level.

For example, the following permissions namespace defines permissions like ReadAnyEntity. If user has this permission for an entity type, there is no need to check if the user has Read permission for any specific entity of that type.

public class EntityTypePermissionsNamespace : PermissionsNamespace
{
    public Permission Access { get; } = new Permission("{AC6A4DE5-1D57-4F9C-A3BE-B1F5ABD76C05}", "Access", 1);
    public Permission Create { get; } = new Permission("{6F915AA3-7E83-4E27-BC48-0CC01477AEF6}", "Create", 2);
    public Permission ReadAnyEntity { get; } = new Permission("{50BC8FDC-A77C-424F-8CCC-8E8BF23EA2EA}", "ReadAnyEntity", 4);
    public Permission CreateAnyEntity { get; } = new Permission("{2B1FE65E-A2A3-4FF9-B4C5-3907779799E3}", "CreateAnyEntity", 8);
    public Permission UpdateAnyEntity { get; } = new Permission("{925F679A-4DD7-4C21-9D91-47EE48EE5AE6}", "UpdateAnyEntity", 16);
    public Permission DeleteAnyEntity { get; } = new Permission("{893507B3-2424-4C1D-B71D-FD5AF246065B}", "DeleteAnyEntity", 32);
    public Permission ReadAnyProperty { get; } = new Permission("{6C377408-BEC0-48CE-8945-4DC24722F7BB}", "ReadAnyProperty", 64);
    public Permission InitializeAnyProperty { get; } = new Permission("{C3308D2E-816B-45DC-AB9E-00E7CE3AD535}", "InitializeAnyProperty", 128);
    public Permission UpdateAnyProperty { get; } = new Permission("{A3165233-D299-4454-AEC2-E46B42A46717}", "UpdateAnyProperty", 256);
}

Registering security scopes

Security scopes are registered by calling AddEntry method of PermissionsHubConfiguration class instance that can be later used to configure permission services. During scope registration the following information must be provided:

  • Security scope
  • Permissions namespace
  • Permissions manager constructor

Permissions manager defines the way the permissions are checked. Permissions manager constructor defines which permissions manager should be used and its configuration.

The following permission managers are supported at the moment:

  • FixedByRole - checks that the user has a necessary set of roles. This is the only permissions manager that can be used for parameterless scopes.
  • FixedByRole<T> - checks that the user has a necessary set of roles. Security scope parameter is ignored here.
  • ListByRole<T> - checks that the user has a necessary set of roles to access a specific constant entity from fixed set.
  • ByRoleOrRelation<T> - checks that the user has a necessary set of roles and is optionally referenced from the protected entity or its related entities.

FixedByRole permissions manager

This permissions manager should be used in cases when user roles are enough to check the permissions. It can be configured using following methods:

  • RequireRoles - defines a set of roles that are required for specified permission or permissions namespace.
  • AllowAnonymous - defines a list of permissions that are available to anonymous users.
  • ConfigureOverrides - defines parent security scope overrides for single entity permissions checks.
  • ConfigureQueryOverrides - defines parent security scope overrides for permissions-based query filtering.

FixedByRole Example

configuration.AddEntry("/Domain/Product", ApplicationPermissions.EntityType, PermissionsManager.FixedByRole(builder => builder
    .RequireRoles(ApplicationPermissions.EntityType, "Administrator") // Administrator has all permissions
    .RequireRoles(ApplicationPermissions.EntityType, "Manager") // Manager has all permissions
    .AllowAnonymous(ApplicationPermissions.EntityType.Access, ApplicationPermissions.EntityType.ReadAnyEntity)); // Everyone can access and read any entity

ListByRole permissions manager

This permissions manager defines permissions over a constant set of entities based on user roles. It can be configured using following methods:

  • SetDefaultBehavior - set default behavior for the permissions manager: access is either allowed or denied by default.
  • AddException - adds an exception to the default behavior for specific entity and permission.
  • ConfigureOverrides - defines parent security scope overrides for single entity permissions checks.

ListByRole Example

configuration.AddEntry("/Domain/Order/Properties/{property:String}", ApplicationPermissions.EntityProperty, PermissionsManager.ListByRole<String>(builder => builder
    .SetDefaultBehavior(ListPermissionsManagerDefaultBehavior.Allow)
    .AddException("Customer", ApplicationPermissions.EntityProperty.Update, RoleList.WithRoles("Manager"))));

ByRoleOrRelation permissions manager

This permissions manager defines permissions for entity by specifying a set of necessary roles and optionally a reference from secured entity (or its related entities) to the user. It can be configured using following methods:

  • RequireNothing - anyone has specified permissions for all protected entities.
  • RequireAuthentication any authenticated user has specified permissions for all protected entities.
  • RequireRoles - any user with a specified set of roles has specified permissions for all protected entities.
  • RequireRelations - any authenticated user has specified permissions for a subset of protected entities that have a relation with this user.
  • RequireRolesAndRelations - any user with a specified set of roles has specified permissions for a subset of protected entities that have a relation with this user.
  • ConfigureOverrides - defines parent security scope overrides for single entity permissions checks.
  • ConfigureQueryOverrides - defines parent security scope overrides for permissions-based query filtering.

ByRoleOrRelation Example

configuration.AddEntry("/Domain/Order/Entities/{entity:Order}", ApplicationPermissions.Entity, PermissionsManager.ByRoleOrRelation<Order>(builder => builder
    .RequireRoles(ApplicationPermissions.Entity, "Administrator")
    .RequireRolesAndRelations(ApplicationPermissions.Entity.Read, x => x.Customer.UserId, "Customer")));

Adding services

To add the necessary services to dependency injection container, add the following line to ConfigureServices method of Startup class:

services.AddPermissions(this.ConfigurePermissions());

ConfigurePermissions is a method that defines scopes by registering them within an instance of PermissionsHubConfiguration that is later returned from this method.

In addition to adding services, its necessary to add a InsufficientPermissionsHandlingMiddleware middleware to the pipeline. This middleware is used to handle failed permissions checks and turn them into either login redirect, 403 or 404 response:

  • If requestor is not authenticated, the middleware will return a HTTP 302 redirect to a login page (address is configurable via LoginUrl option).
  • If requestor is authenticated, the middleware will return either HTTP 403 or HTTP 404 error, depending on ReturnNotFoundForAuthenticatedUsers option.
app.UsePermissions(new PermissionsMiddlewaresOptions
{
    InsufficientPermissionsHandling =
    {
        ReturnNotFoundForAuthenticatedUsers = true,
    },
});

Using permissions manager

IPermissionsManager can be acquired using IPermissionsHub service:

public void Example1(IPermissionsHub permissionsHub)
{
    var manager1 = permissionsHub.GetManager<IPermissionsManager>("/Domain/Order");
    var manager2 = permissionsHub.GetManager<IPermissionsManager<Order>>("/Domain/Order/Entities/{entity:Order}");
}

IPermissionsManager provides following methods for permissions check:

  • CheckPermissionAsync - checks a single permission and returns check result.
  • CheckPermissionsAsync - checks multiple permissions and returns check result.
  • DemandPermissionAsync - checks a single permission and throws an exception if check fails.
  • DemandPermissionsAsync - checks multiple permissions and throws an exception if check fails.
  • ApplyQueryFilterAsync - applies permissions filter to the provided query.

A result of a permission check is either Undefined or Allow.

Clone this wiki locally