synapse 2.168.0__py311-none-any.whl → 2.170.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 (42) hide show
  1. synapse/cortex.py +99 -10
  2. synapse/datamodel.py +5 -0
  3. synapse/lib/agenda.py +3 -0
  4. synapse/lib/ast.py +70 -12
  5. synapse/lib/cell.py +83 -21
  6. synapse/lib/httpapi.py +3 -0
  7. synapse/lib/layer.py +75 -6
  8. synapse/lib/node.py +7 -0
  9. synapse/lib/snap.py +25 -5
  10. synapse/lib/storm.py +1 -1
  11. synapse/lib/stormlib/cortex.py +1 -1
  12. synapse/lib/stormlib/model.py +420 -1
  13. synapse/lib/stormtypes.py +68 -3
  14. synapse/lib/types.py +35 -0
  15. synapse/lib/version.py +2 -2
  16. synapse/lib/view.py +94 -24
  17. synapse/models/files.py +40 -0
  18. synapse/models/inet.py +8 -4
  19. synapse/models/infotech.py +355 -17
  20. synapse/tests/files/cpedata.json +525034 -0
  21. synapse/tests/test_cortex.py +99 -0
  22. synapse/tests/test_lib_agenda.py +17 -3
  23. synapse/tests/test_lib_ast.py +66 -0
  24. synapse/tests/test_lib_cell.py +133 -52
  25. synapse/tests/test_lib_layer.py +52 -1
  26. synapse/tests/test_lib_scrape.py +72 -71
  27. synapse/tests/test_lib_snap.py +16 -1
  28. synapse/tests/test_lib_storm.py +118 -0
  29. synapse/tests/test_lib_stormlib_cortex.py +27 -0
  30. synapse/tests/test_lib_stormlib_model.py +532 -0
  31. synapse/tests/test_lib_stormtypes.py +161 -14
  32. synapse/tests/test_lib_types.py +20 -0
  33. synapse/tests/test_lib_view.py +77 -0
  34. synapse/tests/test_model_files.py +51 -0
  35. synapse/tests/test_model_inet.py +63 -1
  36. synapse/tests/test_model_infotech.py +187 -26
  37. synapse/tests/utils.py +12 -0
  38. {synapse-2.168.0.dist-info → synapse-2.170.0.dist-info}/METADATA +1 -1
  39. {synapse-2.168.0.dist-info → synapse-2.170.0.dist-info}/RECORD +42 -41
  40. {synapse-2.168.0.dist-info → synapse-2.170.0.dist-info}/LICENSE +0 -0
  41. {synapse-2.168.0.dist-info → synapse-2.170.0.dist-info}/WHEEL +0 -0
  42. {synapse-2.168.0.dist-info → synapse-2.170.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import synapse.exc as s_exc
2
+ import synapse.lib.time as s_time
2
3
  import synapse.lib.layer as s_layer
3
4
 
4
5
  import synapse.tests.utils as s_test
@@ -295,3 +296,534 @@ class StormlibModelTest(s_test.SynTest):
295
296
 
296
297
  self.stormIsInWarn('.pdep is not yet locked', mesgs)
297
298
  self.stormNotInWarn('test:dep:easy.pdep is not yet locked', mesgs)
299
+
300
+ async def test_stormlib_model_migration(self):
301
+
302
+ async with self.getTestCore() as core:
303
+
304
+ nodes = await core.nodes('[ test:str=src test:str=dst test:str=deny test:str=other ]')
305
+ otheriden = nodes[3].iden()
306
+
307
+ lowuser = await core.auth.addUser('lowuser')
308
+ aslow = {'user': lowuser.iden}
309
+
310
+ # copy node data
311
+
312
+ await self.asyncraises(s_exc.BadArg, core.nodes('test:str=src $lib.model.migration.copyData($node, newp)'))
313
+ await self.asyncraises(s_exc.BadArg, core.nodes('test:str=dst $lib.model.migration.copyData(newp, $node)'))
314
+
315
+ nodes = await core.nodes('''
316
+ test:str=src
317
+ $node.data.set(a, a-src)
318
+ $node.data.set(b, b-src)
319
+ $n=$node -> {
320
+ test:str=dst
321
+ $node.data.set(a, a-dst)
322
+ $lib.model.migration.copyData($n, $node)
323
+ }
324
+ ''')
325
+ self.len(1, nodes)
326
+ self.sorteq(
327
+ [('a', 'a-dst'), ('b', 'b-src')],
328
+ [data async for data in nodes[0].iterData()]
329
+ )
330
+
331
+ nodes = await core.nodes('''
332
+ test:str=src $n=$node -> {
333
+ test:str=dst
334
+ $lib.model.migration.copyData($n, $node, overwrite=$lib.true)
335
+ }
336
+ ''')
337
+ self.len(1, nodes)
338
+ self.sorteq(
339
+ [('a', 'a-src'), ('b', 'b-src')],
340
+ [data async for data in nodes[0].iterData()]
341
+ )
342
+
343
+ q = 'test:str=src $n=$node -> { test:str=deny $lib.model.migration.copyData($n, $node) }'
344
+ await self.asyncraises(s_exc.AuthDeny, core.nodes(q, opts=aslow))
345
+
346
+ # copy edges
347
+
348
+ await self.asyncraises(s_exc.BadArg, core.nodes('test:str=src $lib.model.migration.copyEdges($node, newp)'))
349
+ await self.asyncraises(s_exc.BadArg, core.nodes('test:str=dst $lib.model.migration.copyEdges(newp, $node)'))
350
+
351
+ nodes = await core.nodes('''
352
+ test:str=src
353
+ [ <(foo)+ { test:str=other } +(bar)> { test:str=other } ]
354
+ $n=$node -> {
355
+ test:str=dst
356
+ $lib.model.migration.copyEdges($n, $node)
357
+ }
358
+ ''')
359
+ self.len(1, nodes)
360
+ self.eq([('bar', otheriden)], [edge async for edge in nodes[0].iterEdgesN1()])
361
+ self.eq([('foo', otheriden)], [edge async for edge in nodes[0].iterEdgesN2()])
362
+
363
+ q = 'test:str=src $n=$node -> { test:str=deny $lib.model.migration.copyEdges($n, $node) }'
364
+ await self.asyncraises(s_exc.AuthDeny, core.nodes(q, opts=aslow))
365
+
366
+ # copy tags
367
+
368
+ await self.asyncraises(s_exc.BadArg, core.nodes('test:str=src $lib.model.migration.copyTags($node, newp)'))
369
+ await self.asyncraises(s_exc.BadArg, core.nodes('test:str=dst $lib.model.migration.copyTags(newp, $node)'))
370
+
371
+ await core.nodes('$lib.model.ext.addTagProp(test, (str, ({})), ({}))')
372
+
373
+ nodes = await core.nodes('''
374
+ test:str=src
375
+ [ +#foo=(2010, 2012) +#foo.bar +#baz:test=src ]
376
+ $n=$node -> {
377
+ test:str=dst
378
+ [ +#foo=(2010, 2011) +#baz:test=dst ]
379
+ $lib.model.migration.copyTags($n, $node)
380
+ }
381
+ ''')
382
+ self.len(1, nodes)
383
+ self.sorteq([
384
+ ('baz', (None, None)),
385
+ ('foo', (s_time.parse('2010'), s_time.parse('2012'))),
386
+ ('foo.bar', (None, None))
387
+ ], nodes[0].getTags())
388
+ self.eq([], nodes[0].getTagProps('foo'))
389
+ self.eq([], nodes[0].getTagProps('foo.bar'))
390
+ self.eq([('test', 'dst')], [(k, nodes[0].getTagProp('baz', k)) for k in nodes[0].getTagProps('baz')])
391
+
392
+ nodes = await core.nodes('''
393
+ test:str=src $n=$node -> {
394
+ test:str=dst
395
+ $lib.model.migration.copyTags($n, $node, overwrite=$lib.true)
396
+ }
397
+ ''')
398
+ self.len(1, nodes)
399
+ self.eq([('test', 'src')], [(k, nodes[0].getTagProp('baz', k)) for k in nodes[0].getTagProps('baz')])
400
+
401
+ q = 'test:str=src $n=$node -> { test:str=deny $lib.model.migration.copyTags($n, $node) }'
402
+ await self.asyncraises(s_exc.AuthDeny, core.nodes(q, opts=aslow))
403
+
404
+ async def test_model_migration_s_itSecCpe_2_170_0(self):
405
+
406
+ async with self.getRegrCore('itSecCpe_2_170_0') as core:
407
+ # Migrate it:sec:cpe nodes with a valid CPE2.3, valid CPE2.2
408
+ q = 'it:sec:cpe +#test.cpe.23valid +#test.cpe.22valid'
409
+ nodes = await core.nodes(q)
410
+ self.len(2, nodes)
411
+ self.eq(
412
+ [
413
+ ('it:sec:cpe', 'cpe:2.3:a:abine:donottrackme_-_mobile_privacy:1.1.8:*:*:*:*:android:*:*'),
414
+ ('it:sec:cpe', 'cpe:2.3:a:01generator:pireospay:-:*:*:*:*:prestashop:*:*')
415
+ ],
416
+ [node.ndef for node in nodes]
417
+ )
418
+
419
+ q = '''
420
+ it:sec:cpe +#test.cpe.23valid +#test.cpe.22valid
421
+ $lib.debug=$lib.true
422
+ $lib.model.migration.s.itSecCpe_2_170_0($node)
423
+ $node.data.load(migration.s.itSecCpe_2_170_0)
424
+ '''
425
+ nodes = await core.nodes(q)
426
+
427
+ data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
428
+ self.nn(data)
429
+ self.eq(data['status'], 'success')
430
+ self.none(data.get('reason'))
431
+
432
+ data = nodes[1].nodedata['migration.s.itSecCpe_2_170_0']
433
+ self.nn(data)
434
+ self.eq(data['status'], 'success')
435
+ self.none(data.get('reason'))
436
+
437
+ async with self.getRegrCore('itSecCpe_2_170_0') as core:
438
+ # Migrate it:sec:cpe nodes with a valid CPE2.3, invalid CPE2.2
439
+ q = '''
440
+ it:sec:cpe +#test.cpe.23valid +#test.cpe.22invalid
441
+ $lib.debug=$lib.true
442
+ $lib.model.migration.s.itSecCpe_2_170_0($node)
443
+ '''
444
+ nodes = await core.nodes(q)
445
+ self.len(3, nodes)
446
+ self.eq(
447
+ [
448
+ ('it:sec:cpe', 'cpe:2.3:a:1c:1c\\:enterprise:-:*:*:*:*:*:*:*'),
449
+ ('it:sec:cpe', 'cpe:2.3:o:zyxel:nas542_firmware:5.21\\%28aazf.15\\%29co:*:*:*:*:*:*:*'),
450
+ ('it:sec:cpe', 'cpe:2.3:a:abinitio:control\\>center:-:*:*:*:*:*:*:*'),
451
+ ],
452
+ [node.ndef for node in nodes]
453
+ )
454
+
455
+ q = '''
456
+ it:sec:cpe +#test.cpe.23valid +#test.cpe.22invalid
457
+ $node.data.load(migration.s.itSecCpe_2_170_0)
458
+ '''
459
+ nodes = await core.nodes(q)
460
+ self.len(3, nodes)
461
+
462
+ data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
463
+ self.nn(data)
464
+ self.eq(data['status'], 'success')
465
+ self.eq(data['updated'], ['v2_2', 'product'])
466
+ self.eq(nodes[0].get('v2_2'), 'cpe:/a:1c:1c%3aenterprise:-')
467
+ self.eq(nodes[0].get('product'), '1c:enterprise')
468
+
469
+ data = nodes[1].nodedata['migration.s.itSecCpe_2_170_0']
470
+ self.nn(data)
471
+ self.eq(data['status'], 'success')
472
+ self.none(data.get('valu'))
473
+ self.eq(data['updated'], ['v2_2', 'version'])
474
+ self.eq(nodes[1].get('v2_2'), 'cpe:/o:zyxel:nas542_firmware:5.21%2528aazf.15%2529co')
475
+ self.eq(nodes[1].get('version'), '5.21%28aazf.15%29co')
476
+
477
+ data = nodes[2].nodedata['migration.s.itSecCpe_2_170_0']
478
+ self.nn(data)
479
+ self.eq(data['status'], 'success')
480
+ self.eq(data['updated'], ['v2_2', 'product'])
481
+ self.eq(nodes[2].get('v2_2'), 'cpe:/a:abinitio:control%3ecenter:-')
482
+ self.eq(nodes[2].get('product'), 'control>center')
483
+
484
+ # The migration of this node was not correct because the CPE2.3 string (primary property) is valid but was
485
+ # not created correctly due to a bad CPE2.2 input value. Now we update :v2_2 to be correct, and re-run the
486
+ # migration. This time, we specify `prefer_v22=True` and `force=True` so the migration will use the updated
487
+ # :v2_2 prop for reparsing the strings. Force will cause the migration to continue past the check where both
488
+ # the primary property and :v2_2 are valid.
489
+ q = '''
490
+ it:sec:cpe:product=nas542_firmware [ :v2_2="cpe:/o:zyxel:nas542_firmware:5.21%28aazf.15%29co" ]
491
+ $lib.debug=$lib.true
492
+ $lib.model.migration.s.itSecCpe_2_170_0($node, prefer_v22=$lib.true, force=$lib.true)
493
+ '''
494
+ nodes = await core.nodes(q)
495
+ self.len(1, nodes)
496
+
497
+ # Lift the updated node and check the migration did what was expected.
498
+ q = '''
499
+ it:sec:cpe:product=nas542_firmware
500
+ $node.data.load(migration.s.itSecCpe_2_170_0)
501
+ '''
502
+ nodes = await core.nodes(q)
503
+ self.len(1, nodes)
504
+
505
+ data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
506
+ self.nn(data)
507
+ self.eq(data['status'], 'success')
508
+ self.eq(data['updated'], ['version'])
509
+ self.eq(data['valu'], 'cpe:2.3:o:zyxel:nas542_firmware:5.21\\(aazf.15\\)co:*:*:*:*:*:*:*')
510
+ self.eq(nodes[0].get('v2_2'), 'cpe:/o:zyxel:nas542_firmware:5.21%28aazf.15%29co')
511
+ self.eq(nodes[0].get('version'), '5.21(aazf.15)co')
512
+
513
+ async with self.getRegrCore('itSecCpe_2_170_0') as core:
514
+ # Migrate it:sec:cpe nodes with a invalid CPE2.3, valid CPE2.2
515
+ q = '''
516
+ it:sec:cpe +#test.cpe.23invalid +#test.cpe.22valid
517
+ $lib.debug=$lib.true
518
+ $lib.model.migration.s.itSecCpe_2_170_0($node)
519
+ '''
520
+ nodes = await core.nodes(q)
521
+ self.len(4, nodes)
522
+ self.eq(
523
+ [
524
+ ('it:sec:cpe', 'cpe:2.3:h:d\\-link:dir\\-850l:*:*:*:*:*:*:*:*'),
525
+ ('it:sec:cpe', 'cpe:2.3:a:acurax:under_construction_%2f_maintenance_mode:-::~~~wordpress~~:*:*:*:*:*'),
526
+ ('it:sec:cpe', 'cpe:2.3:a:10web:social_feed_for_instagram:1.0.0::~~premium~wordpress~~:*:*:*:*:*'),
527
+ ('it:sec:cpe', 'cpe:2.3:o:zyxel:nas326_firmware:5.21%28aazf.14%29c0:*:*:*:*:*:*:*'),
528
+ ],
529
+ [node.ndef for node in nodes]
530
+ )
531
+
532
+ q = '''
533
+ it:sec:cpe +#test.cpe.23invalid +#test.cpe.22valid
534
+ $node.data.load(migration.s.itSecCpe_2_170_0)
535
+ '''
536
+ nodes = await core.nodes(q)
537
+ self.len(4, nodes)
538
+
539
+ data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
540
+ self.nn(data)
541
+ self.eq(data['status'], 'success')
542
+ self.eq(data['updated'], ['vendor', 'product'])
543
+ self.eq(data['valu'], 'cpe:2.3:h:d-link:dir-850l:*:*:*:*:*:*:*:*')
544
+ self.eq(nodes[0].get('vendor'), 'd-link')
545
+ self.eq(nodes[0].get('product'), 'dir-850l')
546
+
547
+ data = nodes[1].nodedata['migration.s.itSecCpe_2_170_0']
548
+ self.nn(data)
549
+ self.eq(data['status'], 'success')
550
+ self.eq(data['updated'], ['product', 'update', 'edition', 'target_sw'])
551
+ self.eq(data['valu'], 'cpe:2.3:a:acurax:under_construction_\\/_maintenance_mode:-:*:*:*:*:wordpress:*:*')
552
+ self.eq(nodes[1].get('product'), 'under_construction_/_maintenance_mode')
553
+ self.eq(nodes[1].get('update'), '*')
554
+ self.eq(nodes[1].get('edition'), '*')
555
+ self.eq(nodes[1].get('target_sw'), 'wordpress')
556
+
557
+ data = nodes[2].nodedata['migration.s.itSecCpe_2_170_0']
558
+ self.nn(data)
559
+ self.eq(data['status'], 'success')
560
+ self.eq(data['updated'], ['update', 'edition', 'sw_edition', 'target_sw'])
561
+ self.eq(data['valu'], 'cpe:2.3:a:10web:social_feed_for_instagram:1.0.0:*:*:*:premium:wordpress:*:*')
562
+ self.eq(nodes[2].get('update'), '*')
563
+ self.eq(nodes[2].get('edition'), '*')
564
+ self.eq(nodes[2].get('sw_edition'), 'premium')
565
+ self.eq(nodes[2].get('target_sw'), 'wordpress')
566
+
567
+ data = nodes[3].nodedata['migration.s.itSecCpe_2_170_0']
568
+ self.nn(data)
569
+ self.eq(data['status'], 'success')
570
+ self.eq(data['updated'], ['version'])
571
+ self.eq(data['valu'], 'cpe:2.3:o:zyxel:nas326_firmware:5.21\\(aazf.14\\)c0:*:*:*:*:*:*:*')
572
+ self.eq(nodes[3].get('version'), '5.21(aazf.14)c0')
573
+
574
+ async with self.getRegrCore('itSecCpe_2_170_0') as core:
575
+ # Migrate it:sec:cpe nodes with a invalid CPE2.3, invalid CPE2.2
576
+ q = '''
577
+ it:sec:cpe +#test.cpe.23invalid +#test.cpe.22invalid
578
+ $lib.debug=$lib.true
579
+ $lib.model.migration.s.itSecCpe_2_170_0($node)
580
+ '''
581
+ msgs = await core.stormlist(q)
582
+ mesg = 'itSecCpe_2_170_0(it:sec:cpe=cpe:2.3:a:openbsd:openssh:8.2p1 ubuntu-4ubuntu0.2:*:*:*:*:*:*:*): '
583
+ mesg += 'Unable to migrate due to invalid data. Primary property and :v2_2 are both invalid.'
584
+ self.stormIsInWarn(mesg, msgs)
585
+
586
+ ndefs = [m[1][0] for m in msgs if m[0] == 'node']
587
+ self.eq(
588
+ [
589
+ ('it:sec:cpe', 'cpe:2.3:a:openbsd:openssh:7.4\r\n:*:*:*:*:*:*:*'),
590
+ ('it:sec:cpe', 'cpe:2.3:a:openbsd:openssh:8.2p1 ubuntu-4ubuntu0.2:*:*:*:*:*:*:*')
591
+ ],
592
+ ndefs
593
+ )
594
+
595
+ q = '''
596
+ it:sec:cpe +#test.cpe.23invalid +#test.cpe.22invalid
597
+ $node.data.load(migration.s.itSecCpe_2_170_0)
598
+ '''
599
+ nodes = await core.nodes(q)
600
+ self.len(2, nodes)
601
+
602
+ for node in nodes:
603
+ data = node.nodedata['migration.s.itSecCpe_2_170_0']
604
+ self.eq(data, {
605
+ 'status': 'failed',
606
+ 'reason': 'Unable to migrate due to invalid data. Primary property and :v2_2 are both invalid.',
607
+ })
608
+
609
+ # Now update the :v2_2 on one of the nodes and migrate again
610
+ q = '''
611
+ it:sec:cpe:version^=8.2p1 [ :v2_2="cpe:/a:openbsd:openssh:8.2p1_ubuntu-4ubuntu0.2" ]
612
+ $lib.debug=$lib.true
613
+ $lib.model.migration.s.itSecCpe_2_170_0($node)
614
+ '''
615
+ msgs = await core.stormlist(q)
616
+ self.stormHasNoWarnErr(msgs)
617
+
618
+ q = '''
619
+ it:sec:cpe:version^=8.2p1
620
+ $node.data.load(migration.s.itSecCpe_2_170_0)
621
+ '''
622
+ nodes = await core.nodes(q)
623
+ self.len(1, nodes)
624
+
625
+ data = nodes[0].nodedata['migration.s.itSecCpe_2_170_0']
626
+ self.nn(data)
627
+ self.eq(data['status'], 'success')
628
+ self.eq(data['updated'], ['version'])
629
+ self.eq(data['valu'], 'cpe:2.3:a:openbsd:openssh:8.2p1_ubuntu-4ubuntu0.2:*:*:*:*:*:*:*')
630
+ self.eq(nodes[0].get('version'), '8.2p1_ubuntu-4ubuntu0.2')
631
+
632
+ # Run the migration again to make sure we identify already migrated
633
+ # nodes correctly and bail early.
634
+ q = '''
635
+ it:sec:cpe:version^=8.2p1
636
+ $lib.debug=$lib.true
637
+ $lib.model.migration.s.itSecCpe_2_170_0($node)
638
+ '''
639
+ msgs = await core.stormlist(q)
640
+ self.stormIsInPrint(f'DEBUG: itSecCpe_2_170_0(it:sec:cpe=cpe:2.3:a:openbsd:openssh:8.2p1 ubuntu-4ubuntu0.2:*:*:*:*:*:*:*): Node already migrated.', msgs)
641
+
642
+ q = '''
643
+ it:sec:cpe:version^=8.2p1
644
+ $lib.debug=$lib.true
645
+ $lib.model.migration.s.itSecCpe_2_170_0($node, force=$lib.true)
646
+ '''
647
+ msgs = await core.stormlist(q)
648
+ self.stormIsInPrint(f'DEBUG: itSecCpe_2_170_0(it:sec:cpe=cpe:2.3:a:openbsd:openssh:8.2p1 ubuntu-4ubuntu0.2:*:*:*:*:*:*:*): No property updates required.', msgs)
649
+
650
+ async with self.getTestCore() as core:
651
+ with self.raises(s_exc.BadArg):
652
+ await core.callStorm('$lib.model.migration.s.itSecCpe_2_170_0(newp)')
653
+
654
+ with self.raises(s_exc.BadArg):
655
+ await core.callStorm('[ inet:fqdn=vertex.link ] $lib.model.migration.s.itSecCpe_2_170_0($node)')
656
+
657
+ async def test_stormlib_model_migrations_risk_hasvuln_vulnerable(self):
658
+
659
+ async with self.getTestCore() as core:
660
+
661
+ await core.nodes('$lib.model.ext.addTagProp(test, (str, ({})), ({}))')
662
+ await core.nodes('$lib.model.ext.addFormProp(risk:hasvuln, _test, (ps:contact, ({})), ({}))')
663
+
664
+ await core.nodes('[ risk:vuln=* it:prod:softver=* +#test ]')
665
+
666
+ opts = {
667
+ 'vars': {
668
+ 'guid00': (guid00 := 'c6f158a4d8e267a023b06415a04bf583'),
669
+ 'guid01': (guid01 := 'e98f7eada5f5057bc3181ab3fab1f7d5'),
670
+ 'guid02': (guid02 := '99b27f37f5cc1681ad0617e7c97a4094'),
671
+ }
672
+ }
673
+
674
+ # nodes with 1 vulnerable node get matching guids
675
+ # all data associated with hasvuln (except ext props) are migrated
676
+
677
+ nodes = await core.nodes('''
678
+ [ risk:hasvuln=$guid00
679
+ :software={ it:prod:softver#test }
680
+ :vuln={ risk:vuln#test }
681
+ :_test={[ ps:contact=* ]}
682
+ .seen=(2010, 2011)
683
+ +#test=(2012, 2013)
684
+ +#test.foo:test=hi
685
+ <(seen)+ {[ meta:source=* :name=foo ]}
686
+ +(refs)> {[ ps:contact=* :name=bar ]}
687
+ ]
688
+ $node.data.set(baz, bam)
689
+ $n=$node -> { yield $lib.model.migration.s.riskHasVulnToVulnerable($n) }
690
+ ''', opts=opts)
691
+ self.len(1, nodes)
692
+ self.eq(guid00, nodes[0].ndef[1])
693
+ self.eq([
694
+ ('test', (s_time.parse('2012'), s_time.parse('2013'))),
695
+ ('test.foo', (None, None))
696
+ ], nodes[0].getTags())
697
+ self.eq('hi', nodes[0].getTagProp('test.foo', 'test'))
698
+ self.eq('bam', await nodes[0].getData('baz'))
699
+
700
+ self.len(1, await core.nodes('risk:vulnerable#test <(seen)- meta:source +:name=foo'))
701
+ self.len(1, await core.nodes('risk:vulnerable#test -(refs)> ps:contact +:name=bar'))
702
+ self.len(1, await core.nodes('risk:vulnerable#test :vuln -> risk:vuln +#test'))
703
+ self.len(1, await core.nodes('risk:vulnerable#test :node -> * +it:prod:softver +#test'))
704
+
705
+ # migrate guids - node existence not required
706
+
707
+ nodes = await core.nodes('''
708
+ [ risk:hasvuln=$guid01
709
+ :software=$lib.guid()
710
+ :vuln=$lib.guid()
711
+ ]
712
+ $n=$node -> { yield $lib.model.migration.s.riskHasVulnToVulnerable($n) }
713
+ ''', opts=opts)
714
+ self.len(1, nodes)
715
+ self.eq(guid01, nodes[0].ndef[1])
716
+ self.nn(nodes[0].get('node'))
717
+ self.nn(nodes[0].get('vuln'))
718
+
719
+ # multi-prop - unique guids by prop
720
+
721
+ nodes = await core.nodes('''
722
+ [ risk:hasvuln=$guid02
723
+ :hardware={[ it:prod:hardware=* ]}
724
+ :host={[ it:host=* ]}
725
+ :item={[ mat:item=* ]}
726
+ :org={[ ou:org=* ]}
727
+ :person={[ ps:person=* ]}
728
+ :place={[ geo:place=* ]}
729
+ :software={ it:prod:softver#test }
730
+ :spec={[ mat:spec=* ]}
731
+ :vuln={ risk:vuln#test }
732
+ +#test2
733
+ ]
734
+ $n=$node -> { yield $lib.model.migration.s.riskHasVulnToVulnerable($n) }
735
+ ''', opts=opts)
736
+ self.len(8, nodes)
737
+ self.false(any(n.ndef[1] == guid02 for n in nodes))
738
+ self.true(all(n.hasTag('test2') for n in nodes))
739
+ nodes.sort(key=lambda n: n.get('node'))
740
+ self.eq(
741
+ ['geo:place', 'it:host', 'it:prod:hardware', 'it:prod:softver',
742
+ 'mat:item', 'mat:spec', 'ou:org', 'ps:person'],
743
+ [n.get('node')[0] for n in nodes]
744
+ )
745
+
746
+ self.len(2, await core.nodes('it:prod:softver#test -> risk:vulnerable +{ :vuln -> risk:vuln +#test }'))
747
+
748
+ # nodata
749
+
750
+ self.len(1, await core.nodes('risk:vulnerable=$guid00 $node.data.pop(baz)', opts=opts))
751
+
752
+ nodes = await core.nodes('''
753
+ risk:hasvuln=$guid00 $n=$node
754
+ -> { yield $lib.model.migration.s.riskHasVulnToVulnerable($n, nodata=$lib.true) }
755
+ ''', opts=opts)
756
+ self.len(1, nodes)
757
+ self.none(await nodes[0].getData('baz'))
758
+
759
+ # no-ops
760
+
761
+ self.len(0, await core.nodes('''
762
+ [ risk:hasvuln=* ]
763
+ $n=$node -> { yield $lib.model.migration.s.riskHasVulnToVulnerable($n) }
764
+ '''))
765
+
766
+ self.len(0, await core.nodes('''
767
+ [ risk:hasvuln=* :vuln={[ risk:vuln=* ]} ]
768
+ $n=$node -> { yield $lib.model.migration.s.riskHasVulnToVulnerable($n) }
769
+ '''))
770
+
771
+ self.len(0, await core.nodes('''
772
+ [ risk:hasvuln=* :host={[ it:host=* ]} ]
773
+ $n=$node -> { yield $lib.model.migration.s.riskHasVulnToVulnerable($n) }
774
+ '''))
775
+
776
+ # perms
777
+
778
+ lowuser = await core.auth.addUser('low')
779
+ aslow = {'user': lowuser.iden}
780
+
781
+ await lowuser.addRule((True, ('node', 'tag', 'add')))
782
+
783
+ await core.nodes('''
784
+ [ risk:hasvuln=*
785
+ :vuln={[ risk:vuln=* ]}
786
+ :host={[ it:host=* ]}
787
+ .seen=2010
788
+ +#test.low
789
+ ]
790
+ ''')
791
+
792
+ scmd = '''
793
+ risk:hasvuln#test.low $n=$node
794
+ -> {
795
+ yield $lib.model.migration.s.riskHasVulnToVulnerable($n)
796
+ }
797
+ '''
798
+
799
+ with self.raises(s_exc.AuthDeny) as ectx:
800
+ await core.nodes(scmd, opts=aslow)
801
+ self.eq(perm := 'node.add.risk:vulnerable', ectx.exception.errinfo['perm'])
802
+ await lowuser.addRule((True, perm.split('.')))
803
+
804
+ with self.raises(s_exc.AuthDeny) as ectx:
805
+ await core.nodes(scmd, opts=aslow)
806
+ self.eq(perm := 'node.prop.set.risk:vulnerable.vuln', ectx.exception.errinfo['perm'])
807
+ await lowuser.addRule((True, perm.split('.')))
808
+
809
+ with self.raises(s_exc.AuthDeny) as ectx:
810
+ await core.nodes(scmd, opts=aslow)
811
+ self.eq(perm := 'node.prop.set.risk:vulnerable.node', ectx.exception.errinfo['perm'])
812
+ await lowuser.addRule((True, perm.split('.')))
813
+
814
+ with self.raises(s_exc.AuthDeny) as ectx:
815
+ await core.nodes(scmd, opts=aslow)
816
+ self.eq(perm := 'node.prop.set.risk:vulnerable..seen', ectx.exception.errinfo['perm'])
817
+ await lowuser.addRule((True, perm.split('.', maxsplit=4)))
818
+
819
+ self.len(1, await core.nodes(scmd, opts=aslow))
820
+
821
+ # bad inputs
822
+
823
+ with self.raises(s_exc.BadArg) as ectx:
824
+ await core.nodes('[ it:host=* ] $lib.model.migration.s.riskHasVulnToVulnerable($node)')
825
+ self.isin('only accepts risk:hasvuln nodes', ectx.exception.errinfo['mesg'])
826
+
827
+ with self.raises(s_exc.BadArg) as ectx:
828
+ await core.nodes('$lib.model.migration.s.riskHasVulnToVulnerable(newp)')
829
+ self.isin('must be a node', ectx.exception.errinfo['mesg'])