Hierarchical Security

Jul 16, 2014 at 1:04 PM
Me again. Last post for now - sry.
As VITA supports "authorization" out of the box, I wonder if I can model fairly complex security scenarios in a hierarchical environment, like department A has sub-departments and this can see all its contents.

I read two quite interesting articles on codeproject. One has a quite simple approach of a tree-like structure: here

The other one even builds a graph to allow for multiple parents:
theory
implementation

Do you think, I could easily adapt the second approach (graph based) with VITA?
Jul 16, 2014 at 5:43 PM
I will have a look at these and let you know how it fits. My initial guess is that the way authorization is designed in VITA, it should be able to incorporate hierarchies - you provide 'resolving lambdas' in DataFilters - c# snippets that link together objects (doc to user or his dept); these are free-form c# code that should be able to express wide range of constructs.
Jul 17, 2014 at 7:12 PM
Well, I've read it. What can I say... Nice attempts to do something, completely from scratch. It might work (worked in the past more likely, 7 years ago), in some very limited, closed for changes and variations environment, in one very specific application. Don't want to sound rogue and snobbish, but from today's prospective, these solutions are really simplistic, at the verge of being primitive, at least in terms of conceptual models - implementation with fancy SQLs is quite "sophisticated".
I'll go thru a few points of trouble, applicable to both of these solutions, mostly.
  1. Conceptual model - what are authorization-related objects, how to construct and define them. Both of the solutions heavily rely on a hierarchical, tree structure of the org model; the authorization grants derive directly from the user's place in this hierarchy. Several problems.
    1.1 Representing Org as a tree is a simplification that works only for very small orgs; in recent years the recommended pattern is matrix or even multi-dim cube; imagine a bank with branches all over the country. For employee in HR department in a branch office, there are two lines of subordination - branch manager and HR VP in central office. So hierarchy actually turns into matrix, and the same goes for all kinds of companies in manufacturing, retail, services, etc.
    1.2. The model of subordination breaks down completely if you have a web site where you provide services for paying customers (orgs) and their employees (Salesforce). You have site admin (system admin) who is definitely more powerful than Customer's power user, but this does not mean that system admins are bosses of everybody, and can see everything. All they can do is setup customer, grant them rights to manage users in their own domain. Also, if John is Mary's manager, it does not mean that he can see ALL Mary's info in HR app - so subordination in real life does not imply 'can see/use everything' from subordinate. It all depends, some info might be OK (salary) but others (pers info, medical ins-related info) might be extremely private. But these solutions actually imply this.
    1.3. There's no way to express any relation to data outside this 'hierarchy'. As an example, in VITA books sample app, book authors when logged in as users, can edit description of books they wrote. How to express this in these models? and this comes up all the time in real life systems.
Jul 17, 2014 at 7:33 PM
Edited Jul 17, 2014 at 7:34 PM
  1. Implementation.
    2.1 SQL-bound implementation. It is quite indicative that these articles are from 2005-2007, when memory was not as abundant and cheap as it is now. So to save memory, all the info is kept in the database, and all permission resolving logic is in SQLs. As a result, because of quite limited nature of SQL compared to say c#, the resulting capabilities are quite primitive. If you'd do it today, you'd probably load all basic permission info in memory, and then use c# and all fancy dictionaries and hash tables to prepare permission sets for immediate use. Who cares about extra few Mbs of RAM? Even in mobile devices, UI artifacts occupy way more than that. So the conclusion - we're way past that kind of solutions now.
    2.2. The other part of implementation - actual use in data access operations. A good system should be something that works in the background and almost invisible for business logic. Ideally, you supply the current user's context, and start reading/writing, and ORM or whatever you have quietly watches what you do and blows up as soon as your code violates its boundaries. (ex: Windows/Linux file system security). The solutions you refer to have nothing like this to offer. They provide explicit authorization API that you have to call explicitly in your code, every time you try access the data. This causes an extreme bloat of the code, and still is extremely error-prone. And very difficult to test.
    2.3. Column-level granularity - they simply don't provide these; one article mentions it and advises to split the tables to separate column groups that have different authorization rules. Not a good advice - messes up db schema, but main problem - it is not doable at all, because there might be several intercepting groups of columns with different level of access for different users. By the way, the access rights supported there are only yes/no - what about No/READ/WRITE?! - not supported and author admits it will complicate the system beyond limits.
    2.4. All permissions are static, granted forever in any context. Lately, from the needs of real world apps, a new concept emerged - Purpose-based authorization, when user is allowed access only within context of a certain function/screen. Like email - only for emergency contact, but not for marketing campaign screen. VITA by the way supports this, through dynamic grants
