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. |