News, examples, tips, ideas and plans.
Thoughts around ORM, .NET and SQL databases.

Tuesday, April 12, 2011

On security system, part 3

This is the third part in a series of posts that are dedicated to security system concept in DataObjects.Net. The first part contained common considerations and bits of theory, the second one was about the approach in development and the requirements to security system.

In this part I'll describe main blocks of security system architecture, i.e. roles & permissions.

First of all, let's start with roles. As you might remember, there is a list of positions in our imaginary sales organization:
  • Stock manager 
  • Sales representative 
  • Sales manager 
  • Sales president
I won't list their particular duties and access permissions again as this information can be found in the previous post. The thing that must be mentioned here is that each position executes one or more roles within the company, for example, Stock manager is also an employee, so Stock manager position includes some basic duties as well as duties specific to stock manager. The same goes for Sales manager, he not only have to approve orders but also acts like a sale representative as well as a common employee. Therefore, these positions can be arranged into accurate hierarchical structure according to the rule of inheritance. Luckily, RBAC level 2 declares support for roles hierarchies, and so does our security model.

Role hierarchy for the imaginary sales company:

Note, that each role is defined as a separate class. This approach as any other one has advantages and well as drawbacks. It is less dynamic, but provides natural inheritance, doesn't require persistence and is more intuitive, visual and understandable. Moreover, as roles in a company are mostly static, I've found the approach highly advantageous.

Look, EmployeeRole inherits a Role class. This is the base role class, it provides inheritors with necessary infrastructure: a set of Permissions for a role, name of a role and a method for permission registration.

To continue with roles hierarchy configuration we should define a permission first. According to the access matrix in the previous post, there are 2 common permissions that are actual for all kind of entities: Read permission & Write permission. In addition to these basic ones, there might be extended permissions as well, like Approve permission defined for Order entity type. Thus, we've defined a base Permission type and OrderPermission inheritor that provides requested functionality:

Let's see how these roles and permission are intended to be used. Let's start with the EmployeeRole first.  As it is mentioned above, all company positions have at least read-only access to employees & products, we can define the base Role node for this organization and call it EmployeeRole. Let this role is given to any employee in the company.

Here is how EmployeeRole is declared:

public class EmployeeRole : Role
{
  public EmployeeRole()
  {
    // This is base role for every employee

    // All employees can read products
    RegisterPermission(new Permission<Product>());
    // All employees can read employees
    RegisterPermission(new Permission<Employee>());
  }
}

That's it. Now all employees have a permission to read Products & Employees.

What about the Stock manager role? Stock manager should inherit the base EmployeeRole and add permission for editing products.

public class StockManagerRole : EmployeeRole
{
  public StockManagerRole()
  {
    // Stock manager inherits Employee permissions

    // Stock managers can read and edit products
    RegisterPermission(new Permission<Product>(canWrite:true));
  }
}

As I've already mentioned, the advantage of the class-based approach in role hierarchy definition is in its essential inheritance support. Derived role can inherit all permissions and other options of the base class and override something if is necessary. This is achievable in the most natural way for developers: make an inheritor & override all you need.

On the other branch of roles hierarchy, Sales representative role also inherits the base EmployeeRole and should add permissions to edit customers and orders. In order to add Order permission we should define OrderPermission first, where a property CanApprove should be included. Here it is:

public class OrderPermission : Permission<Order>
{
  public bool CanApprove { get; private set; }

  public OrderPermission(bool canWrite, bool canApprove)
    : base(canWrite)
  {
    CanApprove = canApprove;
  }
}

Having declared the OrderPermission, we can proceed with SaleRepresentativeRole class:

public class SalesRepresentativeRole : EmployeeRole
{
  public SalesRepresentativeRole()
  {
    // Sales representative inherits Employee permissions

    // Sales representative can read and edit customers
    RegisterPermission(new Permission<Customer>(canWrite:true));
    // Sales representative can read and edit sale orders but not approve
    RegisterPermission(new OrderPermission(canWrite:true, canApprove:false));
  }
}

As for Sales manager role, it inherits Sales representative role and adds permission to approve orders. Here is how:

public class SalesManagerRole : SalesRepresentativeRole
{
  public SalesManagerRole()
  {
    // Sales manager inherits Sales representative permissions

    // Sales manager can do sale orders approval, in addition
    RegisterPermission(new OrderPermission(canWrite:true, canApprove:true));
  }
}

The last one is Sales president role. It in turn has access to all entities in read-only mode plus write permission to Employees, so we have to override permissions declared in its ansector (SalesManagerRole):

public class SalesPresidentRole : SalesManagerRole
{
  public SalesPresidentRole()
  {
    // Sales president inherits Sales manager permissions

    // Sales President can read all, but can't alter any details, except employees. They usually need read-only aggregated reports, actually
    RegisterPermission(new Permission<Customer>());
    RegisterPermission(new Permission<Order>());
    RegisterPermission(new Permission<Product>());
    RegisterPermission(new Permission<Employee>(canWrite:true));
  }
}

Note the clarity and the ease of permissions overriding.

That's all for today. For now we have permissions and roles defined. Hence, we've got a mechanism that can be used to describe the access matrix from the previous post in full manner:

2 comments:

  1. This is what i am expecting you do, but i'am interesting how this interact with DO4 with queries, sessions, etc...

    ReplyDelete
  2. Peter, that was an absolutely required preamble.

    ReplyDelete