In the previous part we discussed the roles and permissions in terms of restricting LINQ queries. This time let's focus on users, passwords and validation.
First of all, according to the requirements to the security system listed in the very first post concerning this topic, we announced that it would be good if we integrate somehow with the core .NET security interfaces, like IPrincipal & IIdentity. As a result, our core IPrincipal interface inherits System.Security.IPrincipal interface and adds 2 persistent fields and 1 method:
public interface IPrincipal : IEntity, System.Security.Principal.IPrincipal { [Field] string Name { get; } [Field] [Association(PairTo = "Principal", OnOwnerRemove = OnRemoveAction.Cascade)] PrincipalRoleSet PrincipalRoles { get; } bool IsInRole(Role role); }
Field 'Name' is used for storing identity information in storage. 'PrincipalRoles' is a set of roles that a principal owns. The PrincipalRole type is just a auxiliary type that help associate a Principal with its roles. A role itself is not persistent type, so we use its name as a reference.
[HierarchyRoot] [KeyGenerator(KeyGeneratorKind.None)] public class PrincipalRole : Entity { [Field, Key(0)] public IPrincipal Principal { get; private set; } [Field(Length = 50), Key(1)] public string Name { get; private set; } public PrincipalRole (Session session, IPrincipal principal, string name) : base(session, principal, name) {} }
In order not to realize all functionality of IPrincipal interface by yourselves, there is 2 base implementations: Principal & GenericPrincipal. The first one is pure implementation of the IPrincipal interface with no additional stuff. Here it is:
public abstract class Principal : Entity, IPrincipal { [NotNullConstraint(Mode = ConstrainMode.OnValidate)] [Field(Length = 50, Indexed = true)] public string Name { get; set; } public virtual IIdentity Identity { get { return new GenericIdentity(Name); } } [Field] public PrincipalRoleSet PrincipalRoles { get; private set; } public bool IsInRole(string role) { return PrincipalRoles.Any(r => r.Name == role); } public bool IsInRole(Role role) { return PrincipalRoles.Contains(role); } }
The second persistent class called GenericPrincipal is designed for use in scenarios with username/password matching:
public abstract class GenericPrincipal : Principal { [Field(Length = 50)] public string Password { get; protected set; } public virtual void SetPassword(string password); }Note that both classes neither define Key fields nor apply HierarchyRoot attribute because no prediction can be made about such properties in your domain models. However, this means that you should add your own persistent type called User or similar and inherit from Principal or GenericPrinipal types in order to use all features of the security system. No additional coding is required, just inherit one of them, define Key field(s) and apply HierarchyRoot on top and that's all.
As GenericPrincipal type is intended to be used in username/password scenarios, the next question is: who will be responsible for possible encryption of passwords and how users will be validated/authenticated?
The previous version of DataObjects.Net provided several password encryption algorithms you could choose from, however the possibility to implement your own one was absent. This time we are going to provide a generic interface for string encryption as well as several common implementations. Customers will have the ability to make their own implementations and plug them in. Here is the interface:
public interface IEncryptionService : IDomainService { string Encrypt(string value); }
It is absolutely simple and straightforward. For now there are 2 realizations: PlainEncryptionService that actually doesn't make any encryption at all and could be used for testing purposes, and Md5EncryptionService. I'll show how to configure the services later.
The next part is user authentication. Keeping in mind that DataObjects.Net-based applications might operate with various types of principals, for example, Windows principal or generic one with username/password authorization scheme, we provide the following generic interface for this task:
public interface IPrincipalValidationService : ISessionService { IPrincipal Validate(IIdentity identity, params object[] args); IPrincipal Validate(string name, params object[] args); }
The method that takes IIdentity as the first parameter is more generic, it might take WindowsIdentity as well as any other IIdentity descendants. The second argument is an array of values that are used for authentication, these could be passwords, tokens, tickets, you name it. The framework will provide base user validation service for username/password authentication scheme, and you'll have the ability to make your own ones.
It seems that there is nothing left to describe. In the next part we'll try making our first sample with integrated security. Stay tuned.