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

Friday, April 15, 2011

On security system, part 4

This is the forth part in a series of posts that is dedicated to security system concept in DataObjects.Net. In the previous part I described Role & Permission types and their relationships in context of entity access based on entity type. This part of the concept answers the question whether a user has access to entities of a persistent type or not and what kind of access if yes (read, write, approve, you name it).

In the part where the requirements were listed there was another set that defines some business rules applied to the set of entities that are accessible to a user. Remember, Sales Representatives can edit only their own Order instances, while Sales Managers can edit both their own orders as well as orders of stuff from their Sales Department and so on.

Let me recall the matrix:


So, we should have an option to restrict the set of Customers and Orders visible for Sales Representative & Sales Manager roles. These requirements are more like full-fledged business rules applied to the entire set of Customers & Orders than just a security issue, thus they rarely can be implemented in terms of ACL model.

Having Permission<T> class we can add there a property that will hold an IQueryable<T> object that will be used to describe the entire set of entities of type T that is accessible to a role that contains the permission.

Here is the definition of the Permission<T> class:

public class Permission<T> : Permission where T : class, IEntity
{
  public Func<SecurityContext, IQueryable<T>> Query { get; protected set; }
  ...
  public Permission(bool canWrite)
    : base(typeof(T), canWrite)
  {
    // If not set explicitly, all entities are accessible by default
    Query = context => context.Session.Query.All<T>();
  }

  public Permission(bool canWrite, Func<SecurityContext, IQueryable<T>> query)
    : base(typeof(T), canWrite)
  {
    Query = query;
  }
}

Having such an option, we can use it to explicitly define the accessibility-related business rules. Let's start with Sale Representative. This role restricts access to Order entities to those that are created by particular sales representative. So we have to define a rule that will build an IQueryable<Order> and register it properly. Here is how:

public class SalesRepresentativeRole : EmployeeRole
{
  private static IQueryable<Order> GetOrdersQuery(SecurityContext context)
  {
    // Sales representative role has access to its own orders only
    return context.Session.Query.All<Order>()
      .Where(o => o.Employee == context.User);
  }

  public SalesRepresentativeRole()
  {
    RegisterPermission(new OrderPermission(canWrite:true, canApprove:false, GetOrdersQuery));
  }
}

I won't focus on SecurityContext for now, The only thing that we should know about it is that it contains a Session and a User.

Note that we use Func<SecurityContext, IQueryable<T>> because we need to dynamically resolve the IQueryable<T> depending on the security context, currently active session, etc.

