Coverage for control/perm.py : 86%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Permission library
3* Computing permissions
4* Support for the authorization system
5"""
7from config import Config as C, Names as N
8from control.utils import pick as G
10CT = C.tables
11CP = C.perm
13DEFAULT_PERM = CP.default
14GROUP_RANK = CP.groupRank
15TABLE_PERM = CT.perm
17NOBODY = N.nobody
18UNAUTH = N.public
19AUTH = N.auth
20EDIT = N.edit
21OWN = N.own
22COORD = N.coord
23OFFICE = N.office
24SYSTEM = N.system
25ROOT = N.root
27ALLOW_OUR = set(CT.userTables) | set(CT.userEntryTables)
30def checkTable(
31 auth, table
32):
33 """Verify whether user's credentials match the requirements for a table.
35 Every table can only be listed on the interface if the user has permissions
36 for it.
37 The table permissions are configured in `tables.yaml`, under the key `perm`.
38 For each table the minimal role is stated that can access the table.
40 If a table is not permitted, then its records are also not permitted,
41 neither are fields in those records.
43 However, this holds for the generic interface. Individual tables and records
44 and fields may be regulated in addition by
45 `control.workflow.apply.WorkflowItem` conditions,
46 which can open up and close off material.
48 Parameters
49 ----------
50 auth: object
51 The `control.auth.Auth` singleton, from which the group of the current
52 user can be obtained.
53 table: string
54 The table for which permission is required.
56 Returns
57 -------
58 boolean
59 """
61 require = G(TABLE_PERM, table)
62 if require is None: 62 ↛ 63line 62 didn't jump to line 63, because the condition on line 62 was never true
63 return False
65 if require == UNAUTH:
66 return True
68 group = auth.groupRep()
70 if require == NOBODY: 70 ↛ 71line 70 didn't jump to line 71, because the condition on line 70 was never true
71 return False
73 if require == AUTH: 73 ↛ 74line 73 didn't jump to line 74, because the condition on line 73 was never true
74 return group != UNAUTH
76 isSuper = group in {OFFICE, SYSTEM, ROOT}
78 if require == OFFICE:
79 return isSuper
81 if require == SYSTEM: 81 ↛ 84line 81 didn't jump to line 84, because the condition on line 81 was never false
82 return group in {SYSTEM, ROOT}
84 if require == ROOT:
85 return group == ROOT
87 if require == COORD:
88 return group == COORD or isSuper
91def checkPerm(require, perm):
92 """Verify whether user's credentials match the requirements.
94 Parameters
95 ----------
96 require: string
97 The required permission level (see the perm.yaml configuration file under
98 the key `roles`).
99 perm: dict
100 User attributes, in particular `group` which is the role a user can play on the
101 basis of his/her identity.
102 But it also contains attributes that links a user to ceertain records, e.g. the
103 records of which (s)he is creator/editor, or National Coordinator.
105 Returns
106 -------
107 boolean
108 """
110 if require == UNAUTH:
111 return True
113 group = G(perm, N.group)
115 if require == NOBODY:
116 return False
118 if require == AUTH:
119 return group != UNAUTH
121 isSuper = group in {OFFICE, SYSTEM, ROOT}
123 if require == OFFICE:
124 return isSuper
126 if require == SYSTEM: 126 ↛ 127line 126 didn't jump to line 127, because the condition on line 126 was never true
127 return group in {SYSTEM, ROOT}
129 if require == ROOT: 129 ↛ 130line 129 didn't jump to line 130, because the condition on line 129 was never true
130 return group == ROOT
132 if require == EDIT:
133 return group != UNAUTH and (G(perm, N.isEdit) or isSuper)
135 if require == OWN:
136 return group != UNAUTH and (G(perm, N.isOwn) or isSuper)
138 if require == COORD: 138 ↛ exitline 138 didn't return from function 'checkPerm', because the condition on line 138 was never false
139 return group == COORD and G(perm, N.sameCountry) or isSuper
142def getPermField(table, permRecord, require, actual=None, minimum=None):
143 """Determine read/edit permissions for a field in a record in a table.
145 !!! hint
146 When it comes to checking a field permission, first get the
147 record permission, then the field requirements.
148 Then apply this function to get the result.
150 Parameters
151 ----------
152 table: string
153 Where the record resides
154 permRecord:
155 Permissions based on the record as a whole.
156 require:
157 Permissions required for reading/editing a particular field,
158 coming from the table specific .yaml files with field specifications.
159 actual, minimum: string, optional None
160 If the permission is dependent on the actual value of this field,
161 pass the actual value and the minimum value.
162 This applies to the edit permission.
163 The typical use case is when the field type is permissionGroup,
164 and an edit is only allowed if the permissionGroup of the user
165 has a greater or equal rank than/as the current value of the field.
166 We assume the ranks will be passed, not the values themselves.
168 Returns
169 -------
170 mayRead: boolean
171 mayEdit: boolean
172 """
174 mayRead = None
175 if table in ALLOW_OUR:
176 mayRead = G(permRecord, N.isOur) or None
177 if mayRead is None:
178 readRequire = (
179 G(DEFAULT_PERM, N.read)
180 if require is None or N.read not in require
181 else G(require, N.read)
182 )
183 mayRead = checkPerm(readRequire, permRecord)
185 editRequire = (
186 G(DEFAULT_PERM, N.edit)
187 if require is None or N.edit not in require
188 else G(require, N.edit)
189 )
190 mayEdit = checkPerm(editRequire, permRecord)
191 if mayEdit and actual is not None and minimum is not None:
192 if G(GROUP_RANK, actual, 0) > G(GROUP_RANK, minimum, 100): 192 ↛ 193line 192 didn't jump to line 193, because the condition on line 192 was never true
193 mayEdit = False
194 return (mayRead, mayEdit)
197def permRecord(context, table, record):
198 """Determine record permissions.
200 Various possible relationships between this user and the record will be examined.
202 Parameters
203 ----------
205 Returns
206 -------
207 group: string
208 Permission group of current user
209 country: ObjectId
210 Country to which the record belongs. Follow the detail-master chain from the
211 record to the contrib record and read the country from the contrib.
212 isOwn: boolean
213 Is the record created by the current user?
214 isEdit: boolean
215 Is the record created by the current user or is (s)he in the list of
216 editors of this record?
217 sameCountry: boolean
218 Does the record belong to the same country as the currennt user?
219 isCoordinated: boolean
220 Is this record under the jurisdiction of the current user as National
221 Coordinator?
222 isOur: boolean
223 Is this record in the workflow of the current user?
224 As creator/editor/reviewer/coordinator?
225 contribId: ObjectId
226 The id of the contrib to which this record is linked.
228 These attributes are returned as a `dict`.
229 """
230 auth = context.auth
231 user = auth.user
232 uid = G(user, N._id)
233 group = auth.groupRep()
234 uCountry = G(user, N.country)
236 cRecord = {}
237 aRecord = {}
239 if table == N.contrib:
240 cRecord = record
241 elif table == N.assessment:
242 aRecord = record
243 contribId = G(record, N.contrib)
244 cRecord = context.getItem(N.contrib, contribId)
245 elif table in {N.review, N.criteriaEntry, N.reviewEntry}:
246 assessmentId = G(record, N.assessment)
247 aRecord = context.getItem(N.assessment, assessmentId)
248 contribId = G(aRecord, N.contrib)
249 cRecord = context.getItem(N.contrib, contribId)
251 refCountry = G(cRecord, N.country)
252 reviewerE = G(aRecord, N.reviewerE)
253 reviewerF = G(aRecord, N.reviewerF)
254 reviewers = {reviewerE, reviewerF} - {None}
256 sameCountry = refCountry is not None and refCountry == uCountry
257 isAuth = group != UNAUTH and uid is not None
258 isCreator = uid is not None and uid == G(record, N.creator)
259 isEditor = uid is not None and uid in (G(record, N.editors) or set())
260 isCoordinated = isAuth and sameCountry and group == COORD
261 isACreator = uid is not None and uid == G(aRecord, N.creator)
262 isAEditor = uid is not None and uid in (G(aRecord, N.editors) or set())
263 isReviewer = uid is not None and uid in reviewers
265 isOur = table in ALLOW_OUR and (
266 isCoordinated
267 or isCreator
268 or isEditor
269 or isACreator
270 or isAEditor
271 or isReviewer
272 )
274 return {
275 N.group: group,
276 N.country: refCountry,
277 N.isOwn: isAuth and isCreator,
278 N.isEdit: isAuth and (isCreator or isEditor),
279 N.sameCountry: sameCountry,
280 N.isCoordinated: isCoordinated,
281 N.isOur: isOur,
282 N.isReviewer: isReviewer,
283 N.contribId: G(cRecord, N._id),
284 }