Authzdesign » History » Version 4
Shuvam Misra, 22/06/2018 06:18 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 | * *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. |
||
13 | * *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* |
||
14 | 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. |
15 | 2 | Shuvam Misra | * *Language independent*: may be implemented in any programming language |
16 | * *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 |
||
17 | 1 | Shuvam Misra | |
18 | ## The basic entities |
||
19 | |||
20 | Any authorisation system deals with the following entities: |
||
21 | |||
22 | * **user**: the human user |
||
23 | * **data objects**: the item, or set of items, or class of items, on which operations are being performed |
||
24 | * **operations**: the operations being attempted |
||
25 | * **permissions**: rules which specify which combinations of the above three entities are permitted. All else are blocked. |
||
26 | |||
27 | Additional derived entities, defined for convenience, are |
||
28 | |||
29 | * a group of users, called **user groups** |
||
30 | * a group of permissions, sometimes called **roles** |
||
31 | |||
32 | 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. |
||
33 | |||
34 | ## The structure of an access rule |
||
35 | |||
36 | We need to understand the demands on the access rule. |
||
37 | |||
38 | In its simplest form, a rule will just have three parts: |
||
39 | |||
40 | * which user the rule applies to: *e.g.* "user `sanjeev`" |
||
41 | * which entity the rule applies to: *e.g.* "vouchers" (a class of objects, not just a single object) |
||
42 | * which operation is being permitted by the rule: *e.g.* "create" |
||
43 | |||
44 | This three-part rule says "User `sanjeev` can create vouchers". |
||
45 | |||
46 | For simple designs, this can even be combined to a two-part rule: |
||
47 | |||
48 | * the user (`sanjeev`) |
||
49 | * the privilege (`vouchercreate`) |
||
50 | |||
51 | where you combine the class of objects with the operation name and call this combined thing the privilege being given. |
||
52 | |||
53 | We need more sophistication than this for more complex business applications. |
||
54 | |||
55 | * **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*. |
||
56 | * **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. |
||
57 | |||
58 | 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. |
||
59 | |||
60 | So, the final structure of an access rule can be as follows: |
||
61 | |||
62 | * **ID**: a mandatory, unique ID, the primary key for database access and cross-reference purposes. |
||
63 | * **usertype**: one char, with "`U`" for user and "`G`" for user-group. Mandatory, non-NULL |
||
64 | * **who**: string, will contain a username or a groupname. Mandatory non-NULL. A "`*`" here means that this rule applies to all users without exception. |
||
65 | * **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. |
||
66 | * **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. |
||
67 | * **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. |
||
68 | * **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. |
||
69 | |||
70 | That's it. Six useful fields (excluding the ID) can capture details of all possible access rules. |
||
71 | |||
72 | 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`**. |
||
73 | |||
74 | ## Supporting the idea of roles |
||
75 | |||
76 | 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. |
||
77 | |||
78 | A Roles master can contain the following useful fields: |
||
79 | |||
80 | * **ID**: the mandatory unique ID column |
||
81 | * **descr**: a string description of the role, for human consumption |
||
82 | |||
83 | 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 |
||
84 | |||
85 | * an ordinary user can be linked to an access rule, |
||
86 | * a user-group can be associated with the rule, or |
||
87 | * a role can be associated with it |
||
88 | |||
89 | 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: |
||
90 | |||
91 | * **role**: the ID of the role record in the Roles master |
||
92 | * **usertype**: a single-character field, holding "`U`" or "`G`" |
||
93 | * **who**: the username of a user, or the groupname of a group |
||
94 | |||
95 | All columns here are mandatory non-NULL, and the uniqueness criterion will apply to the entire 3-tuple. |
||
96 | |||
97 | ## Summary of tables |
||
98 | |||
99 | * **`accessrules`** |
||
100 | * **`ID`** |
||
101 | * **`usertype`** |
||
102 | * **`who`** |
||
103 | * **`resource`** |
||
104 | * **`instance`** |
||
105 | * **`part`** |
||
106 | * **`action`** |
||
107 | |||
108 | |||
109 | * **`roles`** |
||
110 | * **`ID`** |
||
111 | * **`descr`** |
||
112 | |||
113 | * **`rolemembersmap`** |
||
114 | * **`role`** |
||
115 | * **`usertype`** |
||
116 | * **`who`** |
||
117 | |||
118 | ## Loading an access profile |
||
119 | |||
120 | 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. |
||
121 | |||
122 | ``` |
||
123 | load_access_profile(user, rulestree) |
||
124 | groupslist = load all user-group names from groups table |
||
125 | where user is a member |
||
126 | roleslist = load all roles from rolemembersmap where |
||
127 | (usertype == 'U' and who == username) OR |
||
128 | (usertype == 'G' and who exists in groupslist) |
||
129 | |||
130 | ruleIDlist = load the IDs of all rules from accessrules where |
||
131 | (usertype == 'U' and who == username) OR |
||
132 | (usertype == 'G' and who exists in groupslist) OR |
||
133 | (usertype == 'R' and who exists in roleslist) OR |
||
134 | (who == '*') |
||
135 | |||
136 | // we now have the IDs of all the rules which apply to this user |
||
137 | |||
138 | // We now convert the set of lists into a tree data structure, |
||
139 | // based on the paths in the "resource" attributes of the rules. |
||
140 | // Any rule with "resource" == "ui" or "ws" or just a one-part |
||
141 | // path will be associated with a Level 1 node just below the root. |
||
142 | // Any rule with "resource" == "ui/fa" will be associated with |
||
143 | // a Level 2 node, labelled "fa", below the "ui" node. And so on. |
||
144 | // |
||
145 | // Each node in the tree has a set of one or more access rules and |
||
146 | // their associated attributes. |
||
147 | |||
148 | rulestree = rules_list_to_tree(ruleIDlist) |
||
149 | end procedure |
||
150 | ``` |
||
151 | |||
152 | This process creates an in-memory tree data structure which can be traversed very efficiently whenever a specific access is attempted. |
||
153 | |||
154 | ## Checking access |
||
155 | |||
156 | 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: |
||
157 | |||
158 | * `user`: the username of the user who is attempting the operation |
||
159 | * `resource`: the resource type being operated upon. May in some cases just identify a module or a resource class. |
||
160 | * `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 |
||
161 | * `part`: a path string indicating the sub-part of the resource which is being operated upon. This may be NULL. |
||
162 | * `operation`: an identifier indicating what is the operation being attempted. |
||
163 | |||
164 | |||
165 | ``` |
||
166 | boolean |
||
167 | is_allowed(user, resource, instance, part, operation) |
||
168 | patharray[] = break resource into parts at "/" |
||
169 | thisnode = rulestree // initialise to the root node of tree |
||
170 | |||
171 | for each pathstep in patharray[] do |
||
172 | if any of the rules at thisnode matches the other parameters of the call, then |
||
173 | return TRUE |
||
174 | else if thisnode.childnode corresponding to pathstep exists then |
||
175 | thisnode = thisnode.childnode |
||
176 | else |
||
177 | return FALSE |
||
178 | endif |
||
179 | endfor |
||
180 | |||
181 | return FALSE |
||
182 | end function |
||
183 | ``` |
||
184 | |||
185 | 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. |