synapse 2.198.0__py311-none-any.whl → 2.200.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

synapse/lib/hiveauth.py DELETED
@@ -1,1336 +0,0 @@
1
- # pragma: no cover
2
-
3
- ###
4
- ### THIS WHOLE MODULE IS DEPRECATED AND EXPECTED TO BE REMOVED O/A v2.198.0
5
- ###
6
-
7
- import logging
8
- import dataclasses
9
-
10
- from typing import Optional, Union
11
-
12
- import synapse.exc as s_exc
13
- import synapse.common as s_common
14
-
15
- import synapse.lib.base as s_base
16
- import synapse.lib.cache as s_cache
17
- import synapse.lib.nexus as s_nexus
18
- import synapse.lib.config as s_config
19
-
20
- import synapse.lib.crypto.passwd as s_passwd
21
-
22
- logger = logging.getLogger(__name__)
23
-
24
- reqValidRules = s_config.getJsValidator({
25
- 'type': 'array',
26
- 'items': {
27
- 'type': 'array',
28
- 'items': [
29
- {'type': 'boolean'},
30
- {'type': 'array', 'items': {'type': 'string'}},
31
- ],
32
- 'minItems': 2,
33
- 'maxItems': 2,
34
- }
35
- })
36
-
37
- def getShadow(passwd): # pragma: no cover
38
- '''This API is deprecated.'''
39
- s_common.deprecated('hiveauth.getShadow()', curv='2.110.0', eolv='2.199.0')
40
- salt = s_common.guid()
41
- hashed = s_common.guid((salt, passwd))
42
- return (salt, hashed)
43
-
44
- def textFromRule(rule):
45
- s_common.deprecated('hiveauth.textFromRule()', curv='2.198.0', eolv='2.199.0') # pragma: no cover
46
- text = '.'.join(rule[1])
47
- if not rule[0]:
48
- text = '!' + text
49
- return text
50
-
51
- @dataclasses.dataclass(slots=True)
52
- class _allowedReason:
53
- s_common.deprecated('hiveauth._allowedReason()', curv='2.198.0', eolv='2.199.0')
54
- value: Union[bool | None]
55
- default: bool = False
56
- isadmin: bool = False
57
- islocked: bool = False
58
- gateiden: Union[str | None] = None
59
- roleiden: Union[str | None] = None
60
- rolename: Union[str | None] = None
61
- rule: tuple = ()
62
-
63
- @property
64
- def mesg(self):
65
- if self.islocked:
66
- return 'The user is locked.'
67
- if self.default:
68
- return 'No matching rule found.'
69
-
70
- if self.isadmin:
71
- if self.gateiden:
72
- return f'The user is an admin of auth gate {self.gateiden}.'
73
- return 'The user is a global admin.'
74
-
75
- if self.rule:
76
- rt = textFromRule((self.value, self.rule))
77
- if self.gateiden:
78
- if self.roleiden:
79
- m = f'Matched role rule ({rt}) for role {self.rolename} on gate {self.gateiden}.'
80
- else:
81
- m = f'Matched user rule ({rt}) on gate {self.gateiden}.'
82
- else:
83
- if self.roleiden:
84
- m = f'Matched role rule ({rt}) for role {self.rolename}.'
85
- else:
86
- m = f'Matched user rule ({rt}).'
87
- return m
88
-
89
- return 'No matching rule found.'
90
-
91
- class Auth(s_nexus.Pusher):
92
- '''
93
- Auth is a user authentication and authorization stored in a Hive. Users
94
- correspond to separate logins with different passwords and potentially
95
- different privileges.
96
-
97
- Users are assigned "rules". These rules are evaluated in order until a rule
98
- matches. Each rule is a tuple of boolean, and a rule path (a sequence of
99
- strings). Rules that are prefixes of a privilege match, i.e. a rule
100
- ('foo',) will match ('foo', 'bar').
101
-
102
- Roles are just collections of rules. When a user is "granted" a role those
103
- rules are assigned to that user. Unlike in an RBAC system, users don't
104
- explicitly assume a role; they are merely a convenience mechanism to easily
105
- assign the same rules to multiple users.
106
-
107
- Authgates are objects that manage their own authorization. Each
108
- AuthGate has roles and users subkeys which contain rules specific to that
109
- user or role for that AuthGate. The roles and users of an AuthGate,
110
- called GateRole and GateUser respectively, contain the iden of a role or
111
- user defined prior and rules specific to that role or user; they do not
112
- duplicate the metadata of the role or user.
113
-
114
- Node layout::
115
-
116
- Auth root (passed into constructor)
117
- ├ roles
118
- │ ├ <role iden 1>
119
- │ ├ ...
120
- │ └ last role
121
- ├ users
122
- │ ├ <user iden 1>
123
- │ ├ ...
124
- │ └ last user
125
- └ authgates
126
- ├ <iden 1>
127
- │ ├ roles
128
- │ │ ├ <role iden 1>
129
- │ │ ├ ...
130
- │ │ └ last role
131
- │ └ users
132
- │ ├ <user iden 1>
133
- │ ├ ...
134
- │ └ last user
135
- ├ <iden 2>
136
- │ ├ ...
137
- └ ... last authgate
138
-
139
- '''
140
-
141
- async def __anit__(self, node, nexsroot=None, seed=None, maxusers=0):
142
- '''
143
- Args:
144
- node (HiveNode): The root of the persistent storage for auth
145
- '''
146
- s_common.deprecated('Auth.__anit__()', curv='2.198.0', eolv='2.199.0')
147
- # Derive an iden from the parent
148
- iden = 'auth:' + ':'.join(node.full)
149
- await s_nexus.Pusher.__anit__(self, iden, nexsroot=nexsroot)
150
-
151
- self.node = node
152
-
153
- if seed is None:
154
- seed = s_common.guid()
155
-
156
- self.maxusers = maxusers
157
-
158
- self.usersbyiden = {}
159
- self.rolesbyiden = {}
160
- self.usersbyname = {}
161
- self.rolesbyname = {}
162
- self.authgates = {}
163
-
164
- self.allrole = None
165
- self.rootuser = None
166
-
167
- roles = await self.node.open(('roles',))
168
- for _, node in roles:
169
- await self._addRoleNode(node)
170
-
171
- users = await self.node.open(('users',))
172
- for _, node in users:
173
- await self._addUserNode(node)
174
-
175
- authgates = await self.node.open(('authgates',))
176
- for _, node in authgates:
177
- try:
178
- await self._addAuthGate(node)
179
- except Exception: # pragma: no cover
180
- logger.exception('Failure loading AuthGate')
181
-
182
- self.allrole = await self.getRoleByName('all')
183
- if self.allrole is None:
184
- # initialize the role of which all users are a member
185
- guid = s_common.guid((seed, 'auth', 'role', 'all'))
186
- await self._addRole(guid, 'all')
187
- self.allrole = self.role(guid)
188
-
189
- # initialize an admin user named root
190
- self.rootuser = await self.getUserByName('root')
191
- if self.rootuser is None:
192
- guid = s_common.guid((seed, 'auth', 'user', 'root'))
193
- await self._addUser(guid, 'root')
194
- self.rootuser = self.user(guid)
195
-
196
- await self.rootuser.setAdmin(True, logged=False)
197
- await self.rootuser.setLocked(False, logged=False)
198
-
199
- async def fini():
200
- await self.allrole.fini()
201
- await self.rootuser.fini()
202
- [await u.fini() for u in self.users()]
203
- [await r.fini() for r in self.roles()]
204
- [await a.fini() for a in self.authgates.values()]
205
-
206
- self.onfini(fini)
207
-
208
- def users(self):
209
- return self.usersbyiden.values()
210
-
211
- def roles(self):
212
- return self.rolesbyiden.values()
213
-
214
- def role(self, iden):
215
- return self.rolesbyiden.get(iden)
216
-
217
- def user(self, iden):
218
- return self.usersbyiden.get(iden)
219
-
220
- async def reqUser(self, iden):
221
-
222
- user = self.user(iden)
223
- if user is None:
224
- mesg = f'No user with iden {iden}.'
225
- raise s_exc.NoSuchUser(mesg=mesg, user=iden)
226
- return user
227
-
228
- async def reqRole(self, iden):
229
-
230
- role = self.role(iden)
231
- if role is None:
232
- mesg = f'No role with iden {iden}.'
233
- raise s_exc.NoSuchRole(mesg=mesg)
234
- return role
235
-
236
- async def reqUserByName(self, name):
237
- user = await self.getUserByName(name)
238
- if user is None:
239
- mesg = f'No user named {name}.'
240
- raise s_exc.NoSuchUser(mesg=mesg, username=name)
241
- return user
242
-
243
- async def reqUserByNameOrIden(self, name):
244
- user = await self.getUserByName(name)
245
- if user is not None:
246
- return user
247
-
248
- user = self.user(name)
249
- if user is None:
250
- mesg = f'No user with name or iden {name}.'
251
- raise s_exc.NoSuchUser(mesg=mesg)
252
- return user
253
-
254
- async def reqRoleByName(self, name):
255
- role = await self.getRoleByName(name)
256
- if role is None:
257
- mesg = f'No role named {name}.'
258
- raise s_exc.NoSuchRole(mesg=mesg)
259
- return role
260
-
261
- async def getUserByName(self, name):
262
- '''
263
- Get a user by their username.
264
-
265
- Args:
266
- name (str): Name of the user to get.
267
-
268
- Returns:
269
- HiveUser: A Hive User. May return None if there is no user by the requested name.
270
- '''
271
- return self.usersbyname.get(name)
272
-
273
- async def getUserIdenByName(self, name):
274
- user = await self.getUserByName(name)
275
- return None if user is None else user.iden
276
-
277
- async def getRoleByName(self, name):
278
- return self.rolesbyname.get(name)
279
-
280
- async def _addUserNode(self, node):
281
-
282
- user = await HiveUser.anit(node, self)
283
-
284
- self.usersbyiden[user.iden] = user
285
- self.usersbyname[user.name] = user
286
-
287
- return user
288
-
289
- async def setUserName(self, iden, name):
290
- if not isinstance(name, str):
291
- raise s_exc.BadArg(mesg='setUserName() name must be a string')
292
-
293
- user = self.usersbyname.get(name)
294
- if user is not None:
295
- if user.iden == iden:
296
- return
297
- raise s_exc.DupUserName(name=name)
298
-
299
- user = await self.reqUser(iden)
300
-
301
- self.usersbyname.pop(user.name, None)
302
- self.usersbyname[name] = user
303
-
304
- user.name = name
305
- await user.node.set(name)
306
-
307
- beheld = {
308
- 'iden': iden,
309
- 'valu': name,
310
- }
311
- await self.feedBeholder('user:name', beheld)
312
-
313
- async def setRoleName(self, iden, name):
314
- if not isinstance(name, str):
315
- raise s_exc.BadArg(mesg='setRoleName() name must be a string')
316
-
317
- role = self.rolesbyname.get(name)
318
- if role is not None:
319
- if role.iden == iden:
320
- return
321
- raise s_exc.DupRoleName(name=name)
322
-
323
- role = await self.reqRole(iden)
324
-
325
- if role.name == 'all':
326
- mesg = 'Role "all" may not be renamed.'
327
- raise s_exc.BadArg(mesg=mesg)
328
-
329
- self.rolesbyname.pop(role.name, None)
330
- self.rolesbyname[name] = role
331
-
332
- role.name = name
333
- await role.node.set(name)
334
-
335
- beheld = {
336
- 'iden': iden,
337
- 'valu': name,
338
- }
339
- await self.feedBeholder('role:name', beheld)
340
-
341
- async def feedBeholder(self, evnt, info, gateiden=None, logged=True):
342
- if self.nexsroot and self.nexsroot.started and logged:
343
- behold = {
344
- 'event': evnt,
345
- 'offset': await self.nexsroot.index(),
346
- 'info': info
347
- }
348
-
349
- if gateiden:
350
- gate = self.getAuthGate(gateiden)
351
- if gate:
352
- behold['gates'] = [gate.pack()]
353
-
354
- await self.fire('cell:beholder', **behold)
355
-
356
- async def setUserInfo(self, iden, name, valu, gateiden=None, logged=True, mesg=None):
357
-
358
- user = await self.reqUser(iden)
359
-
360
- info = user.info
361
- if gateiden is not None:
362
- info = await user.genGateInfo(gateiden)
363
-
364
- if name in ('locked', 'archived') and not valu:
365
- self.checkUserLimit()
366
-
367
- await info.set(name, valu)
368
-
369
- if mesg is None:
370
- mesg = {
371
- 'iden': iden,
372
- 'name': name,
373
- }
374
- if name != 'passwd':
375
- mesg['valu'] = valu
376
-
377
- await self.feedBeholder('user:info', mesg, gateiden=gateiden, logged=logged)
378
-
379
- # since any user info *may* effect auth
380
- user.clearAuthCache()
381
-
382
- async def setRoleInfo(self, iden, name, valu, gateiden=None, logged=True, mesg=None):
383
- role = await self.reqRole(iden)
384
-
385
- info = role.info
386
- if gateiden is not None:
387
- info = await role.genGateInfo(gateiden)
388
-
389
- await info.set(name, valu)
390
-
391
- if mesg is None:
392
- mesg = {
393
- 'iden': iden,
394
- 'name': name,
395
- 'valu': valu,
396
- }
397
- await self.feedBeholder('role:info', mesg, gateiden=gateiden, logged=logged)
398
-
399
- role.clearAuthCache()
400
-
401
- async def _addRoleNode(self, node):
402
-
403
- role = await HiveRole.anit(node, self)
404
-
405
- self.rolesbyiden[role.iden] = role
406
- self.rolesbyname[role.name] = role
407
-
408
- return role
409
-
410
- async def _addAuthGate(self, node):
411
- gate = await AuthGate.anit(node, self)
412
- self.authgates[gate.iden] = gate
413
- return gate
414
-
415
- async def addAuthGate(self, iden, authgatetype):
416
- '''
417
- Retrieve AuthGate by iden. Create if not present.
418
-
419
- Note:
420
- Not change distributed
421
-
422
- Returns:
423
- (HiveAuthGate)
424
- '''
425
- gate = self.getAuthGate(iden)
426
- if gate is not None:
427
- if gate.type != authgatetype:
428
- raise s_exc.InconsistentStorage(mesg=f'Stored AuthGate is of type {gate.type}, not {authgatetype}')
429
- return gate
430
-
431
- path = self.node.full + ('authgates', iden)
432
- node = await self.node.hive.open(path)
433
- await self.node.hive.set(path, authgatetype)
434
- return await self._addAuthGate(node)
435
-
436
- async def delAuthGate(self, iden):
437
- '''
438
- Delete AuthGate by iden.
439
-
440
- Note:
441
- Not change distributed
442
- '''
443
- gate = self.getAuthGate(iden)
444
- if gate is None:
445
- raise s_exc.NoSuchAuthGate(iden=iden)
446
-
447
- await gate.fini()
448
- await gate.delete()
449
- await gate.node.pop()
450
-
451
- del self.authgates[iden]
452
-
453
- def getAuthGate(self, iden):
454
- return self.authgates.get(iden)
455
-
456
- def getAuthGates(self):
457
- return list(self.authgates.values())
458
-
459
- def reqAuthGate(self, iden):
460
- gate = self.authgates.get(iden)
461
- if gate is None:
462
- mesg = f'No auth gate found with iden: ({iden}).'
463
- raise s_exc.NoSuchAuthGate(iden=iden, mesg=mesg)
464
- return gate
465
-
466
- def checkUserLimit(self):
467
- '''
468
- Check if we're at the specified user limit.
469
-
470
- This should be called right before adding/unlocking/unarchiving a user.
471
-
472
- Raises: s_exc.HitLimit if the number of active users is at the maximum.
473
- '''
474
- if self.maxusers == 0:
475
- return
476
-
477
- numusers = 0
478
-
479
- for user in self.users():
480
- if user.name == 'root':
481
- continue
482
-
483
- if user.isLocked() or user.isArchived():
484
- continue
485
-
486
- numusers += 1
487
-
488
- if numusers >= self.maxusers:
489
- mesg = f'Cell at maximum number of users ({self.maxusers}).'
490
- raise s_exc.HitLimit(mesg=mesg)
491
-
492
- async def addUser(self, name, passwd=None, email=None, iden=None):
493
- '''
494
- Add a User to the Hive.
495
-
496
- Args:
497
- name (str): The name of the User.
498
- passwd (str): A optional password for the user.
499
- email (str): A optional email for the user.
500
- iden (str): A optional iden to use as the user iden.
501
-
502
- Returns:
503
- HiveUser: A Hive User.
504
- '''
505
-
506
- self.checkUserLimit()
507
-
508
- if self.usersbyname.get(name) is not None:
509
- raise s_exc.DupUserName(name=name)
510
-
511
- if iden is None:
512
- iden = s_common.guid()
513
- else:
514
- if not s_common.isguid(iden):
515
- raise s_exc.BadArg(name='iden', arg=iden, mesg='Argument it not a valid iden.')
516
-
517
- if self.usersbyiden.get(iden) is not None:
518
- raise s_exc.DupIden(name=name, iden=iden,
519
- mesg='User already exists for the iden.')
520
-
521
- await self._push('user:add', iden, name)
522
-
523
- user = self.user(iden)
524
-
525
- if passwd is not None:
526
- await user.setPasswd(passwd)
527
-
528
- if email is not None:
529
- await self.setUserInfo(user.iden, 'email', email)
530
-
531
- # Everyone's a member of 'all'
532
- await user.grant(self.allrole.iden)
533
-
534
- return user
535
-
536
- async def _addUser(self, iden, name):
537
-
538
- user = self.usersbyname.get(name)
539
- if user is not None:
540
- return
541
-
542
- node = await self.node.open(('users', iden))
543
- await node.set(name)
544
-
545
- await self._addUserNode(node)
546
-
547
- user = self.usersbyname.get(name)
548
- await self.feedBeholder('user:add', user.pack())
549
-
550
- async def addRole(self, name, iden=None):
551
- if self.rolesbyname.get(name) is not None:
552
- raise s_exc.DupRoleName(name=name)
553
-
554
- if iden is None:
555
- iden = s_common.guid()
556
-
557
- await self._push('role:add', iden, name)
558
-
559
- return self.role(iden)
560
-
561
- async def _addRole(self, iden, name):
562
-
563
- role = self.rolesbyname.get(name)
564
- if role is not None:
565
- return
566
-
567
- node = await self.node.open(('roles', iden))
568
- await node.set(name)
569
-
570
- await self._addRoleNode(node)
571
-
572
- role = self.rolesbyname.get(name)
573
- await self.feedBeholder('role:add', role.pack())
574
-
575
- async def delUser(self, iden):
576
-
577
- await self.reqUser(iden)
578
- return await self._push('user:del', iden)
579
-
580
- async def _delUser(self, iden):
581
-
582
- if iden == self.rootuser.iden:
583
- mesg = 'User "root" may not be deleted.'
584
- raise s_exc.BadArg(mesg=mesg)
585
-
586
- user = self.user(iden)
587
- if user is None:
588
- return
589
-
590
- udef = user.pack()
591
- self.usersbyiden.pop(user.iden)
592
- self.usersbyname.pop(user.name)
593
-
594
- path = self.node.full + ('users', user.iden)
595
-
596
- for gate in self.authgates.values():
597
- await gate._delGateUser(user.iden)
598
-
599
- await user.fini()
600
- await self.node.hive.pop(path)
601
- await self.fire('user:del', udef=udef)
602
- await self.feedBeholder('user:del', {'iden': iden})
603
-
604
- def _getUsersInRole(self, role):
605
- for user in self.users():
606
- if role.iden in user.info.get('roles', ()):
607
- yield user
608
-
609
- async def delRole(self, iden):
610
- await self.reqRole(iden)
611
- return await self._push('role:del', iden)
612
-
613
- async def _delRole(self, iden):
614
-
615
- if iden == self.allrole.iden:
616
- mesg = 'Role "all" may not be deleted.'
617
- raise s_exc.BadArg(mesg=mesg)
618
-
619
- role = self.role(iden)
620
- if role is None:
621
- return
622
-
623
- for user in self._getUsersInRole(role):
624
- await user.revoke(role.iden, nexs=False)
625
-
626
- for gate in self.authgates.values():
627
- await gate._delGateRole(role.iden)
628
-
629
- self.rolesbyiden.pop(role.iden)
630
- self.rolesbyname.pop(role.name)
631
-
632
- await role.fini()
633
-
634
- # directly set the node's value and let events prop
635
- path = self.node.full + ('roles', role.iden)
636
- await self.node.hive.pop(path)
637
- await self.feedBeholder('role:del', {'iden': iden})
638
-
639
- class AuthGate(s_base.Base):
640
- '''
641
- The storage object for object specific rules for users/roles.
642
- '''
643
- async def __anit__(self, node, auth):
644
- await s_base.Base.__anit__(self)
645
- self.auth = auth
646
-
647
- self.iden = node.name()
648
- self.type = node.valu
649
-
650
- self.node = node
651
-
652
- self.gateroles = {} # iden -> HiveRole
653
- self.gateusers = {} # iden -> HiveUser
654
-
655
- for useriden, usernode in await node.open(('users',)):
656
-
657
- user = self.auth.user(useriden)
658
- if user is None: # pragma: no cover
659
- logger.warning(f'Hive: path {useriden} refers to unknown user')
660
- continue
661
-
662
- userinfo = await usernode.dict()
663
- self.gateusers[user.iden] = user
664
- user.authgates[self.iden] = userinfo
665
- user.clearAuthCache()
666
-
667
- for roleiden, rolenode in await node.open(('roles',)):
668
-
669
- role = self.auth.role(roleiden)
670
- if role is None: # pragma: no cover
671
- logger.warning(f'Hive: path {roleiden} refers to unknown role')
672
- continue
673
-
674
- roleinfo = await rolenode.dict()
675
- self.gateroles[role.iden] = role
676
- role.authgates[self.iden] = roleinfo
677
-
678
- async def genUserInfo(self, iden):
679
- node = await self.node.open(('users', iden))
680
- userinfo = await node.dict()
681
-
682
- user = self.auth.user(iden)
683
- self.gateusers[iden] = user
684
- user.authgates[self.iden] = userinfo
685
-
686
- return userinfo
687
-
688
- async def genRoleInfo(self, iden):
689
- node = await self.node.open(('roles', iden))
690
- roleinfo = await node.dict()
691
-
692
- role = self.auth.role(iden)
693
- self.gateroles[iden] = role
694
- role.authgates[self.iden] = roleinfo
695
-
696
- return roleinfo
697
-
698
- async def _delGateUser(self, iden):
699
- self.gateusers.pop(iden, None)
700
- await self.node.pop(('users', iden))
701
-
702
- async def _delGateRole(self, iden):
703
- self.gateroles.pop(iden, None)
704
- await self.node.pop(('roles', iden))
705
-
706
- async def delete(self):
707
-
708
- await self.fini()
709
-
710
- for _, user in list(self.gateusers.items()):
711
- user.authgates.pop(self.iden, None)
712
- user.clearAuthCache()
713
-
714
- for _, role in list(self.gateroles.items()):
715
- role.authgates.pop(self.iden, None)
716
- role.clearAuthCache()
717
-
718
- await self.node.pop()
719
-
720
- def pack(self):
721
-
722
- users = []
723
- for user in self.gateusers.values():
724
-
725
- gateinfo = user.authgates.get(self.iden)
726
- if gateinfo is None:
727
- continue
728
-
729
- users.append({
730
- 'iden': user.iden,
731
- 'rules': gateinfo.get('rules', ()),
732
- 'admin': gateinfo.get('admin', False),
733
- })
734
-
735
- roles = []
736
- for role in self.gateroles.values():
737
-
738
- gateinfo = role.authgates.get(self.iden)
739
- if gateinfo is None:
740
- continue
741
-
742
- roles.append({
743
- 'iden': role.iden,
744
- 'rules': gateinfo.get('rules', ()),
745
- 'admin': gateinfo.get('admin', False),
746
- })
747
-
748
- return {
749
- 'iden': self.iden,
750
- 'type': self.type,
751
- 'users': users,
752
- 'roles': roles,
753
- }
754
-
755
- class HiveRuler(s_base.Base):
756
- '''
757
- A HiveNode that holds a list of rules. This includes HiveUsers, HiveRoles, and the AuthGate variants of those
758
- '''
759
-
760
- async def __anit__(self, node, auth):
761
- await s_base.Base.__anit__(self)
762
-
763
- self.iden = node.name()
764
-
765
- self.auth = auth
766
- self.node = node
767
- self.name = node.valu
768
- self.info = await node.dict()
769
-
770
- self.info.setdefault('admin', False)
771
- self.info.setdefault('rules', ())
772
-
773
- self.authgates = {}
774
-
775
- async def _setRulrInfo(self, name, valu, gateiden=None, nexs=True, mesg=None): # pragma: no cover
776
- raise s_exc.NoSuchImpl(mesg='Subclass must implement _setRulrInfo')
777
-
778
- def getRules(self, gateiden=None):
779
-
780
- if gateiden is None:
781
- return list(self.info.get('rules', ()))
782
-
783
- gateinfo = self.authgates.get(gateiden)
784
- if gateinfo is None:
785
- return []
786
-
787
- return list(gateinfo.get('rules', ()))
788
-
789
- async def setRules(self, rules, gateiden=None, nexs=True, mesg=None):
790
- reqValidRules(rules)
791
- return await self._setRulrInfo('rules', rules, gateiden=gateiden, nexs=nexs, mesg=mesg)
792
-
793
- async def addRule(self, rule, indx=None, gateiden=None, nexs=True):
794
- reqValidRules((rule,))
795
- rules = self.getRules(gateiden=gateiden)
796
-
797
- mesg = {
798
- 'name': 'rule:add',
799
- 'iden': self.iden,
800
- 'valu': rule,
801
- }
802
- if indx is None:
803
- rules.append(rule)
804
- else:
805
- rules.insert(indx, rule)
806
- mesg['indx'] = indx
807
-
808
- await self.setRules(rules, gateiden=gateiden, nexs=nexs, mesg=mesg)
809
-
810
- async def delRule(self, rule, gateiden=None):
811
- reqValidRules((rule,))
812
- rules = self.getRules(gateiden=gateiden)
813
- if rule not in rules:
814
- return False
815
-
816
- mesg = {
817
- 'name': 'rule:del',
818
- 'iden': self.iden,
819
- 'valu': rule,
820
- }
821
- rules.remove(rule)
822
- await self.setRules(rules, gateiden=gateiden, mesg=mesg)
823
- return True
824
-
825
- class HiveRole(HiveRuler):
826
- '''
827
- A role within the Hive authorization subsystem.
828
-
829
- A role in HiveAuth exists to bundle rules together so that the same
830
- set of rules can be applied to multiple users.
831
- '''
832
- def pack(self):
833
- return {
834
- 'type': 'role',
835
- 'iden': self.iden,
836
- 'name': self.name,
837
- 'rules': self.info.get('rules'),
838
- 'authgates': {name: info.pack() for (name, info) in self.authgates.items()},
839
- }
840
-
841
- async def _setRulrInfo(self, name, valu, gateiden=None, nexs=True, mesg=None):
842
- return await self.auth.setRoleInfo(self.iden, name, valu, gateiden=gateiden, logged=nexs, mesg=mesg)
843
-
844
- async def setName(self, name):
845
- return await self.auth.setRoleName(self.iden, name)
846
-
847
- def clearAuthCache(self):
848
- for user in self.auth.users():
849
- if user.hasRole(self.iden):
850
- user.clearAuthCache()
851
-
852
- async def genGateInfo(self, gateiden):
853
- info = self.authgates.get(gateiden)
854
- if info is None:
855
- gate = self.auth.reqAuthGate(gateiden)
856
- info = self.authgates[gateiden] = await gate.genRoleInfo(self.iden)
857
- return info
858
-
859
- def allowed(self, perm, default=None, gateiden=None):
860
-
861
- perm = tuple(perm)
862
- if gateiden is not None:
863
- info = self.authgates.get(gateiden)
864
- if info is not None:
865
- for allow, path in info.get('rules', ()):
866
- if perm[:len(path)] == path:
867
- return allow
868
- return default
869
-
870
- # 2. check role rules
871
- for allow, path in self.info.get('rules', ()):
872
- if perm[:len(path)] == path:
873
- return allow
874
-
875
- return default
876
-
877
- class HiveUser(HiveRuler):
878
- '''
879
- A user (could be human or computer) of the system within HiveAuth.
880
-
881
- Cortex-wide rules are stored here. AuthGate-specific rules for this user are stored in an GateUser.
882
- '''
883
- async def __anit__(self, node, auth):
884
- await HiveRuler.__anit__(self, node, auth)
885
-
886
- self.info.setdefault('roles', ())
887
- self.info.setdefault('admin', False)
888
- self.info.setdefault('passwd', None)
889
- self.info.setdefault('locked', False)
890
- self.info.setdefault('archived', False)
891
-
892
- # arbitrary profile data for application layer use
893
- prof = await self.node.open(('profile',))
894
- self.profile = await prof.dict(nexs=True)
895
-
896
- # TODO: max size check / max count check?
897
- varz = await self.node.open(('vars',))
898
- self.vars = await varz.dict(nexs=True)
899
-
900
- self.permcache = s_cache.FixedCache(self._allowed)
901
- self.allowedcache = s_cache.FixedCache(self._getAllowedReason)
902
-
903
- def pack(self, packroles=False):
904
-
905
- roles = self.info.get('roles', ())
906
- if packroles:
907
- _roles = []
908
- for r in roles:
909
- role = self.auth.role(r)
910
- if role is None:
911
- logger.error(f'User {self.iden} ({self.name}) contains a missing role: {r}')
912
- continue
913
- _roles.append(role.pack())
914
- roles = _roles
915
-
916
- return {
917
- 'type': 'user',
918
- 'iden': self.iden,
919
- 'name': self.name,
920
- 'rules': self.info.get('rules', ()),
921
- 'roles': roles,
922
- 'admin': self.info.get('admin', ()),
923
- 'email': self.info.get('email'),
924
- 'locked': self.info.get('locked'),
925
- 'archived': self.info.get('archived'),
926
- 'authgates': {name: info.pack() for (name, info) in self.authgates.items()},
927
- }
928
-
929
- async def _setRulrInfo(self, name, valu, gateiden=None, nexs=True, mesg=None):
930
- return await self.auth.setUserInfo(self.iden, name, valu, gateiden=gateiden, logged=nexs, mesg=mesg)
931
-
932
- async def setName(self, name):
933
- return await self.auth.setUserName(self.iden, name)
934
-
935
- async def allow(self, perm):
936
- if not self.allowed(perm):
937
- await self.addRule((True, perm), indx=0)
938
-
939
- def allowed(self,
940
- perm: tuple[str, ...],
941
- default: Optional[str] = None,
942
- gateiden: Optional[str] = None,
943
- deepdeny: bool = False) -> Union[bool, None]:
944
- '''
945
- Check if a user is allowed a given permission.
946
-
947
- Args:
948
- perm: The permission tuple to check.
949
- default: The default rule value if there is no match.
950
- gateiden: The gate iden to check against.
951
- deepdeny: If True, give precedence for checking deny rules which are more specific than the requested
952
- permission.
953
-
954
- Notes:
955
- The use of the deepdeny argument is intended for checking a less-specific part of a permissions tree, in
956
- order to know about possible short circuit options. Using it to check a more specific part may have
957
- unintended results.
958
-
959
- Returns:
960
- The allowed value of the permission.
961
- '''
962
- perm = tuple(perm)
963
- return self.permcache.get((perm, default, gateiden, deepdeny))
964
-
965
- def _allowed(self, pkey):
966
- '''
967
- NOTE: This must remain in sync with any changes to _getAllowedReason()!
968
- '''
969
-
970
- perm, default, gateiden, deepdeny = pkey
971
-
972
- if self.info.get('locked'):
973
- return False
974
-
975
- if self.info.get('admin'):
976
- return True
977
-
978
- if deepdeny and self._hasDeepDeny(perm, gateiden):
979
- return False
980
-
981
- # 1. check authgate user rules
982
- if gateiden is not None:
983
-
984
- info = self.authgates.get(gateiden)
985
- if info is not None:
986
-
987
- if info.get('admin'):
988
- return True
989
-
990
- for allow, path in info.get('rules', ()):
991
- if perm[:len(path)] == path:
992
- return allow
993
-
994
- # 2. check user rules
995
- for allow, path in self.info.get('rules', ()):
996
- if perm[:len(path)] == path:
997
- return allow
998
-
999
- # 3. check authgate role rules
1000
- if gateiden is not None:
1001
-
1002
- for role in self.getRoles():
1003
-
1004
- info = role.authgates.get(gateiden)
1005
- if info is None:
1006
- continue
1007
-
1008
- for allow, path in info.get('rules', ()):
1009
- if perm[:len(path)] == path:
1010
- return allow
1011
-
1012
- # 4. check role rules
1013
- for role in self.getRoles():
1014
- for allow, path in role.info.get('rules', ()):
1015
- if perm[:len(path)] == path:
1016
- return allow
1017
-
1018
- return default
1019
-
1020
- def getAllowedReason(self, perm, default=None, gateiden=None):
1021
- '''
1022
- A routine which will return a tuple of (allowed, info).
1023
- '''
1024
- perm = tuple(perm)
1025
- return self.allowedcache.get((perm, default, gateiden))
1026
-
1027
- def _getAllowedReason(self, pkey):
1028
- '''
1029
- NOTE: This must remain in sync with any changes to _allowed()!
1030
- '''
1031
- perm, default, gateiden = pkey
1032
- if self.info.get('locked'):
1033
- return _allowedReason(False, islocked=True)
1034
-
1035
- if self.info.get('admin'):
1036
- return _allowedReason(True, isadmin=True)
1037
-
1038
- # 1. check authgate user rules
1039
- if gateiden is not None:
1040
-
1041
- info = self.authgates.get(gateiden)
1042
- if info is not None:
1043
-
1044
- if info.get('admin'):
1045
- return _allowedReason(True, isadmin=True, gateiden=gateiden)
1046
-
1047
- for allow, path in info.get('rules', ()):
1048
- if perm[:len(path)] == path:
1049
- return _allowedReason(allow, gateiden=gateiden, rule=path)
1050
-
1051
- # 2. check user rules
1052
- for allow, path in self.info.get('rules', ()):
1053
- if perm[:len(path)] == path:
1054
- return _allowedReason(allow, rule=path)
1055
-
1056
- # 3. check authgate role rules
1057
- if gateiden is not None:
1058
-
1059
- for role in self.getRoles():
1060
-
1061
- info = role.authgates.get(gateiden)
1062
- if info is None:
1063
- continue
1064
-
1065
- for allow, path in info.get('rules', ()):
1066
- if perm[:len(path)] == path:
1067
- return _allowedReason(allow, gateiden=gateiden, roleiden=role.iden, rolename=role.name,
1068
- rule=path)
1069
-
1070
- # 4. check role rules
1071
- for role in self.getRoles():
1072
- for allow, path in role.info.get('rules', ()):
1073
- if perm[:len(path)] == path:
1074
- return _allowedReason(allow, roleiden=role.iden, rolename=role.name, rule=path)
1075
-
1076
- return _allowedReason(default, default=True)
1077
-
1078
- def _hasDeepDeny(self, perm, gateiden):
1079
-
1080
- permlen = len(perm)
1081
-
1082
- # 1. check authgate user rules
1083
- if gateiden is not None:
1084
-
1085
- info = self.authgates.get(gateiden)
1086
- if info is not None:
1087
-
1088
- if info.get('admin'):
1089
- return False
1090
-
1091
- for allow, path in info.get('rules', ()):
1092
- if allow:
1093
- continue
1094
- if path[:permlen] == perm and len(path) > permlen:
1095
- return True
1096
-
1097
- # 2. check user rules
1098
- for allow, path in self.info.get('rules', ()):
1099
- if allow:
1100
- continue
1101
-
1102
- if path[:permlen] == perm and len(path) > permlen:
1103
- return True
1104
-
1105
- # 3. check authgate role rules
1106
- if gateiden is not None:
1107
-
1108
- for role in self.getRoles():
1109
-
1110
- info = role.authgates.get(gateiden)
1111
- if info is None:
1112
- continue
1113
-
1114
- for allow, path in info.get('rules', ()):
1115
- if allow:
1116
- continue
1117
- if path[:permlen] == perm and len(path) > permlen:
1118
- return True
1119
-
1120
- # 4. check role rules
1121
- for role in self.getRoles():
1122
- for allow, path in role.info.get('rules', ()):
1123
- if allow:
1124
- continue
1125
- if path[:permlen] == perm and len(path) > permlen:
1126
- return True
1127
-
1128
- return False
1129
-
1130
- def clearAuthCache(self):
1131
- self.permcache.clear()
1132
- self.allowedcache.clear()
1133
-
1134
- async def genGateInfo(self, gateiden):
1135
- info = self.authgates.get(gateiden)
1136
- if info is None:
1137
- gate = self.auth.reqAuthGate(gateiden)
1138
- info = await gate.genUserInfo(self.iden)
1139
- return info
1140
-
1141
- def confirm(self, perm, default=None, gateiden=None):
1142
- if not self.allowed(perm, default=default, gateiden=gateiden):
1143
- self.raisePermDeny(perm, gateiden=gateiden)
1144
-
1145
- def raisePermDeny(self, perm, gateiden=None):
1146
-
1147
- perm = '.'.join(perm)
1148
- if gateiden is None:
1149
- mesg = f'User {self.name!r} ({self.iden}) must have permission {perm}'
1150
- raise s_exc.AuthDeny(mesg=mesg, perm=perm, user=self.iden, username=self.name)
1151
-
1152
- gate = self.auth.reqAuthGate(gateiden)
1153
- mesg = f'User {self.name!r} ({self.iden}) must have permission {perm} on object {gate.iden} ({gate.type}).'
1154
- raise s_exc.AuthDeny(mesg=mesg, perm=perm, user=self.iden, username=self.name)
1155
-
1156
- def getRoles(self):
1157
- for iden in self.info.get('roles', ()):
1158
- role = self.auth.role(iden)
1159
- if role is None:
1160
- logger.warning(f'user {self.iden} has non-existent role: {iden}')
1161
- continue
1162
- yield role
1163
-
1164
- def hasRole(self, iden):
1165
- return iden in self.info.get('roles', ())
1166
-
1167
- async def grant(self, roleiden, indx=None):
1168
-
1169
- role = await self.auth.reqRole(roleiden)
1170
-
1171
- roles = list(self.info.get('roles'))
1172
- if role.iden in roles:
1173
- return
1174
-
1175
- if indx is None:
1176
- roles.append(role.iden)
1177
- else:
1178
- roles.insert(indx, role.iden)
1179
-
1180
- mesg = {'name': 'role:grant', 'iden': self.iden, 'role': role.pack()}
1181
- await self.auth.setUserInfo(self.iden, 'roles', roles, mesg=mesg)
1182
-
1183
- async def setRoles(self, roleidens):
1184
- '''
1185
- Replace all the roles for a given user with a new list of roles.
1186
-
1187
- Args:
1188
- roleidens (list): A list of roleidens.
1189
-
1190
- Notes:
1191
- The roleiden for the "all" role must be present in the new list of roles. This replaces all existing roles
1192
- that the user has with the new roles.
1193
-
1194
- Returns:
1195
- None
1196
- '''
1197
- current_roles = list(self.info.get('roles'))
1198
-
1199
- roleidens = list(roleidens)
1200
-
1201
- if current_roles == roleidens:
1202
- return
1203
-
1204
- if self.auth.allrole.iden not in roleidens:
1205
- mesg = 'Role "all" must be in the list of roles set.'
1206
- raise s_exc.BadArg(mesg=mesg)
1207
-
1208
- roles = []
1209
- for iden in roleidens:
1210
- r = await self.auth.reqRole(iden)
1211
- roles.append(r.pack())
1212
-
1213
- mesg = {'name': 'role:set', 'iden': self.iden, 'roles': roles}
1214
- await self.auth.setUserInfo(self.iden, 'roles', roleidens, mesg=mesg)
1215
-
1216
- async def revoke(self, iden, nexs=True):
1217
-
1218
- role = await self.auth.reqRole(iden)
1219
-
1220
- if role.name == 'all':
1221
- mesg = 'Role "all" may not be revoked.'
1222
- raise s_exc.BadArg(mesg=mesg)
1223
-
1224
- roles = list(self.info.get('roles'))
1225
- if role.iden not in roles:
1226
- return
1227
-
1228
- roles.remove(role.iden)
1229
- mesg = {'name': 'role:revoke', 'iden': self.iden, 'role': role.pack()}
1230
- await self.auth.setUserInfo(self.iden, 'roles', roles, logged=nexs, mesg=mesg)
1231
-
1232
- def isLocked(self):
1233
- return self.info.get('locked')
1234
-
1235
- def isAdmin(self, gateiden=None):
1236
-
1237
- # being a global admin always wins
1238
- admin = self.info.get('admin', False)
1239
- if admin or gateiden is None:
1240
- return admin
1241
-
1242
- gateinfo = self.authgates.get(gateiden)
1243
- if gateinfo is None:
1244
- return False
1245
-
1246
- return gateinfo.get('admin', False)
1247
-
1248
- def reqAdmin(self, gateiden=None, mesg=None):
1249
-
1250
- if self.isAdmin(gateiden=gateiden):
1251
- return
1252
-
1253
- if mesg is None:
1254
- mesg = 'This action requires global admin permissions.'
1255
- if gateiden is not None:
1256
- mesg = f'This action requires admin permissions on gate: {gateiden}'
1257
-
1258
- raise s_exc.AuthDeny(mesg=mesg, user=self.iden, username=self.name)
1259
-
1260
- def isArchived(self):
1261
- return self.info.get('archived')
1262
-
1263
- async def setAdmin(self, admin, gateiden=None, logged=True):
1264
- if not isinstance(admin, bool):
1265
- raise s_exc.BadArg(mesg='setAdmin requires a boolean')
1266
- await self.auth.setUserInfo(self.iden, 'admin', admin, gateiden=gateiden, logged=logged)
1267
-
1268
- async def setLocked(self, locked, logged=True):
1269
- if not isinstance(locked, bool):
1270
- raise s_exc.BadArg(mesg='setLocked requires a boolean')
1271
- await self.auth.setUserInfo(self.iden, 'locked', locked, logged=logged)
1272
-
1273
- async def setArchived(self, archived):
1274
- if not isinstance(archived, bool):
1275
- raise s_exc.BadArg(mesg='setArchived requires a boolean')
1276
- await self.auth.setUserInfo(self.iden, 'archived', archived)
1277
- if archived:
1278
- await self.setLocked(True)
1279
-
1280
- async def tryPasswd(self, passwd, nexs=True):
1281
-
1282
- if self.info.get('locked', False):
1283
- return False
1284
-
1285
- if passwd is None:
1286
- return False
1287
-
1288
- onepass = self.info.get('onepass')
1289
- if onepass is not None:
1290
- if isinstance(onepass, dict):
1291
- shadow = onepass.get('shadow')
1292
- expires = onepass.get('expires')
1293
- if expires >= s_common.now():
1294
- if await s_passwd.checkShadowV2(passwd=passwd, shadow=shadow):
1295
- await self.auth.setUserInfo(self.iden, 'onepass', None)
1296
- logger.debug(f'Used one time password for {self.name}',
1297
- extra={'synapse': {'user': self.iden, 'username': self.name}})
1298
- return True
1299
- else:
1300
- # Backwards compatible password handling
1301
- expires, params, hashed = onepass
1302
- if expires >= s_common.now():
1303
- if s_common.guid((params, passwd)) == hashed:
1304
- await self.auth.setUserInfo(self.iden, 'onepass', None)
1305
- logger.debug(f'Used one time password for {self.name}',
1306
- extra={'synapse': {'user': self.iden, 'username': self.name}})
1307
- return True
1308
-
1309
- shadow = self.info.get('passwd')
1310
- if shadow is None:
1311
- return False
1312
-
1313
- if isinstance(shadow, dict):
1314
- return await s_passwd.checkShadowV2(passwd=passwd, shadow=shadow)
1315
-
1316
- # Backwards compatible password handling
1317
- salt, hashed = shadow
1318
- if s_common.guid((salt, passwd)) == hashed:
1319
- logger.debug(f'Migrating password to shadowv2 format for user {self.name}',
1320
- extra={'synapse': {'user': self.iden, 'username': self.name}})
1321
- # Update user to new password hashing scheme.
1322
- await self.setPasswd(passwd=passwd, nexs=nexs)
1323
-
1324
- return True
1325
-
1326
- return False
1327
-
1328
- async def setPasswd(self, passwd, nexs=True):
1329
- # Prevent empty string or non-string values
1330
- if passwd is None:
1331
- shadow = None
1332
- elif passwd and isinstance(passwd, str):
1333
- shadow = await s_passwd.getShadowV2(passwd=passwd)
1334
- else:
1335
- raise s_exc.BadArg(mesg='Password must be a string')
1336
- await self.auth.setUserInfo(self.iden, 'passwd', shadow, logged=nexs)