Using the same technique we can define restrictions for Sales Manager role:

  public class SalesManagerRole : SalesRepresentativeRole
  {
    private static IQueryable<Order> GetOrdersQuery(SecurityContext context)
    {
      // Sales manager role has access to its own orders as well as to orders of his department
      return context.Session.Query.All<Order>()
        .Where(
          o =>
          o.Employee == context.User ||
          o.Employee.In(
            context.Session.Query.All<Employee>().Where(e => e.ReportsTo == context.User)));
    }

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

Note that actually we don't need ACLs or something artificial for defining the restrictions. Imagine a database with thousands and billions of entities and every single one must have one or more records that store entity's owner & all users that should have access to it. That's not an option. Pure business objects from the domain model and their relationships are enough for business rules of almost arbitrary complexity.

Now let's define business rules for Sales Representative & Sales Manager roles for accessing Customer objects. There is a rule that says that both of them should deal only with those customers that are from their local region. In this sample there are 2 sales departments, one is located in London, another is in Seattle. Thus, I split all customers into 2 groups: customers from the Old World & customers from the New World. The first group is served by the London sales department, the second one is by the Seattle one.

To simplify things I added a helper class that contains 2 groups of countries:

public static class WellKnown
{
  public static IList<string> NewWorldCountries;
  public static IList<string> OldWorldCountries;

  static WellKnown()
  {
    NewWorldCountries = new List<string>()
                          {
                            "Argentina", "Australia", "Brazil", "Canada",
                            "Mexico", "USA", "Venezuela"
                          };
    OldWorldCountries = new List<string>()
                          {
                            "Austria", "Belgium", "Denmark", "Finland",
                            "France", "Germany", "Ireland", "Italy",
                            "Japan", "Netherlands", "Norway", "Poland",
                            "Portugal", "Singapore", "Spain", "Sweden",
                            "Switzerland", "UK"
                          };
  }
}

Having that done, I can describe the customers-related business rule as follow:

public class SalesRepresentativeRole : EmployeeRole
{
  private static IQueryable<Customer> GetCustomersQuery(SecurityContext context)
  {
    // Sales representative role has access to local customers only
    var employee = (Employee)context.User;
    if (employee.Address.Country.In(WellKnown.NewWorldCountries))
      return context.Session.Query.All<Customer>()
        .Where(c => c.Address.Country.In(WellKnown.NewWorldCountries));
    else
      return context.Session.Query.All<Customer>()
        .Where(c => c.Address.Country.In(WellKnown.OldWorldCountries));
  }

  public SalesRepresentativeRole()
  {
    // Sales representative can see and edit customers
    RegisterPermission(new Permission<Customer>(canWrite:true, GetCustomersQuery));
  }
}

Is't that good? As the role structure is hierarchical, this business rule is automatically inherited by all descendants of Sale Representative role, if not overridden. This means that there is no need to duplicate it in Sales Manager role. However, as Sales President role shouldn't have such a restriction we properly override that permission:

public class SalesPresidentRole : SalesManagerRole
{
  public SalesPresidentRole()
  {
    // Overriding the inherited permission with restriction, so this role will have access to all customers
    RegisterPermission(new Permission<Customer>());
    ...
  }
}

That's all for today. In the next part I'll describe the SecurityContext class and related stuff in details.

Big note for all:
The security concept I'm talking about is a prototype. Its purpose is to find out the better way to implement the security in DataObjects.Net. The only way I can see this can be done right is to criticize & discuss the thing before it is implemented and released. I know that the most of DataObjects.Net users are mature and experienced developers that have numerous properly built applications that have their own security systems. Therefore, I'm hoping that it will be better to join our efforts and make the security system that will perfectly fit our needs.

For now, I would like to specially thank Vlad Klekovkin for his critics of the prototype. Vlad also shared a concept of his own security system built on top of DataObjects.Net.
Thanks a lot, Vlad!

23 comments:

  1. For now what this prototype has, is enough for me. Maybe when it will go to some "alpha" testing of it, it will shows some fails of it (hope not). Good work as always :-)

    ReplyDelete
  2. Thanks, Peter.
    A corresponding sample is being developed to play with the security concept.

    ReplyDelete
  3. Good work Dmitri! This style of security is very new to me and it will be nice to run it.

    I like to develop with extensibility in mind and i can see this approach fitting into a dynamic discovery model.

    :)

    ReplyDelete
  4. Thanks, Malisa!
    The extensibility is one of the important things that should be taken into account while developing such models. That is why I'm asking for critics and ready to discuss any alternative approache.

    ReplyDelete
  5. Very nice! This will fit in our needs. Do you also support the concept of completely disabling the security system?

    ReplyDelete
  6. Thanks, Marco!
    Yes, I'm going to write about this a bit later, may be in the next post where the SecurityContext notion will be introduced.

    ReplyDelete
  7. And maybe some "ImpersonateAs..." as we does in our security system. Where we can in some situation impersonate as "System" or another "user" (this user is defined by some "token").

    ReplyDelete
  8. Dmitri: What do you think about such "impersonations" ?

    ReplyDelete
  9. Impersonation itself is a quite useful concept. However, it has sense in inflexible and restrictive security models mostly. I'm hoping to avoid such a special operation as "impersonation" in DataObjects.Net security. Let's see.

    ReplyDelete
  10. Ok, maybe such "impersonation" can be done as 3rd party solution :-)

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. Any progress on the implementation?

    ReplyDelete
  13. Hello Marco,

    Right now the team is focused on the Firebird & MySQL providers. Both of them will be included into the 4.5 Beta 1 and I'm hoping to publish the beta this week. After having this done, we will switch back to the security.

    ReplyDelete
  14. I was at a party in SF that had free estimate First Security Service protection, I felt much more comfortable having them there. I'm definitely going to use them next time I throw an event at our office!

    ReplyDelete
  15. Waooow!!! Magnificent blogs, this is what I wanted to search. Thanks buddykaty landscapers

    ReplyDelete
  16. Keep it up!! You have done the nice job having provided the latest information.divorce lawyers houston tx

    ReplyDelete
  17. Well, it’s a nice one, I have been looking for. Thanks for sharing such informative stuff.Lender

    ReplyDelete
  18. Nobody can reject the info you have given in the blogs, this is actually a great work.

    Bank

    ReplyDelete
  19. Your way to enlighten everything on this blog is actually pleasant, everyone manage to efficiently be familiar with it, Thanks a great deal.

    55printing

    ReplyDelete
  20. This is one of the most important blogs that I have seen, keep it up!

    top lawyers

    ReplyDelete
  21. Well, it’s a nice one, I have been looking for. Thanks for sharing such informative stuff.
    loans for bad credit with monthly payments

    ReplyDelete
  22. I will prefer this blog because it has much more informative stuff.
    cambridge digital marketing agency

    ReplyDelete