synapse 2.176.0__py311-none-any.whl → 2.178.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.

Files changed (95) hide show
  1. synapse/axon.py +24 -9
  2. synapse/cortex.py +337 -172
  3. synapse/cryotank.py +46 -37
  4. synapse/datamodel.py +17 -4
  5. synapse/exc.py +19 -0
  6. synapse/lib/agenda.py +7 -13
  7. synapse/lib/aha.py +361 -88
  8. synapse/lib/auth.py +1520 -0
  9. synapse/lib/base.py +27 -9
  10. synapse/lib/cell.py +422 -163
  11. synapse/lib/config.py +15 -11
  12. synapse/lib/coro.py +13 -0
  13. synapse/lib/grammar.py +5 -0
  14. synapse/lib/hive.py +24 -3
  15. synapse/lib/hiveauth.py +6 -32
  16. synapse/lib/layer.py +7 -9
  17. synapse/lib/link.py +22 -18
  18. synapse/lib/lmdbslab.py +152 -3
  19. synapse/lib/modelrev.py +1 -1
  20. synapse/lib/nexus.py +24 -12
  21. synapse/lib/schemas.py +136 -0
  22. synapse/lib/storm.py +61 -29
  23. synapse/lib/stormlib/aha.py +1 -1
  24. synapse/lib/stormlib/auth.py +185 -10
  25. synapse/lib/stormlib/cortex.py +16 -5
  26. synapse/lib/stormlib/gen.py +80 -0
  27. synapse/lib/stormlib/imap.py +6 -2
  28. synapse/lib/stormlib/model.py +55 -0
  29. synapse/lib/stormlib/modelext.py +60 -0
  30. synapse/lib/stormlib/smtp.py +12 -2
  31. synapse/lib/stormlib/tabular.py +212 -0
  32. synapse/lib/stormtypes.py +14 -1
  33. synapse/lib/trigger.py +1 -1
  34. synapse/lib/version.py +2 -2
  35. synapse/lib/view.py +55 -28
  36. synapse/models/base.py +7 -0
  37. synapse/models/biz.py +4 -0
  38. synapse/models/files.py +8 -1
  39. synapse/models/inet.py +8 -0
  40. synapse/telepath.py +32 -17
  41. synapse/tests/files/aha/certs/cas/synapse.crt +28 -0
  42. synapse/tests/files/aha/certs/cas/synapse.key +51 -0
  43. synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.crt +30 -0
  44. synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.key +51 -0
  45. synapse/tests/files/aha/certs/users/root@synapse.crt +29 -0
  46. synapse/tests/files/aha/certs/users/root@synapse.key +51 -0
  47. synapse/tests/files/changelog/model_2.176.0_16ee721a6b7221344eaf946c3ab4602dda546b1a.yaml.gz +0 -0
  48. synapse/tests/files/changelog/model_2.176.0_2a25c58bbd344716cd7cbc3f4304d8925b0f4ef2.yaml.gz +0 -0
  49. synapse/tests/files/rstorm/testsvc.py +1 -1
  50. synapse/tests/test_axon.py +8 -5
  51. synapse/tests/test_cortex.py +149 -141
  52. synapse/tests/test_cryotank.py +4 -4
  53. synapse/tests/test_datamodel.py +7 -0
  54. synapse/tests/test_lib_agenda.py +10 -3
  55. synapse/tests/test_lib_aha.py +336 -490
  56. synapse/tests/{test_lib_hiveauth.py → test_lib_auth.py} +314 -11
  57. synapse/tests/test_lib_base.py +20 -0
  58. synapse/tests/test_lib_cell.py +210 -30
  59. synapse/tests/test_lib_config.py +4 -3
  60. synapse/tests/test_lib_httpapi.py +18 -14
  61. synapse/tests/test_lib_layer.py +33 -33
  62. synapse/tests/test_lib_link.py +42 -1
  63. synapse/tests/test_lib_lmdbslab.py +68 -0
  64. synapse/tests/test_lib_nexus.py +12 -4
  65. synapse/tests/test_lib_node.py +0 -7
  66. synapse/tests/test_lib_storm.py +45 -0
  67. synapse/tests/test_lib_stormlib_aha.py +35 -36
  68. synapse/tests/test_lib_stormlib_auth.py +21 -0
  69. synapse/tests/test_lib_stormlib_cell.py +4 -15
  70. synapse/tests/test_lib_stormlib_cortex.py +12 -12
  71. synapse/tests/test_lib_stormlib_gen.py +99 -0
  72. synapse/tests/test_lib_stormlib_imap.py +14 -3
  73. synapse/tests/test_lib_stormlib_model.py +108 -0
  74. synapse/tests/test_lib_stormlib_modelext.py +64 -0
  75. synapse/tests/test_lib_stormlib_smtp.py +51 -0
  76. synapse/tests/test_lib_stormlib_tabular.py +226 -0
  77. synapse/tests/test_lib_stormsvc.py +4 -1
  78. synapse/tests/test_lib_stormtypes.py +10 -0
  79. synapse/tests/test_model_base.py +3 -0
  80. synapse/tests/test_model_biz.py +3 -0
  81. synapse/tests/test_model_files.py +12 -2
  82. synapse/tests/test_model_inet.py +24 -0
  83. synapse/tests/test_tools_aha.py +78 -101
  84. synapse/tests/test_tools_changelog.py +196 -0
  85. synapse/tests/test_tools_healthcheck.py +4 -3
  86. synapse/tests/utils.py +87 -121
  87. synapse/tools/aha/clone.py +50 -0
  88. synapse/tools/aha/enroll.py +2 -1
  89. synapse/tools/backup.py +2 -2
  90. synapse/tools/changelog.py +776 -15
  91. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/METADATA +48 -48
  92. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/RECORD +95 -82
  93. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/WHEEL +1 -1
  94. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/LICENSE +0 -0
  95. {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ import string
1
2
  import pathlib
2
3
 
3
4
  import synapse.exc as s_exc
@@ -9,7 +10,7 @@ import synapse.tests.utils as s_test
9
10
 
10
11
  class AuthTest(s_test.SynTest):
11
12
 
12
- async def test_hive_auth(self):
13
+ async def test_auth(self):
13
14
 
14
15
  async with self.getTestCore() as core:
15
16
 
@@ -30,6 +31,9 @@ class AuthTest(s_test.SynTest):
30
31
  with self.raises(s_exc.DupRoleName):
31
32
  await auth.addRole('ninjas')
32
33
 
34
+ self.none(await auth._addUser(user.iden, 'visi@vertex.link'))
35
+ self.none(await auth._addRole(user.iden, 'ninjas'))
36
+
33
37
  self.nn(user)
34
38
 
35
39
  with self.raises(s_exc.AuthDeny):
@@ -92,9 +96,13 @@ class AuthTest(s_test.SynTest):
92
96
 
93
97
  await user.revoke(role.iden)
94
98
  self.none(user.allowed(('baz', 'faz')))
99
+ # second revoke does nothing
100
+ await user.revoke(role.iden)
95
101
 
96
102
  await user.grant(role.iden)
97
103
  self.true(user.allowed(('baz', 'faz')))
104
+ # second grant does nothing
105
+ await user.grant(role.iden)
98
106
 
99
107
  await self.asyncraises(s_exc.NoSuchRole, auth.delRole('accountants'))
100
108
 
@@ -106,6 +114,8 @@ class AuthTest(s_test.SynTest):
106
114
  await auth.delUser(user.iden)
107
115
  self.false(user.allowed(('baz', 'faz')))
108
116
 
117
+ self.none(await auth._delUser(user.iden))
118
+
109
119
  role = await auth.addRole('lolusers')
110
120
  role2 = await auth.addRole('lolusers2')
111
121
 
@@ -133,6 +143,15 @@ class AuthTest(s_test.SynTest):
133
143
  self.nn(await auth.getUserByName('user2'))
134
144
  self.none(await auth.getUserByName('user1'))
135
145
 
146
+ with self.raises(s_exc.NoSuchRole):
147
+ await auth.reqRoleByName('newp')
148
+
149
+ with self.raises(s_exc.NoSuchAuthGate):
150
+ await auth.delAuthGate('newp')
151
+
152
+ with self.raises(s_exc.InconsistentStorage):
153
+ await auth.addAuthGate(core.view.iden, 'newp')
154
+
136
155
  async def test_hive_tele_auth(self):
137
156
 
138
157
  # confirm that the primitives used by higher level APIs
@@ -144,7 +163,7 @@ class AuthTest(s_test.SynTest):
144
163
 
145
164
  auth = core.auth
146
165
 
147
- user = await auth.getUserByName('root')
166
+ user = await auth.addUser('lowuser')
148
167
  await user.setPasswd('secret')
149
168
 
150
169
  # tryPasswd
@@ -152,7 +171,7 @@ class AuthTest(s_test.SynTest):
152
171
  self.false(await user.tryPasswd('beep'))
153
172
  self.false(await user.tryPasswd(None))
154
173
 
155
- # hive passwords must be non-zero length strings
174
+ # passwords must be non-zero length strings
156
175
  with self.raises(s_exc.BadArg):
157
176
  await user.setPasswd('')
158
177
  with self.raises(s_exc.BadArg):
@@ -173,23 +192,23 @@ class AuthTest(s_test.SynTest):
173
192
  await user.setLocked(True)
174
193
 
175
194
  with self.raises(s_exc.AuthDeny):
176
- await s_telepath.openurl(turl, user='root', passwd='secret')
195
+ await s_telepath.openurl(turl, user='lowuser', passwd='secret')
177
196
 
178
197
  await user.setLocked(False)
179
198
 
180
199
  # User can't access after being unlocked with wrong password
181
200
  with self.raises(s_exc.AuthDeny):
182
- await s_telepath.openurl(turl, user='root', passwd='newpnewp')
201
+ await s_telepath.openurl(turl, user='lowuser', passwd='newpnewp')
183
202
 
184
203
  # User can access with correct password after being unlocked with
185
- async with await s_telepath.openurl(turl, user='root', passwd='secret') as proxy:
204
+ async with await s_telepath.openurl(turl, user='lowuser', passwd='secret') as proxy:
186
205
  await proxy.getCellInfo()
187
206
 
188
- async def test_hive_authgate_perms(self):
207
+ async def test_authgate_perms(self):
189
208
 
190
209
  async with self.getTestCoreAndProxy() as (core, prox):
191
210
 
192
- # We can retrieve the hivegate information
211
+ # We can retrieve the authgate information
193
212
  gate = await prox.getAuthGate(core.view.iden)
194
213
  self.eq(gate['users'][0], {
195
214
  'iden': core.auth.rootuser.iden,
@@ -286,15 +305,24 @@ class AuthTest(s_test.SynTest):
286
305
  await prox.delRole(friends['iden'])
287
306
  await self.asyncraises(s_exc.AuthDeny, fredcore.count('test:int=11 [:loc=ru]', opts=viewopts))
288
307
 
308
+ friends = await prox.addRole('friends')
309
+ await prox.addRoleRule(friends['iden'], rule, gateiden=layriden)
310
+
311
+ friends = await prox.getRoleInfo('friends')
312
+ self.isin(layriden, friends['authgates'])
313
+
289
314
  wlyr = view2.layers[0]
290
315
 
291
316
  await core.delView(view2.iden)
292
317
  await core.delLayer(wlyr.iden)
293
318
 
294
- # Verify that trashing the layer and view deletes the authgate from the hive
319
+ # Verify that trashing the layer and view deletes the authgate
295
320
  self.none(core.auth.getAuthGate(wlyr.iden))
296
321
  self.none(core.auth.getAuthGate(view2.iden))
297
322
 
323
+ friends = await prox.getRoleInfo('friends')
324
+ self.notin(layriden, friends['authgates'])
325
+
298
326
  # Verify that trashing the write layer deletes the remaining rules and backing store
299
327
  self.false(pathlib.Path(wlyr.dirn).exists())
300
328
  fred = await core.auth.getUserByName('fred')
@@ -302,7 +330,7 @@ class AuthTest(s_test.SynTest):
302
330
  self.len(0, fred.getRules(gateiden=wlyr.iden))
303
331
  self.len(0, fred.getRules(gateiden=view2.iden))
304
332
 
305
- async def test_hive_auth_persistence(self):
333
+ async def test_auth_persistence(self):
306
334
 
307
335
  with self.getTestDir() as fdir:
308
336
 
@@ -345,15 +373,19 @@ class AuthTest(s_test.SynTest):
345
373
  self.none(await core.auth.getUserByName('fred'))
346
374
  self.none(await core.auth.getRoleByName('friends'))
347
375
 
348
- async def test_hive_auth_invalid(self):
376
+ async def test_auth_invalid(self):
349
377
 
350
378
  async with self.getTestCore() as core:
351
379
  with self.raises(s_exc.BadArg):
352
380
  await core.auth.setRoleName(core.auth.allrole.iden, 'ninjas')
353
381
  with self.raises(s_exc.BadArg):
354
382
  await core.auth.rootuser.setName(1)
383
+ with self.raises(s_exc.BadArg):
384
+ await core.auth.rootuser.setName('secretroot')
355
385
  with self.raises(s_exc.BadArg):
356
386
  await core.auth.allrole.setName(1)
387
+ with self.raises(s_exc.BadArg):
388
+ await core.auth.allrole.setName('nobody')
357
389
  with self.raises(s_exc.SchemaViolation):
358
390
  await core.auth.rootuser.addRule('vi.si')
359
391
  with self.raises(s_exc.SchemaViolation):
@@ -362,10 +394,16 @@ class AuthTest(s_test.SynTest):
362
394
  await core.auth.allrole.setRules(None)
363
395
  with self.raises(s_exc.BadArg):
364
396
  await core.auth.rootuser.setAdmin('lol')
397
+ with self.raises(s_exc.BadArg):
398
+ await core.auth.rootuser.setAdmin(False)
365
399
  with self.raises(s_exc.BadArg):
366
400
  await core.auth.rootuser.setLocked('lol')
401
+ with self.raises(s_exc.BadArg):
402
+ await core.auth.rootuser.setLocked(True)
367
403
  with self.raises(s_exc.BadArg):
368
404
  await core.auth.rootuser.setArchived('lol')
405
+ with self.raises(s_exc.BadArg):
406
+ await core.auth.rootuser.setArchived(True)
369
407
  with self.raises(s_exc.SchemaViolation):
370
408
  await core.auth.allrole.addRule((1, ('hehe', 'haha')))
371
409
  with self.raises(s_exc.SchemaViolation):
@@ -373,6 +411,271 @@ class AuthTest(s_test.SynTest):
373
411
  with self.raises(s_exc.SchemaViolation):
374
412
  await core.auth.allrole.setRules([(True, )])
375
413
 
414
+ async def test_auth_password_policy(self):
415
+ policy = {
416
+ 'complexity': {
417
+ 'length': 12,
418
+ 'sequences': 3,
419
+ 'upper:count': 2,
420
+ 'lower:count': 2,
421
+ 'lower:valid': string.ascii_lowercase + 'αβγ',
422
+ 'special:count': 2,
423
+ 'number:count': 2,
424
+ },
425
+ 'attempts': 3,
426
+ 'previous': 2,
427
+ }
428
+
429
+ pass1 = 'jhf9_gaf-xaw-QBX4dqp'
430
+ pass2 = 'rek@hjv6VBV2rwe2qwd!'
431
+ pass3 = 'ZXN-pyv7ber-kzq2kgh'
432
+
433
+ conf = {'auth:passwd:policy': policy}
434
+ async with self.getTestCore(conf=conf) as core:
435
+ auth = core.auth
436
+ self.nn(auth.policy)
437
+
438
+ user = await auth.addUser('blackout@vertex.link')
439
+
440
+ # Compliant passwords
441
+ await core.setUserPasswd(user.iden, pass1)
442
+ await core.setUserPasswd(user.iden, pass2)
443
+ await core.setUserPasswd(user.iden, pass3)
444
+
445
+ # Test password attempt reset
446
+ await core.tryUserPasswd(user.name, 'foo')
447
+ self.eq(user.info.get('policy:attempts'), 1)
448
+
449
+ await core.tryUserPasswd(user.name, pass3)
450
+ self.eq(user.info.get('policy:attempts'), 0)
451
+
452
+ # Test lockout from too many invalid attempts
453
+ self.false(await core.tryUserPasswd(user.name, 'foo'))
454
+ self.false(await core.tryUserPasswd(user.name, 'foo'))
455
+ self.false(await core.tryUserPasswd(user.name, 'foo'))
456
+ self.eq(user.info.get('policy:attempts'), 3)
457
+ self.true(user.info.get('locked'))
458
+
459
+ await user.setLocked(False)
460
+ self.eq(user.info.get('policy:attempts'), 0)
461
+
462
+ # Test reusing previous password
463
+ with self.raises(s_exc.BadArg) as exc:
464
+ await core.setUserPasswd(user.iden, pass2)
465
+ self.eq(exc.exception.get('failures'), [
466
+ 'Password cannot be the same as previous 2 password(s).'
467
+ ])
468
+
469
+ await core.setUserPasswd(user.iden, pass1)
470
+
471
+ # Test password that doesn't meet complexity requirements
472
+ with self.raises(s_exc.BadArg) as exc:
473
+ await core.setUserPasswd(user.iden, 'Ff1!')
474
+ self.eq(exc.exception.get('failures'), [
475
+ 'Password must be at least 12 characters.',
476
+ 'Password must contain at least 2 uppercase characters, 1 found.',
477
+ 'Password must contain at least 2 lowercase characters, 1 found.',
478
+ 'Password must contain at least 2 special characters, 1 found.',
479
+ 'Password must contain at least 2 digit characters, 1 found.'
480
+ ])
481
+
482
+ # Check sequences
483
+ seqmsg = f'Password must not contain forward/reverse sequences longer than 3 characters.'
484
+ passwords = [
485
+ # letters
486
+ 'abcA', 'dcbA', 'Abcd', 'Acba',
487
+
488
+ # numbers
489
+ '123A', '432A', 'A234', 'A321',
490
+
491
+ # greek alphabet (unicode test)
492
+ 'αβγA', 'Aαβγ', 'γβαA', 'Aγβα',
493
+
494
+ ]
495
+
496
+ for password in passwords:
497
+ with self.raises(s_exc.BadArg) as exc:
498
+ await core.setUserPasswd(user.iden, password)
499
+ self.isin(seqmsg, exc.exception.get('failures'))
500
+
501
+ with self.raises(s_exc.BadArg) as exc:
502
+ await core.setUserPasswd(user.iden, 'AAAA')
503
+ self.eq(exc.exception.get('failures'), [
504
+ 'Password must be at least 12 characters.',
505
+ 'Password must contain at least 2 lowercase characters, 0 found.',
506
+ 'Password must contain at least 2 special characters, 0 found.',
507
+ 'Password must contain at least 2 digit characters, 0 found.'
508
+ ])
509
+
510
+ with self.raises(s_exc.BadArg) as exc:
511
+ await core.setUserPasswd(user.iden, 'aaaa')
512
+ self.eq(exc.exception.get('failures'), [
513
+ 'Password must be at least 12 characters.',
514
+ 'Password must contain at least 2 uppercase characters, 0 found.',
515
+ 'Password must contain at least 2 special characters, 0 found.',
516
+ 'Password must contain at least 2 digit characters, 0 found.'
517
+ ])
518
+
519
+ with self.raises(s_exc.BadArg) as exc:
520
+ await core.setUserPasswd(user.iden, None)
521
+ self.isin(
522
+ 'Password must be at least 12 characters.',
523
+ exc.exception.get('failures')
524
+ )
525
+
526
+ # Attempting to add a user with a bad passwd will add the user and fail to set the password
527
+ with self.raises(s_exc.BadArg):
528
+ await core.addUser('bob.grey', email='bob.grey@vertex.link', passwd='noncompliant')
529
+ user = await core.auth.getUserByName('bob.grey')
530
+ self.eq('bob.grey@vertex.link', user.info.get('email'))
531
+ self.len(1, user.info.get('roles')) # User has the default all role
532
+ # Password was not set
533
+ self.false(await user.tryPasswd('noncompliant'))
534
+
535
+ policy = {
536
+ 'complexity': {
537
+ 'length': None,
538
+ },
539
+ }
540
+
541
+ conf = {'auth:passwd:policy': policy}
542
+ async with self.getTestCore(conf=conf) as core:
543
+ auth = core.auth
544
+
545
+ user = await auth.addUser('blackout@vertex.link')
546
+
547
+ await core.setUserPasswd(user.iden, None)
548
+
549
+ with self.raises(s_exc.BadArg) as exc:
550
+ await core.setUserPasswd(user.iden, 'αβγA')
551
+ self.isin(
552
+ "Password contains invalid characters: ['α', 'β', 'γ']",
553
+ exc.exception.get('failures')
554
+ )
555
+
556
+ policy = {
557
+ 'complexity': None,
558
+ 'previous': 3,
559
+ }
560
+
561
+ conf = {'auth:passwd:policy': policy}
562
+ async with self.getTestCore(conf=conf) as core:
563
+ auth = core.auth
564
+
565
+ user = await auth.addUser('blackout@vertex.link')
566
+ await core.setUserPasswd(user.iden, pass1)
567
+ await core.setUserPasswd(user.iden, pass2)
568
+ await core.setUserPasswd(user.iden, pass3)
569
+
570
+ with self.raises(s_exc.BadArg) as exc:
571
+ await core.setUserPasswd(user.iden, pass1)
572
+ self.eq(exc.exception.get('failures'), [
573
+ 'Password cannot be the same as previous 3 password(s).'
574
+ ])
575
+
576
+ # Single complexity rule, uses default character lists
577
+ policy = {'complexity': {'length': 3}}
578
+ conf = {'auth:passwd:policy': policy}
579
+ async with self.getTestCore(conf=conf) as core:
580
+ auth = core.auth
581
+ user = await auth.addUser('blackout@vertex.link')
582
+ with self.raises(s_exc.BadArg):
583
+ await core.setUserPasswd(user.iden, 'no')
584
+ await core.setUserPasswd(user.iden, 'hehe')
585
+ await core.setUserPasswd(user.iden, 'heh')
586
+ with self.raises(s_exc.BadArg) as cm:
587
+ await core.setUserPasswd(user.iden, 'hehαβγ')
588
+ self.isin('Password contains invalid characters', cm.exception.get('mesg'))
589
+
590
+ # Complexity disables the *:valid groups so they will not be checked
591
+ policy = {'complexity': {'length': 3,
592
+ 'upper:valid': None,
593
+ 'upper:count': 20,
594
+ 'lower:valid': None,
595
+ 'lower:count': 20,
596
+ 'special:valid': None,
597
+ 'special:count': 20,
598
+ 'number:valid': None,
599
+ 'number:count': 20,
600
+ }}
601
+ conf = {'auth:passwd:policy': policy}
602
+ async with self.getTestCore(conf=conf) as core:
603
+ auth = core.auth
604
+ user = await auth.addUser('blackout@vertex.link')
605
+ with self.raises(s_exc.BadArg):
606
+ await core.setUserPasswd(user.iden, 'no')
607
+ await core.setUserPasswd(user.iden, 'heh')
608
+ await core.setUserPasswd(user.iden, 'hehαβγ1234!!@!@!')
609
+
610
+ # Policy only allows lowercase and specials...
611
+ policy = {'complexity': {'length': 2,
612
+ 'upper:valid': None,
613
+ 'number:valid': None,
614
+ 'lower:count': 1,
615
+ 'special:count': 1,
616
+ }}
617
+ conf = {'auth:passwd:policy': policy}
618
+ async with self.getTestCore(conf=conf) as core:
619
+ auth = core.auth
620
+ user = await auth.addUser('blackout@vertex.link')
621
+ with self.raises(s_exc.BadArg):
622
+ await core.setUserPasswd(user.iden, 'No')
623
+ with self.raises(s_exc.BadArg):
624
+ await core.setUserPasswd(user.iden, '1o')
625
+ await core.setUserPasswd(user.iden, 'y#s')
626
+
627
+ # Policy enforces character sets but doesn't care about minimum entries
628
+ policy = {'complexity': {'length': 3,
629
+ 'upper:count': None,
630
+ 'lower:count': None,
631
+ 'special:count': None,
632
+ 'number:count': None,
633
+ }}
634
+ conf = {'auth:passwd:policy': policy}
635
+ async with self.getTestCore(conf=conf) as core:
636
+ auth = core.auth
637
+ user = await auth.addUser('blackout@vertex.link')
638
+ await core.setUserPasswd(user.iden, 'yup')
639
+ await core.setUserPasswd(user.iden, 'Y!0')
640
+ with self.raises(s_exc.BadArg) as cm:
641
+ await core.setUserPasswd(user.iden, 'sadαβγ')
642
+ self.isin('Password contains invalid characters', cm.exception.get('mesg'))
643
+
644
+ # No complexity rules
645
+ policy = {'attempts': 1}
646
+ conf = {'auth:passwd:policy': policy, 'auth:passwd': 'secret'}
647
+ async with self.getTestCore(conf=conf) as core:
648
+ auth = core.auth
649
+ user = await auth.addUser('blackout@vertex.link')
650
+ await core.setUserPasswd(user.iden, 'hehe')
651
+ self.true(await user.tryPasswd('hehe'))
652
+ self.false(await user.tryPasswd('newp'))
653
+ self.true(user.isLocked())
654
+ # Root user may track policy lockouts but will not be locked out by failures.
655
+ root = auth.rootuser
656
+ self.false(await root.tryPasswd('newp'))
657
+ self.false(await root.tryPasswd('newpx'))
658
+ self.eq(root.info.get('policy:attempts'), 2)
659
+ self.false(root.isLocked())
660
+ # valid passwod auth resets root atttempt counter.
661
+ self.true(await root.tryPasswd('secret'))
662
+ self.eq(root.info.get('policy:attempts'), 0)
663
+
664
+ # auth:passwd does not interact with auth:passwd:policy
665
+ with self.getTestDir() as dirn:
666
+ policy = {'complexity': {'length': 5}}
667
+ conf = {'auth:passwd': 'newp', 'auth:passwd:policy': policy}
668
+ async with self.getTestCore(conf=conf, dirn=dirn) as core:
669
+ user = core.auth.rootuser
670
+ self.false(await user.tryPasswd('hehe'))
671
+ self.true(await user.tryPasswd('newp'))
672
+
673
+ conf = {'auth:passwd': 'yupp!!', 'auth:passwd:policy': policy}
674
+ async with self.getTestCore(conf=conf, dirn=dirn) as core:
675
+ user = core.auth.rootuser
676
+ self.false(await user.tryPasswd('newp'))
677
+ self.true(await user.tryPasswd('yupp!!'))
678
+
376
679
  async def test_hive_auth_deepdeny(self):
377
680
  async with self.getTestCore() as core:
378
681
 
@@ -60,6 +60,21 @@ class BaseTest(s_t_utils.SynTest):
60
60
  event = await base.fire('woot', x=3, y=5, ret=[])
61
61
  self.eq(event[1]['ret'], 8)
62
62
 
63
+ base00 = await s_base.Base.anit()
64
+ base01 = await s_base.Base.anit()
65
+ data = {}
66
+ async def onfini():
67
+ data['woot'] = True
68
+
69
+ await base00.fini()
70
+ base00.onfini(onfini)
71
+ await asyncio.sleep(0)
72
+ self.true(data.get('woot'))
73
+
74
+ base00.onfini(base01)
75
+ await asyncio.sleep(0)
76
+ self.true(base01.isfini)
77
+
63
78
  async def test_base_anit(self):
64
79
 
65
80
  afoo = await Hehe.anit(20)
@@ -201,6 +216,7 @@ class BaseTest(s_t_utils.SynTest):
201
216
  self.eq(data['count'], 1)
202
217
 
203
218
  async def test_base_waiter(self):
219
+
204
220
  base0 = await s_base.Base.anit()
205
221
 
206
222
  wait0 = base0.waiter(3, 'foo:bar')
@@ -224,6 +240,10 @@ class BaseTest(s_t_utils.SynTest):
224
240
  evts = await wait2.wait(1)
225
241
  self.len(2, evts)
226
242
 
243
+ with self.raises(s_exc.TimeOut):
244
+ async with base0.waiter(1, 'newp', 'nuuh', timeout=0.01):
245
+ pass
246
+
227
247
  async def test_baseref(self):
228
248
 
229
249
  bref = await s_base.BaseRef.anit()