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

Sunday, June 26, 2011

DataObjects.Net 4.5 Beta 3

Today DataObjects.Net team is happy to publish the third beta of 4.5 version. This release mostly contains changes in the security system as well as bug fixes merged from the stable branch.

After the Beta 2 was published, we received great feedback concerning the security system and we are very glad for that. Thanks to all of you who dropped us a line concerning this stuff.

Changes from Beta 2

First and the most important one is that roles are made persistent. There were numerous requests for this feature, moreover the idea of Terje Myklebust that he formulated as a templates for roles fits to this pattern very well — a persistent class serves as a template, so you can have as many parameterized instances of it as you need. Here is the updated Role hierarchy:
public interface IRole : IEntity
  {
    [Field]
    string Name { get; }

    [Field]
    [Association(PairTo = "Roles", OnOwnerRemove = OnRemoveAction.Clear, OnTargetRemove = OnRemoveAction.Clear)]
    EntitySet Principals { get; }

    IList Permissions { get; }
  }

  [Index("Name", Unique = true)]
  public abstract class Role : Entity, IRole
  {
    [NotNullConstraint(Mode = ConstrainMode.OnSetValue)]
    [Field(Length = 128)]
    public string Name { get; protected set; }

    [Field]
    public EntitySet Principals { get; private set; }

    public IList Permissions { get; }

    protected void RegisterPermission(Permission permission);

    protected abstract void RegisterPermissions();

    protected Role(Session session)
      : base(session)
    {
      Name = GetType().Name;
    }
  }
Xtensive.Practices.Security contains the abstract class Role that implements all the required infrastructure for permission registration and handling. Each instance of Role must contain unique name. By default, Role class sets name property to name of the concrete type. In case you want more than one instances of a role of the same type, you must give them unique names. Role and Principal now are connected via paired entitysets. So a principal knows all its roles and vice versa.

Note that the Role class doesn't define hierarchy root and key fields. You must do this manually as you do in case of Principal, for example:

[HierarchyRoot(InheritanceSchema = InheritanceSchema.SingleTable)]
  public abstract class EmployeeRole : Role
  {
    [Field, Key]
    public int Id { get; set; }

    protected override void RegisterPermissions()
    {
      // This is base role for every employee

      // All employees can see products
      RegisterPermission(new Permission());
      // All employees can see employees
      RegisterPermission(new Permission());
    }

    protected EmployeeRole(Session session)
      : base(session)
    {
    }
  }
All other roles inherit EmployeeRole. Note that in case of roles the SingleTable inheritance scheme is the best option as all role types are similar and can be effectively placed inside a single table.

Other changes:

Session.ValidatePrincipal method is renamed to Session.Authenticate.
IPrincipalValidationService is renamed to IAuthenticationService
GenericPrincipalValidationService is renamed to GenericAuthenticationService
GenericRoleProvider, PrincipalRole, PrincipalRoleSet, RoleSet are discarded as useless.

Roles as templates

There was a fruitful discussion after the previous post and the idea of role templates was suggested. Here is how it can be implemented in the Beta 3. I'll take the same scenario with branches and office manager roles.

Fisrt, let's define branches
[HierarchyRoot]
  public class Branch : Entity
  {
    [Field, Key]
    public int Id { get; private set; }

    [Field]
    public string Name { get; set; }

    public Branch(Session session)
      : base(session)
    {}
  }
And then, define a branch office manager role
public class BranchOfficeManagerRole : EmployeeRole
  {
    [Field]
    public Branch Branch { get; set; }

    private IQueryable GetCustomers(ImpersonationContext context, QueryEndpoint query)
    {
      return query.All()
        .Where(c => c.Branch == Branch);
    }

    protected override void RegisterPermissions()
    {
      RegisterPermission(new Permission(true, GetCustomers));
    }

    public BranchOfficeManagerRole(Session session, Branch branch)
      : base(session)
    {
      Branch = branch;
      Name = branch.Name + "OfficeManager";
    }
  }

See, the role class is not tied to a concrete branch office instance, it only declares a connection between a role type and a branch type instead. The concrete Branch instance is passed in the constructor. Also note how Name property is formed in the constructor to avoid non-uniqueness.

Having these classes declared we can easily use them in creating the appropriate role instances as many as we need. Moreover, we can create branches and branch-dependent roles in runtime.
// Branches
var southBranch = new Branch(session) { Name = "South"};
var northBranch = new Branch(session) { Name = "North"};

// Roles
var southBranchOfficeManagerRole =  new BranchOfficeManagerRole(session, southBranch);
var northBranchOfficeManagerRole =  new BranchOfficeManagerRole(session, northBranch);

// Employees
var user1 = new Employee(session);
user1.Roles.Add(southBranchOfficeManagerRole);

var user2 = new Employee(session);
user2.Roles.Add(northBranchOfficeManagerRole);

// By adding both roles to an employee we can give him a superset of permissions
var user3 = new Employee(session);
user3.Roles.Add(southBranchOfficeManagerRole);
user3.Roles.Add(northBranchOfficeManagerRole);

Download

DataObjects.Net 4.5 Beta 3 can be downloaded from the website.

P.S.
Dear DataObjects.Net users and contributors. The Beta 3 is very close to the Release Candidate. But before the main branch is frozen, I want to clarify
a question: should permissions be persistent or not? Is it vital? If yes, how filter expressions should be stored?

If the question is given a sensible answer, we'll continue implementing persistent permissions and make Beta 4; otherwise we will proceed to RC 1.

Thank you.

P.P.S.
Dear Malisa Ncube,
It seems that after these alterations you'll have to update the SalesPoint database creation script for MySQL. Sorry for that, hope this won't tale too much time.

2 comments:

  1. Good work Dmitri. Thanks for making the roles persistent, it fits into most models we use.

    I'm not quite sure about how you'd make the permissions persistent and specifically store the filter expressions. I can only think about dynamic linq for now, but i wonder who would be making modifications to those stored expressions.

    The end-user would certainly not want to touch that, unless if there is a friendly designer - which i think is too much work.

    On MySQL conversion, I will see about that during the course of the week, or maybe I should wait until there is a more final sample. ;)

    ReplyDelete