Jul 17, 2014 at 7:37 PM
The conclusion - the functionality described in these articles is quite primitive from today's point of view. With VITA you can easily implement the same things if you want to - in much easier to use and reliable manner. And definitely you can do much more.
Jul 17, 2014 at 10:10 PM
Hmmm... as you may be right that the authorization meachanisms those solutions provide are not sufficient to write every application, at least solution two gives a good solution to applications which require hierarchical authorization.
That, of course, doesn't mean I like every aspect of the graph-based solution.
Let me give you an example:
  • I once wrote an application for a client that required two hierarchical roles: managers and department-admins. The managers were associated with their departmets as where the department-admins. As the managers were allowed to see specific data (let's say discussions) of their departments and all sub-departments, the department-admins were only allowed to manage their own department, and the direct child departments. (Please don't ask me why - it was indeed the case...)
  • Now I need to create an application, where sys-admins can create workgroups. Each workgroup has specific roles such as contributor, viewer, approver (much like a Microsoft SharePoint Site if you want). Now each workgroup can be assigned to one, or more parent workgroups and the members of the parent workgroups shall have the same permissions to all child workgroups.
    From my point of view, the graph-based solution fits this scenario quite well. (Although I would only count the workspaces as resources and filter their contents, like documents, using "where" clauses)
So if VITA can solve this hierarchical-scenario (workgroups) better or differently, then I'd be happy to learn how.
Could you please sketch this for me?
Jul 18, 2014 at 4:17 PM
ok, I'll outline this, give me couple of days, a bit busy today and this weekend.
Jul 23, 2014 at 7:56 AM
Ok, sorry for the delay, here's the outline of the solution. I thought a bit how to present-explain it, even considered making up a separate mini-app, but in the end decided to 'extend' the Books sample. So, open the books sample in VS and follow me. We will be extending entity model (add a few entities) and then extend the role list in BooksAuthorizationHelper.cs.
The current authorization model defines several user types - WebVisitor, Author, BookEditor and StoreAdmin. We will focus on BookEditors - those who can edit IBook entities. Let's add departments hierarchy to Book store, with sub- and sub-sub-sub departments, like Tech, Arts, History, Tech/Computers, Tech/Computers/Programming etc. Each book is handled by a department. Book editors are assigned to department(s), with specific sub-type (EditorType) like Assistant, Reviewer, PublishingEditor. As in your example, if user is Reviewer in Dept X, he is in the same role in all child departments. So editing actions are allowed only for books assigned to departments in which the user is an editor with corresponding editor responsibility.
Here are extra entities:
  [Entity]
  public interface IDepartment {
    [PrimaryKey]
    Guid Id { get; }
    [Nullable]
    IDepartment Parent { get; set; }
    [Size(Sizes.Name)]
    string Name { get; set; }
  }

  [Entity, PrimaryKey("Editor,Department")]
  public interface IEditorDepartment {
    IUser Editor { get; set; }
    IDepartment Department { get; set; }
    EditorType EditorType { get; set; }
  }

  public enum EditorType {
    Assistant,
    Reviewer,
    PublishingEditor,
  }
  // We also add Department property to IBook entity:
     IDepartment Department {get;set;}
  // - so each book belongs to some department (Programming, Engineering, Arts, etc)
