synapse 2.169.0__py311-none-any.whl → 2.171.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 (41) hide show
  1. synapse/cortex.py +99 -3
  2. synapse/datamodel.py +5 -0
  3. synapse/lib/ast.py +70 -12
  4. synapse/lib/cell.py +76 -7
  5. synapse/lib/layer.py +75 -6
  6. synapse/lib/lmdbslab.py +17 -0
  7. synapse/lib/node.py +7 -0
  8. synapse/lib/snap.py +22 -4
  9. synapse/lib/storm.py +1 -1
  10. synapse/lib/stormlib/cortex.py +1 -1
  11. synapse/lib/stormlib/model.py +339 -40
  12. synapse/lib/stormtypes.py +58 -1
  13. synapse/lib/types.py +36 -1
  14. synapse/lib/version.py +2 -2
  15. synapse/lib/view.py +94 -15
  16. synapse/models/files.py +40 -0
  17. synapse/models/inet.py +8 -4
  18. synapse/models/infotech.py +355 -17
  19. synapse/tests/files/cpedata.json +525034 -0
  20. synapse/tests/test_cortex.py +108 -0
  21. synapse/tests/test_lib_ast.py +66 -0
  22. synapse/tests/test_lib_cell.py +112 -0
  23. synapse/tests/test_lib_layer.py +52 -1
  24. synapse/tests/test_lib_lmdbslab.py +36 -0
  25. synapse/tests/test_lib_scrape.py +72 -71
  26. synapse/tests/test_lib_snap.py +16 -1
  27. synapse/tests/test_lib_storm.py +118 -0
  28. synapse/tests/test_lib_stormlib_cortex.py +15 -0
  29. synapse/tests/test_lib_stormlib_model.py +427 -0
  30. synapse/tests/test_lib_stormtypes.py +147 -15
  31. synapse/tests/test_lib_types.py +21 -0
  32. synapse/tests/test_lib_view.py +77 -0
  33. synapse/tests/test_model_files.py +52 -0
  34. synapse/tests/test_model_inet.py +63 -1
  35. synapse/tests/test_model_infotech.py +187 -26
  36. synapse/tests/utils.py +42 -9
  37. {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/METADATA +1 -1
  38. {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/RECORD +41 -40
  39. {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/LICENSE +0 -0
  40. {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/WHEEL +0 -0
  41. {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/top_level.txt +0 -0
@@ -19,6 +19,8 @@ import synapse.lib.version as s_version
19
19
  import synapse.tests.utils as s_t_utils
20
20
  from synapse.tests.utils import alist
21
21
 
22
+ import synapse.tools.backup as s_tools_backup
23
+
22
24
  class StormTest(s_t_utils.SynTest):
23
25
 
24
26
  async def test_lib_storm_jsonexpr(self):
@@ -2041,6 +2043,90 @@ class StormTest(s_t_utils.SynTest):
2041
2043
 
2042
2044
  self.eq((1, 6), await core.callStorm('return($lib.queue.gen(foo).get(1))'))
2043
2045
 
2046
+ async def test_storm_dmon_query_state(self):
2047
+ with self.getTestDir() as dirn:
2048
+ dirn00 = s_common.gendir(dirn, 'core00')
2049
+ dirn01 = s_common.gendir(dirn, 'core01')
2050
+ dirn02 = s_common.gendir(dirn, 'core02')
2051
+
2052
+ async with self.getTestCore(dirn=dirn00) as core00:
2053
+
2054
+ msgs = await core00.stormlist('[ inet:ipv4=1.2.3.4 ]')
2055
+ self.stormHasNoWarnErr(msgs)
2056
+
2057
+ s_tools_backup.backup(dirn00, dirn01)
2058
+ s_tools_backup.backup(dirn00, dirn02)
2059
+
2060
+ async with self.getTestCore(dirn=dirn00) as core00:
2061
+ conf01 = {'mirror': core00.getLocalUrl()}
2062
+
2063
+ async with self.getTestCore(dirn=dirn01, conf=conf01) as core01:
2064
+
2065
+ conf02 = {'mirror': core01.getLocalUrl()}
2066
+
2067
+ async with self.getTestCore(dirn=dirn02, conf=conf02) as core02:
2068
+
2069
+ await core02.sync()
2070
+
2071
+ nodes = await core01.nodes('inet:ipv4')
2072
+ self.len(1, nodes)
2073
+ self.eq(nodes[0].ndef, ('inet:ipv4', 16909060))
2074
+
2075
+ q = '''
2076
+ $lib.queue.gen(dmonloop)
2077
+ return(
2078
+ $lib.dmon.add(${
2079
+ $queue = $lib.queue.get(dmonloop)
2080
+ while $lib.true {
2081
+ ($offs, $mesg) = $queue.get()
2082
+
2083
+ switch $mesg.0 {
2084
+ "print": { $lib.print($mesg.1) }
2085
+ "warn": { $lib.warn($mesg.1) }
2086
+ "leave": {
2087
+ $lib.print(leaving)
2088
+ break
2089
+ }
2090
+ *: { continue }
2091
+ }
2092
+
2093
+ $queue.cull($offs)
2094
+ }
2095
+ }, name=dmonloop)
2096
+ )
2097
+ '''
2098
+ ddef = await core02.callStorm(q)
2099
+ self.nn(ddef['iden'])
2100
+
2101
+ dmons = await core02.getStormDmons()
2102
+ self.len(1, dmons)
2103
+ self.eq(dmons[0]['iden'], ddef['iden'])
2104
+
2105
+ info = await core02.getStormDmon(ddef['iden'])
2106
+ self.eq(info['iden'], ddef['iden'])
2107
+ self.eq(info['name'], 'dmonloop')
2108
+ self.eq(info['status'], 'running')
2109
+
2110
+ await core02.callStorm('$lib.queue.get(dmonloop).put((print, printfoo))')
2111
+ await core02.callStorm('$lib.queue.get(dmonloop).put((warn, warnfoo))')
2112
+
2113
+ info = await core02.getStormDmon(ddef['iden'])
2114
+ self.eq(info['status'], 'running')
2115
+
2116
+ logs = await core02.getStormDmonLog(ddef['iden'])
2117
+ msgs = [k[1] for k in logs]
2118
+ self.stormIsInPrint('printfoo', msgs)
2119
+ self.stormIsInWarn('warnfoo', msgs)
2120
+
2121
+ await core02.callStorm('$lib.queue.get(dmonloop).put((leave,))')
2122
+
2123
+ info = await core02.getStormDmon(ddef['iden'])
2124
+ self.eq(info['status'], 'sleeping')
2125
+
2126
+ logs = await core02.getStormDmonLog(ddef['iden'])
2127
+ msgs = [k[1] for k in logs]
2128
+ self.stormIsInPrint('leaving', msgs)
2129
+
2044
2130
  async def test_storm_pipe(self):
2045
2131
 
2046
2132
  async with self.getTestCore() as core:
@@ -3098,6 +3184,38 @@ class StormTest(s_t_utils.SynTest):
3098
3184
  nodes = await core.nodes(q, opts={'view': fork, 'vars': {'view': view}})
3099
3185
  self.len(1, nodes)
3100
3186
 
3187
+ async def test_storm_viewexec(self):
3188
+
3189
+ async with self.getTestCore() as core:
3190
+
3191
+ view = await core.callStorm('return( $lib.view.get().iden )')
3192
+ fork = await core.callStorm('return( $lib.view.get().fork().iden )')
3193
+
3194
+ await core.addStormPkg({
3195
+ 'name': 'testpkg',
3196
+ 'version': (0, 0, 1),
3197
+ 'modules': (
3198
+ {'name': 'priv.exec',
3199
+ 'asroot:perms': [['power-ups', 'testpkg']],
3200
+ 'modconf': {'viewiden': fork},
3201
+ 'storm': '''
3202
+ function asroot () {
3203
+ view.exec $modconf.viewiden { $foo=bar } | return($foo)
3204
+ }
3205
+ '''},
3206
+ ),
3207
+ })
3208
+
3209
+ visi = await core.auth.addUser('visi')
3210
+ asvisi = {'user': visi.iden}
3211
+
3212
+ await core.stormlist('auth.user.addrule visi power-ups.testpkg')
3213
+
3214
+ with self.raises(s_exc.AuthDeny):
3215
+ await core.callStorm('return(woot)', opts={'user': visi.iden, 'view': fork})
3216
+
3217
+ self.eq('bar', await core.callStorm('return($lib.import(priv.exec).asroot())', opts=asvisi))
3218
+
3101
3219
  async def test_storm_argv_parser(self):
3102
3220
 
3103
3221
  pars = s_storm.Parser(prog='hehe')
@@ -1280,6 +1280,12 @@ for $i in $values {
1280
1280
  return ( ($api.iden) )'''
1281
1281
  iden06 = await core.callStorm(q)
1282
1282
 
1283
+ # attempt to JSON decode non-JSON request
1284
+ q = '''$api = $lib.cortex.httpapi.add(bad07)
1285
+ $api.methods.get = ${ $foo = $request.json() }
1286
+ return ( ($api.iden) )'''
1287
+ iden07 = await core.callStorm(q)
1288
+
1283
1289
  async with self.getHttpSess(auth=('root', 'root'), port=hport) as sess:
1284
1290
  resp = await sess.get(f'https://localhost:{hport}/api/ext/bad00')
1285
1291
  self.eq(resp.status, 500)
@@ -1329,6 +1335,15 @@ for $i in $values {
1329
1335
  self.eq(resp.status, 201)
1330
1336
  self.eq(await resp.json(), {})
1331
1337
 
1338
+ with self.getAsyncLoggerStream('synapse.lib.httpapi',
1339
+ f'Error executing Extended HTTP API {iden07}: StormRuntimeError') as stream:
1340
+ resp = await sess.get(f'https://localhost:{hport}/api/ext/bad07')
1341
+ self.true(await stream.wait(timeout=6))
1342
+ self.eq(resp.status, 500)
1343
+ data = await resp.json()
1344
+ self.eq(data.get('code'), 'StormRuntimeError')
1345
+ self.isin('Failed to decode request body as JSON: Expecting value', data.get('mesg'))
1346
+
1332
1347
  async def test_cortex_httpapi_dynamic(self):
1333
1348
 
1334
1349
  # API endpoints can be dynamic. Use at your own risk.
@@ -400,3 +400,430 @@ class StormlibModelTest(s_test.SynTest):
400
400
 
401
401
  q = 'test:str=src $n=$node -> { test:str=deny $lib.model.migration.copyTags($n, $node) }'
402
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'])