Authzdesign » History » Version 6
Shuvam Misra, 22/06/2018 06:20 PM
| 1 | 3 | Shuvam Misra | # Authorization module: core design |
|---|---|---|---|
| 2 | 1 | Shuvam Misra | |
| 3 | The authorization module uses a set of rules in a rulebase to decide who can perform what action on what object. "Who" here is a human user, "what action" is an operation performed on a computer system like a voucher entry, a report viewing, or the adding of a new task in a task tracker. "What object" here means an information entity like a salary record, a voucher, a task list, *etc*. |
||
| 4 | |||
| 5 | {{>toc}} |
||
| 6 | |||
| 7 | 2 | Shuvam Misra | This page describes a design which we can use for any traditional business application. |
| 8 | 1 | Shuvam Misra | |
| 9 | 2 | Shuvam Misra | ## Features |
| 10 | |||
| 11 | * *Application independent*: can be used with any business application |
||
| 12 | 5 | Shuvam Misra | |
| 13 | 6 | Shuvam Misra | * *Useful for front and back*: it is useful for web service calls to decide whether the curent caller can be permitted to execute the call, and is also useful for Javascript code in the front-end to decide whether to display certain buttons or sections of screens at run-time, depending on a common set of access rules. The set of access rules which apply to a user session are described in-memory in a JSON tree data structure. |
| 14 | 5 | Shuvam Misra | |
| 15 | 2 | Shuvam Misra | * *Supports access per object instance*: it supports access controls for specific operations on specific object instances too, *e.g.* can User `rohit` update rates and terms on a specific Purchase Order? This is very powerful for large, critical and long-lasting object instances like tender bids, project plans for large projects, *etc* |
| 16 | 5 | Shuvam Misra | |
| 17 | 4 | Shuvam Misra | * *Associate an access rule with a part of an object*: an access rule can carry additional information which associates a part of an object with a permission, *e.g.* this edit permission applies to Section Two of a specific bid document. |
| 18 | 5 | Shuvam Misra | |
| 19 | 2 | Shuvam Misra | * *Language independent*: may be implemented in any programming language |
| 20 | 5 | Shuvam Misra | |
| 21 | 2 | Shuvam Misra | * *High performance*: requires some data structure processing at user login time, but can then be accessed for very fast go-no-go lookups with each operation or web service call |
| 22 | 1 | Shuvam Misra | |
| 23 | ## The basic entities |
||
| 24 | |||
| 25 | Any authorisation system deals with the following entities: |
||
| 26 | |||
| 27 | * **user**: the human user |
||
| 28 | * **data objects**: the item, or set of items, or class of items, on which operations are being performed |
||
| 29 | * **operations**: the operations being attempted |
||
| 30 | * **permissions**: rules which specify which combinations of the above three entities are permitted. All else are blocked. |
||
| 31 | |||
| 32 | Additional derived entities, defined for convenience, are |
||
| 33 | |||
| 34 | * a group of users, called **user groups** |
||
| 35 | * a group of permissions, sometimes called **roles** |
||
| 36 | |||
| 37 | This design assumes that there is a user master table, a group master table, and a groupusersmap table which maps users to groups of which they are members. |
||
| 38 | |||
| 39 | ## The structure of an access rule |
||
| 40 | |||
| 41 | We need to understand the demands on the access rule. |
||
| 42 | |||
| 43 | In its simplest form, a rule will just have three parts: |
||
| 44 | |||
| 45 | * which user the rule applies to: *e.g.* "user `sanjeev`" |
||
| 46 | * which entity the rule applies to: *e.g.* "vouchers" (a class of objects, not just a single object) |
||
| 47 | * which operation is being permitted by the rule: *e.g.* "create" |
||
| 48 | |||
| 49 | This three-part rule says "User `sanjeev` can create vouchers". |
||
| 50 | |||
| 51 | For simple designs, this can even be combined to a two-part rule: |
||
| 52 | |||
| 53 | * the user (`sanjeev`) |
||
| 54 | * the privilege (`vouchercreate`) |
||
| 55 | |||
| 56 | where you combine the class of objects with the operation name and call this combined thing the privilege being given. |
||
| 57 | |||
| 58 | We need more sophistication than this for more complex business applications. |
||
| 59 | |||
| 60 | * **Access to a specific object**: we may want to give User `sanjeev` access to modify a specific indent or purchase order, but not give him the right to do the same with all indents or purchase orders. In our language we are giving a user *ad hoc* access to a specific *object instance*, not a *class of objects*. |
||
| 61 | * **Access to a sub-object**: we may want to give User `sanjeev` access to the Vendor Details section of a specific purchase order, not all sections. We may want to give User `galahad` access to Tax Computations section of the same purchase order. This requires our access rule to have the ability to refer to a sub-object. |
||
| 62 | |||
| 63 | Note that the complexity of a sub-object reference only comes when we need to give a user rights to a specific sub-object of a *specific object instance*. If we had to give the user rights to a sub-object of a whole class of objects, then we would just have re-defined our object definition to refer to a specific section of the object, not the whole object. For instance, if we needed to give `sanjeev` access to the Vendor Details section of *all Purchase Orders*, we would simply have re-defined our object to refer to "`povendorsection`" instead of the entire "`po`". And then we would have given `sanjeev` the access to all `povendorsection`. The real complexity comes when we want to give `sanjeev` the access to the Vendor Details section of *a specific Purchase Order*. In that case, we need to add a new attribute to the access rule. |
||
| 64 | |||
| 65 | So, the final structure of an access rule can be as follows: |
||
| 66 | |||
| 67 | * **ID**: a mandatory, unique ID, the primary key for database access and cross-reference purposes. |
||
| 68 | * **usertype**: one char, with "`U`" for user and "`G`" for user-group. Mandatory, non-NULL |
||
| 69 | * **who**: string, will contain a username or a groupname. Mandatory non-NULL. A "`*`" here means that this rule applies to all users without exception. |
||
| 70 | * **resource**: string, will specify the resource on which the access control will apply. This may have a path notation to identify a module or sub-module in a hierarchy. This allows us to specify an access control at any level of a hierarchy. Possible values could be "`ui/fa`" meaning the UI components of the Financial Accounting system, or "`ws/fa`" meaning the web services of the FA system, or "`ws/fa/vouchers`" to indicate web services of voucher management within the FA system, and so on. Mandatory, non-NULL. |
||
| 71 | * **instance**: string, may contain the ID of a specific resource instance of the type or class indicated by **`resource`**. For instances, if the **`resource`** fields specified "`ws/fa/vouchers`" and the **`instance`** has the value "`20a00bce`", then this is the unique ID of a specific voucher on which the access control is being applied. Optional, may be empty. |
||
| 72 | * **part**: a value indicating a section or part of the object instance on which the access control is being applied. The object instance itself is identified in the **`instance`** attribute above. Examples could be "`vendordetails`", "`candidate[02]`" (for the second candidate in a list of candidates), and so on. Optional, may be NULL. |
||
| 73 | * **action**: a value indicating the operation being permitted, from the enumerated set of all possible operations. For UI related operations, values could be `show`, `edit`, `delete`, *etc*. For web services, operations could be `get`, `update`, `create`, *etc*. There could be any number of possible operations, and a complete set can only be defined in the context of the application and the business operations it supports. |
||
| 74 | |||
| 75 | That's it. Six useful fields (excluding the ID) can capture details of all possible access rules. |
||
| 76 | |||
| 77 | If the system permits it, the **`action`** attribute may be multi-valued, and can carry a list of operations which are permitted. If the system does not support multi-valued attributes, the entire rule will have to be repeated in the database table for each valiue of **`action`**. |
||
| 78 | |||
| 79 | ## Supporting the idea of roles |
||
| 80 | |||
| 81 | A role is a collection of access rules. It's a convenience. If all Sales Executives need to be given a set of 23 access permissions, it's nice to be able to group them into something called a "role", and then assign permission to users based on such roles. |
||
| 82 | |||
| 83 | A Roles master can contain the following useful fields: |
||
| 84 | |||
| 85 | * **ID**: the mandatory unique ID column |
||
| 86 | * **descr**: a string description of the role, for human consumption |
||
| 87 | |||
| 88 | The access rules table's **`who`** attribute, which is already capable of holding usernames or user-group names, can now be extended to hold role IDs too, and the **usertype** attribute will then contain "`R`". This means that |
||
| 89 | |||
| 90 | * an ordinary user can be linked to an access rule, |
||
| 91 | * a user-group can be associated with the rule, or |
||
| 92 | * a role can be associated with it |
||
| 93 | |||
| 94 | In addition to this, a roles mapping table is needed, to map a role to either individual users or user-groups. This table will have just three columns: |
||
| 95 | |||
| 96 | * **role**: the ID of the role record in the Roles master |
||
| 97 | * **usertype**: a single-character field, holding "`U`" or "`G`" |
||
| 98 | * **who**: the username of a user, or the groupname of a group |
||
| 99 | |||
| 100 | All columns here are mandatory non-NULL, and the uniqueness criterion will apply to the entire 3-tuple. |
||
| 101 | |||
| 102 | ## Summary of tables |
||
| 103 | |||
| 104 | * **`accessrules`** |
||
| 105 | * **`ID`** |
||
| 106 | * **`usertype`** |
||
| 107 | * **`who`** |
||
| 108 | * **`resource`** |
||
| 109 | * **`instance`** |
||
| 110 | * **`part`** |
||
| 111 | * **`action`** |
||
| 112 | |||
| 113 | |||
| 114 | * **`roles`** |
||
| 115 | * **`ID`** |
||
| 116 | * **`descr`** |
||
| 117 | |||
| 118 | * **`rolemembersmap`** |
||
| 119 | * **`role`** |
||
| 120 | * **`usertype`** |
||
| 121 | * **`who`** |
||
| 122 | |||
| 123 | ## Loading an access profile |
||
| 124 | |||
| 125 | When a user logs in to the application, her access profile should be loaded from database into in-core storage or a fast cache so that it can be traversed rapidly at each subsequent request or operation to check whether the user is permitted to do so. |
||
| 126 | |||
| 127 | ``` |
||
| 128 | load_access_profile(user, rulestree) |
||
| 129 | groupslist = load all user-group names from groups table |
||
| 130 | where user is a member |
||
| 131 | roleslist = load all roles from rolemembersmap where |
||
| 132 | (usertype == 'U' and who == username) OR |
||
| 133 | (usertype == 'G' and who exists in groupslist) |
||
| 134 | |||
| 135 | ruleIDlist = load the IDs of all rules from accessrules where |
||
| 136 | (usertype == 'U' and who == username) OR |
||
| 137 | (usertype == 'G' and who exists in groupslist) OR |
||
| 138 | (usertype == 'R' and who exists in roleslist) OR |
||
| 139 | (who == '*') |
||
| 140 | |||
| 141 | // we now have the IDs of all the rules which apply to this user |
||
| 142 | |||
| 143 | // We now convert the set of lists into a tree data structure, |
||
| 144 | // based on the paths in the "resource" attributes of the rules. |
||
| 145 | // Any rule with "resource" == "ui" or "ws" or just a one-part |
||
| 146 | // path will be associated with a Level 1 node just below the root. |
||
| 147 | // Any rule with "resource" == "ui/fa" will be associated with |
||
| 148 | // a Level 2 node, labelled "fa", below the "ui" node. And so on. |
||
| 149 | // |
||
| 150 | // Each node in the tree has a set of one or more access rules and |
||
| 151 | // their associated attributes. |
||
| 152 | |||
| 153 | rulestree = rules_list_to_tree(ruleIDlist) |
||
| 154 | end procedure |
||
| 155 | ``` |
||
| 156 | |||
| 157 | This process creates an in-memory tree data structure which can be traversed very efficiently whenever a specific access is attempted. |
||
| 158 | |||
| 159 | ## Checking access |
||
| 160 | |||
| 161 | Whenever any access is attempted, a function "`is_allowed()`" is called, which traverses the "`rulestree`" created in the loading step, and returns a boolean "`TRUE`" or "`FALSE`". The "`is_allowed()`" function is called with: |
||
| 162 | |||
| 163 | * `user`: the username of the user who is attempting the operation |
||
| 164 | * `resource`: the resource type being operated upon. May in some cases just identify a module or a resource class. |
||
| 165 | * `instance`: the unique ID of the specific instance of type `resource`, *e.g.* Purchase Order ID or voucher ID. May be null for certain operations, for instance an operation which impacts all or many instances of the resource type |
||
| 166 | * `part`: a path string indicating the sub-part of the resource which is being operated upon. This may be NULL. |
||
| 167 | * `operation`: an identifier indicating what is the operation being attempted. |
||
| 168 | |||
| 169 | |||
| 170 | ``` |
||
| 171 | boolean |
||
| 172 | is_allowed(user, resource, instance, part, operation) |
||
| 173 | patharray[] = break resource into parts at "/" |
||
| 174 | thisnode = rulestree // initialise to the root node of tree |
||
| 175 | |||
| 176 | for each pathstep in patharray[] do |
||
| 177 | if any of the rules at thisnode matches the other parameters of the call, then |
||
| 178 | return TRUE |
||
| 179 | else if thisnode.childnode corresponding to pathstep exists then |
||
| 180 | thisnode = thisnode.childnode |
||
| 181 | else |
||
| 182 | return FALSE |
||
| 183 | endif |
||
| 184 | endfor |
||
| 185 | |||
| 186 | return FALSE |
||
| 187 | end function |
||
| 188 | ``` |
||
| 189 | |||
| 190 | This function will be called by every web service call which requires authentication, and if the `rulestree` data structure is passed to the front-end Javascript code at login time, then it can be used by the front-end code too, to decide which UI segments to display or hide. |