Jul 23, 2014 at 8:31 AM
Edited Jul 23, 2014 at 8:47 AM
One note to previous code: User as editor is assigned to a department we create a single IEditorDepartment record; the fact that the user is automatically an editor in all child departments is implied, without explicitly creating extra IEditorDepartment records.
Now we turn to BookAuthorizationHelper, Init method.
But before we start defining roles, we need some helper facilities. We will need to have a function that determines if a given user is in a certain EditorRole for department that book is assigned to:
private static bool CheckEditorDepartment(IBook book, Guid userId, EditorType editorType) { ... }
-returns true if the user is an Editor of given type in book's assigned department. Note that we need to check the department user explicitly assigned to AND all its child departments. To begin actual authorization check, we need to have the following table for a user:
  // Contains list of sets of departments in which user (identified by Id) is an editor of certain type
  //editor type -> set of department IDs (explicitly assigned dept and all child departments) 
  public class UserDepartmentTable : Dictionary<EditorType, HashSet<Guid>> { }
and a function that returns it:
    private static UserDepartmentTable GetUserDepartmentsTable(Guid userId) {
certainly the function should not rebuilt the table from scratch each time it is called - only first time, and then cache in static cache using ObjectCache or smth like that. For building the table, it's necessary to pre-build and keep forever a table of child departments for each departments (full child-relation transitive closure) - this is static table which should be built at startup and kept in memory. Just load all Department records and use it to build a dictionary (DeptId -> List-of-ChildIds)
Now the full code of CheckEditorDepartment function:
    private static bool CheckEditorDepartment(IBook book, Guid userId, EditorType editorType) {
      var editorDepts = GetUserDepartmentsTable(userId);
      if(editorDepts == null || editorDepts.Count == 0)
        return false;
      HashSet<Guid> depts;
      if(!editorDepts.TryGetValue(editorType, out depts))
        return false;
      var bookDeptId = book.Department.Id;
      var match = depts.Contains(bookDeptId);
      return match; 
    }
We are now ready to start building authorization roles
Jul 23, 2014 at 9:31 AM
We will create 3 activities, one for each editor type, and grant these activities to the same role we have currently: BookEditor (see BooksAuthorizationHelper.Init method).
Currently, we have a single book editing activity and it is added to BookEditor role directly, without data filter. In new setup we will grant activities using data filters based on the CheckEditorDepartment function.
So we should define 3 new editing activities using specific permissions, similar to how it's currently done with other activities. I skip the code, assuming you can figure it out. Now let's say you have an activity representing editing by an Assistant editor - assistantEditorEditing. We define a data filter and grant activity to BookEditor role using this data filter:
      Activity assistantEditorEditing = new Activity("AssistantEditorEditing", new Permission[]{ .. .. .. });
      var asstDataFilter = new DataFilter<Guid>("BookEditorDataFilter", userIdReader); 
      asstDataFilter.AddEntity<IBook>((bk, userId) => CheckEditorDepartment(bk, userId, EditorType.Assistant) );
      BookEditor.Grant(asstDataFilter, assistantEditorEditing); 
userIdReader is the same ContextValueReader that we define at the beginning of Init method, it reads UserId from the operation context. Note that a lambda method for IBook entity in data filter used in AddEntity call, uses CheckEditorDepartment function we defined previously, with fixed EditorType (Assistant).
Repeat the process for other editor types. As for the code that opens sessions and executes actual calls - nothing changes, see AuthorizationTests in unit tests project.
Jul 25, 2014 at 9:02 AM
Wow thanks! I'll definetly try this out. :)
Jul 25, 2014 at 9:36 PM
good luck!
One afterthought. CheckEditorDepartment method - better version would be with first parameter as IDepartment or DepartmentId (Guid). So when you call it in lambda, you use 'bk.Department'. Then you can reuse the function for other entities like IBookReview, using 'review.Book.Department', etc.