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)] EntitySetXtensive.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.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; } }
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 PermissionAll 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.()); // All employees can see employees RegisterPermission(new Permission ()); } protected EmployeeRole(Session session) : base(session) { } }
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 IQueryableGetCustomers(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.