synapse 2.167.0__py311-none-any.whl → 2.169.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.

@@ -0,0 +1,161 @@
1
+ import synapse.lib.module as s_module
2
+
3
+ class PlanModule(s_module.CoreModule):
4
+
5
+ def getModelDefs(self):
6
+ return (('plan', {
7
+ 'types': (
8
+ ('plan:system', ('guid', {}), {
9
+ 'doc': 'A planning or behavioral analysis system that defines phases and procedures.'}),
10
+
11
+ ('plan:phase', ('guid', {}), {
12
+ 'doc': 'A phase within a planning system which may be used to group steps within a procedure.'}),
13
+
14
+ ('plan:procedure', ('guid', {}), {
15
+ 'doc': 'A procedure consisting of steps.'}),
16
+
17
+ ('plan:procedure:type:taxonomy', ('taxonomy', {}), {
18
+ 'interfaces': ('meta:taxonomy',),
19
+ 'doc': 'A taxonomy of procedure types.'}),
20
+
21
+ ('plan:procedure:variable', ('guid', {}), {
22
+ 'doc': 'A variable used by a procedure.'}),
23
+
24
+ ('plan:procedure:step', ('guid', {}), {
25
+ 'doc': 'A step within a procedure.'}),
26
+
27
+ ('plan:procedure:link', ('guid', {}), {
28
+ 'doc': 'A link between steps in a procedure.'}),
29
+ ),
30
+
31
+ 'forms': (
32
+ ('plan:system', {}, (
33
+
34
+ ('name', ('str', {'lower': True, 'onespace': True}), {
35
+ 'ex': 'mitre att&ck flow',
36
+ 'doc': 'The name of the planning system.'}),
37
+
38
+ ('summary', ('str', {}), {
39
+ 'disp': {'hint': 'text'},
40
+ 'doc': 'A summary of the purpose and use case for the planning system.'}),
41
+
42
+ ('author', ('ps:contact', {}), {
43
+ 'doc': 'The contact of the person or organization which authored the system.'}),
44
+
45
+ ('created', ('time', {}), {
46
+ 'doc': 'The time the planning system was first created.'}),
47
+
48
+ ('updated', ('time', {}), {
49
+ 'doc': 'The time the planning system was last updated.'}),
50
+
51
+ ('version', ('it:semver', {}), {
52
+ 'doc': 'The version of the planning system.'}),
53
+
54
+ ('url', ('inet:url', {}), {
55
+ 'doc': 'The primary URL which documents the planning system.'}),
56
+ )),
57
+ ('plan:phase', {}, (
58
+ ('title', ('str', {}), {
59
+ 'ex': 'Reconnaissance Phase',
60
+ 'doc': 'The title of the phase.'}),
61
+
62
+ ('summary', ('str', {}), {
63
+ 'disp': {'hint': 'text'},
64
+ 'doc': 'A summary of the definition of the phase.'}),
65
+
66
+ ('index', ('int', {}), {
67
+ 'doc': 'The index of this phase within the phases of the system.'}),
68
+
69
+ ('url', ('inet:url', {}), {
70
+ 'doc': 'A URL which links to the full documentation about the phase.'}),
71
+
72
+ ('system', ('plan:system', {}), {
73
+ 'doc': 'The planning system which defines this phase.'}),
74
+ )),
75
+ ('plan:procedure:type:taxonomy', {}, ()),
76
+ ('plan:procedure', {}, (
77
+
78
+ ('title', ('str', {}), {
79
+ 'ex': 'Network Reconnaissance Procedure',
80
+ 'doc': 'The name of the procedure.'}),
81
+
82
+ ('summary', ('str', {}), {
83
+ 'disp': {'hint': 'text'},
84
+ 'doc': 'A summary of the purpose and use cases for the procedure.'}),
85
+
86
+ ('author', ('ps:contact', {}), {
87
+ 'doc': 'The contact of the person or organization which authored the procedure.'}),
88
+
89
+ ('created', ('time', {}), {
90
+ 'doc': 'The time the procedure was created.'}),
91
+
92
+ ('updated', ('time', {}), {
93
+ 'doc': 'The time the procedure was last updated.'}),
94
+
95
+ ('version', ('it:semver', {}), {
96
+ 'doc': 'The version of the procedure.'}),
97
+
98
+ ('system', ('plan:system', {}), {
99
+ 'doc': 'The planning system which defines this procedure.'}),
100
+
101
+ ('type', ('plan:procedure:type:taxonomy', {}), {
102
+ 'doc': 'A type classification for the procedure.'}),
103
+
104
+ ('inputs', ('array', {'type': 'plan:procedure:variable', 'uniq': True, 'sorted': True}), {
105
+ 'doc': 'An array of inputs required to execute the procedure.'}),
106
+
107
+ ('firststep', ('plan:procedure:step', {}), {
108
+ 'doc': 'The first step in the procedure.'}),
109
+ )),
110
+ ('plan:procedure:variable', {}, (
111
+
112
+ ('name', ('str', {}), {
113
+ 'doc': 'The name of the variable.'}),
114
+
115
+ ('type', ('str', {}), {
116
+ 'doc': 'The type for the input. Types are specific to the planning system.'}),
117
+
118
+ ('default', ('data', {}), {
119
+ 'doc': 'The optional default value if the procedure is invoked without the input.'}),
120
+
121
+ ('procedure', ('plan:procedure', {}), {
122
+ 'doc': 'The procedure which defines the variable.'}),
123
+ )),
124
+ ('plan:procedure:step', {}, (
125
+
126
+ ('phase', ('plan:phase', {}), {
127
+ 'doc': 'The phase that the step belongs within.'}),
128
+
129
+ ('procedure', ('plan:procedure', {}), {
130
+ 'doc': 'The procedure which defines the step.'}),
131
+
132
+ ('title', ('str', {}), {
133
+ 'ex': 'Scan the IPv4 address range for open ports',
134
+ 'doc': 'The title of the step.'}),
135
+
136
+ ('summary', ('str', {}), {
137
+ 'doc': 'A summary of the tasks executed within the step.'}),
138
+
139
+ ('outputs', ('array', {'type': 'plan:procedure:variable', 'uniq': True, 'sorted': True}), {
140
+ 'doc': 'An array of variables defined in this step.'}),
141
+
142
+ ('techniques', ('array', {'type': 'ou:technique', 'uniq': True, 'sorted': True}), {
143
+ 'doc': 'An array of techniques used when executing this step.'}),
144
+
145
+ ('links', ('array', {'type': 'plan:procedure:link', 'uniq': True}), {
146
+ 'doc': 'An array of links to subsequent steps.'}),
147
+
148
+ )),
149
+ ('plan:procedure:link', {}, (
150
+
151
+ ('condition', ('bool', {}), {
152
+ 'doc': 'Set to true/false if this link is conditional based on a decision step.'}),
153
+
154
+ ('next', ('plan:procedure:step', {}), {
155
+ 'doc': 'The next step in the plan.'}),
156
+
157
+ ('procedure', ('plan:procedure', {}), {
158
+ 'doc': 'The procedure which defines the link.'}),
159
+ )),
160
+ ),
161
+ }),)
@@ -7958,7 +7958,7 @@ class CortexBasicTest(s_t_utils.SynTest):
7958
7958
  waiter = core01.stormpool.waiter(1, 'svc:del')
7959
7959
  msgs = await core01.stormlist('aha.pool.svc.del pool00... 01.core...', opts={'mirror': False})
7960
7960
  self.stormHasNoWarnErr(msgs)
7961
- self.stormIsInPrint('AHA service (01.core...) removed from service pool (pool00.loop.vertex.link)', msgs)
7961
+ self.stormIsInPrint('AHA service (01.core.loop.vertex.link) removed from service pool (pool00.loop.vertex.link)', msgs)
7962
7962
 
7963
7963
  # TODO: this wait should not return None
7964
7964
  await waiter.wait(timeout=3)
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import asyncio
2
3
  import hashlib
3
4
  import datetime
@@ -156,7 +157,7 @@ class AgendaTest(s_t_utils.SynTest):
156
157
  newts = ar.nexttime(now)
157
158
  self.eq(newts, datetime.datetime(year=2018, month=12, day=5, hour=7, minute=2, tzinfo=tz.utc).timestamp())
158
159
 
159
- async def test_agenda(self):
160
+ async def test_agenda_base(self):
160
161
  MONO_DELT = 1543827303.0
161
162
  unixtime = datetime.datetime(year=2018, month=12, day=5, hour=7, minute=0, tzinfo=tz.utc).timestamp()
162
163
 
@@ -382,8 +383,21 @@ class AgendaTest(s_t_utils.SynTest):
382
383
  self.true(appt.enabled)
383
384
  self.eq(0, appt.startcount)
384
385
 
385
- unixtime = datetime.datetime(year=2019, month=2, day=13, hour=10, minute=16, tzinfo=tz.utc).timestamp()
386
- self.eq((12, 'bar'), await asyncio.wait_for(core.callStorm('return($lib.queue.gen(visi).pop(wait=$lib.true))'), timeout=5))
386
+ # Ensure structured logging captures the cron iden value
387
+ core.stormlog = True
388
+ with self.getStructuredAsyncLoggerStream('synapse.storm') as stream:
389
+ unixtime = datetime.datetime(year=2019, month=2, day=13, hour=10, minute=16,
390
+ tzinfo=tz.utc).timestamp()
391
+ self.eq((12, 'bar'), await asyncio.wait_for(core.callStorm('return($lib.queue.gen(visi).pop(wait=$lib.true))'), timeout=5))
392
+ core.stormlog = False
393
+
394
+ data = stream.getvalue()
395
+ raw_mesgs = [m for m in data.split('\n') if m]
396
+ msgs = [json.loads(m) for m in raw_mesgs]
397
+ msgs = [m for m in msgs if m['text'] == '$lib.queue.gen(visi).put(bar)']
398
+ self.gt(len(msgs), 0)
399
+ for m in msgs:
400
+ self.eq(m.get('cron'), appt.iden)
387
401
 
388
402
  self.eq(1, appt.startcount)
389
403
 
@@ -1126,7 +1126,7 @@ class AhaTest(s_test.SynTest):
1126
1126
  ready = svcinfo.get('ready')
1127
1127
  online = svcinfo.get('online')
1128
1128
  self.none(online)
1129
- self.true(ready) # Ready is not cleared upon restart
1129
+ self.false(ready) # Ready is cleared upon restart / setting service down.
1130
1130
 
1131
1131
  n = 3
1132
1132
  if len(stack._exit_callbacks) > 0:
@@ -1245,7 +1245,12 @@ class AhaTest(s_test.SynTest):
1245
1245
 
1246
1246
  msgs = await core00.stormlist('aha.pool.svc.del pool00... 00...')
1247
1247
  self.stormHasNoWarnErr(msgs)
1248
- self.stormIsInPrint('AHA service (00...) removed from service pool (pool00.loop.vertex.link)', msgs)
1248
+ self.stormIsInPrint('AHA service (00.loop.vertex.link) removed from service pool (pool00.loop.vertex.link)',
1249
+ msgs)
1250
+
1251
+ msgs = await core00.stormlist('aha.pool.svc.del pool00... 00...')
1252
+ self.stormHasNoWarnErr(msgs)
1253
+ self.stormIsInPrint('Did not remove (00...) from the service pool.', msgs)
1249
1254
 
1250
1255
  await waiter.wait(timeout=3)
1251
1256
  run00 = await (await pool.proxy(timeout=3)).getCellRunId()
@@ -496,52 +496,6 @@ class CellTest(s_t_utils.SynTest):
496
496
  with self.raises(s_exc.AuthDeny):
497
497
  await prox.setCellUser(visiiden)
498
498
 
499
- async def test_cell_hiveboot(self):
500
-
501
- with self.getTestDir() as dirn:
502
-
503
- tree = {
504
- 'kids': {
505
- 'hehe': {'value': 'haha'},
506
- }
507
- }
508
-
509
- bootpath = os.path.join(dirn, 'hiveboot.yaml')
510
-
511
- s_common.yamlsave(tree, bootpath)
512
-
513
- with warnings.catch_warnings(record=True) as warns:
514
- async with self.getTestCell(s_cell.Cell, dirn=dirn) as cell:
515
- self.eq('haha', await cell.hive.get(('hehe',)))
516
-
517
- self.isin('Initial hive config from hiveboot.yaml', str(warns[0].message))
518
-
519
- # test that the file does not load again
520
- tree['kids']['redballoons'] = {'value': 99}
521
- s_common.yamlsave(tree, bootpath)
522
-
523
- async with self.getTestCell(s_cell.Cell, dirn=dirn) as cell:
524
- self.none(await cell.hive.get(('redballoons',)))
525
-
526
- # Do a full hive dump/load
527
- with self.getTestDir() as dirn:
528
- dir0 = s_common.genpath(dirn, 'cell00')
529
- dir1 = s_common.genpath(dirn, 'cell01')
530
- async with self.getTestCell(s_cell.Cell, dirn=dir0, conf={'auth:passwd': 'root'}) as cell00:
531
- await cell00.hive.set(('beeps',), [1, 2, 'three'])
532
-
533
- tree = await cell00.saveHiveTree()
534
- s_common.yamlsave(tree, dir1, 'hiveboot.yaml')
535
- with s_common.genfile(dir1, 'cell.guid') as fd:
536
- _ = fd.write(cell00.iden.encode())
537
-
538
- async with self.getTestCell(s_cell.Cell, dirn=dir1) as cell01:
539
- resp = await cell01.hive.get(('beeps',))
540
- self.isinstance(resp, tuple)
541
- self.eq(resp, (1, 2, 'three'))
542
-
543
- self.eq(cell00.iden, cell01.iden)
544
-
545
499
  async def test_cell_getinfo(self):
546
500
  async with self.getTestCore() as cell:
547
501
  cell.COMMIT = 'mycommit'
@@ -2243,8 +2197,8 @@ class CellTest(s_t_utils.SynTest):
2243
2197
  async with self.getTestCore(dirn=dirn, conf=conf) as core:
2244
2198
  pass
2245
2199
 
2246
- stream.seek(0)
2247
- self.isin('onboot optimization complete!', stream.read())
2200
+ stream.seek(0)
2201
+ self.isin('onboot optimization complete!', stream.read())
2248
2202
 
2249
2203
  stat01 = os.stat(lmdbfile)
2250
2204
  self.ne(stat00.st_ino, stat01.st_ino)
@@ -2266,10 +2220,25 @@ class CellTest(s_t_utils.SynTest):
2266
2220
  async with self.getTestCore(dirn=dirn, conf=conf) as core:
2267
2221
  pass
2268
2222
 
2269
- stream.seek(0)
2270
- buf = stream.read()
2271
- self.notin('onboot optimization complete!', buf)
2272
- self.isin('not on the same volume', buf)
2223
+ stream.seek(0)
2224
+ buf = stream.read()
2225
+ self.notin('onboot optimization complete!', buf)
2226
+ self.isin('not on the same volume', buf)
2227
+
2228
+ # Local backup files are skipped
2229
+ async with self.getTestCore(dirn=dirn) as core:
2230
+ await core.runBackup()
2231
+
2232
+ with self.getAsyncLoggerStream('synapse.lib.cell') as stream:
2233
+
2234
+ conf = {'onboot:optimize': True}
2235
+ async with self.getTestCore(dirn=dirn, conf=conf) as core:
2236
+ pass
2237
+
2238
+ stream.seek(0)
2239
+ buf = stream.read()
2240
+ self.isin('Skipping backup file', buf)
2241
+ self.isin('onboot optimization complete!', buf)
2273
2242
 
2274
2243
  async def test_cell_gc(self):
2275
2244
  async with self.getTestCore() as core:
@@ -139,7 +139,7 @@ Member: 00.cell.loop.vertex.link'''
139
139
  self.len(nevents, await waiter.wait(timeout=12))
140
140
 
141
141
  msgs = await core00.stormlist('aha.svc.list')
142
- self.stormIsInPrint('01.cell.loop.vertex.link false false true', msgs)
142
+ self.stormIsInPrint('01.cell.loop.vertex.link false false false', msgs)
143
143
 
144
144
  # Fake a record
145
145
  await aha.addAhaSvc('00.newp', info={'urlinfo': {'scheme': 'tcp', 'host': '0.0.0.0', 'port': '3030'}},
@@ -281,6 +281,18 @@ $request.reply(206, headers=$headers, body=({"no":"body"}))
281
281
  self.eq(data.get('json'), 'err')
282
282
  self.eq(data.get('path'), 'echo/words/wOw')
283
283
 
284
+ # Storm query logging includes the httpapi iden in structlog data
285
+ core.stormlog = True
286
+ with self.getStructuredAsyncLoggerStream('synapse.storm', 'Executing storm query') as stream:
287
+ resp = await sess.get(url)
288
+ self.eq(resp.status, 200)
289
+ self.true(await stream.wait(timeout=12))
290
+ data = stream.getvalue()
291
+ raw_mesgs = [m for m in data.split('\n') if m]
292
+ msgs = [json.loads(m) for m in raw_mesgs]
293
+ self.eq(msgs[0].get('httpapi'), echoiden)
294
+ core.stormlog = False
295
+
284
296
  # Sad paths on the $request methods
285
297
  q = '''$api = $lib.cortex.httpapi.add(testpath02)
286
298
  $api.methods.get = ${ $request.sendcode(200) $request.sendheaders('beep beep') }
@@ -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,107 @@ 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))
@@ -575,6 +575,32 @@ class StormTypesTest(s_test.SynTest):
575
575
  self.len(1, nodes)
576
576
  self.eq(30, nodes[0].ndef[1])
577
577
 
578
+ # $lib.min / $lib.max behavior with 1 item
579
+ ret = await core.callStorm('$x = ([(1234)]) return ( $lib.min($x) )')
580
+ self.eq(ret, 1234)
581
+
582
+ ret = await core.callStorm('return ( $lib.min(1234) )')
583
+ self.eq(ret, 1234)
584
+
585
+ ret = await core.callStorm('$x = ([(1234)]) return ( $lib.max($x) )')
586
+ self.eq(ret, 1234)
587
+
588
+ ret = await core.callStorm('return ( $lib.max(1234) )')
589
+ self.eq(ret, 1234)
590
+
591
+ # $lib.min / $lib.max behavior with 0 items
592
+ with self.raises(s_exc.StormRuntimeError):
593
+ await core.callStorm('$lib.max()')
594
+
595
+ with self.raises(s_exc.StormRuntimeError):
596
+ await core.callStorm('$l=() $lib.max($l)')
597
+
598
+ with self.raises(s_exc.StormRuntimeError):
599
+ await core.callStorm('$lib.min()')
600
+
601
+ with self.raises(s_exc.StormRuntimeError):
602
+ await core.callStorm('$l=() $lib.min($l)')
603
+
578
604
  nodes = await core.nodes('[ inet:asn=$lib.len(asdf) ]')
579
605
  self.len(1, nodes)
580
606
  self.eq(4, nodes[0].ndef[1])
@@ -369,7 +369,7 @@ class TrigTest(s_t_utils.SynTest):
369
369
 
370
370
  # coverage for migration mode
371
371
  await core.nodes('[inet:fqdn=vertex.link +#foo]') # for additional migration mode trigger tests below
372
- with core.enterMigrationMode():
372
+ async with core.enterMigrationMode():
373
373
  await core.nodes('inet:fqdn=vertex.link [ +#bar -#foo ]')
374
374
 
375
375
  async def test_trigger_delete(self):
@@ -774,7 +774,7 @@ class TrigTest(s_t_utils.SynTest):
774
774
  await core.nodes('trigger.add edge:add --verb r* --n2form test:int --query { [ +#n2 ] }')
775
775
  await core.nodes('trigger.add edge:add --verb no** --form test:int --n2form test:str --query { [ +#both ] }')
776
776
 
777
- with core.enterMigrationMode():
777
+ async with core.enterMigrationMode():
778
778
  nodes = await core.nodes('[test:int=123 +(foo:beep:boop)> { [test:str=neato] }]')
779
779
  self.len(1, nodes)
780
780
  self.notin('foo', nodes[0].tags)
@@ -834,7 +834,7 @@ class TrigTest(s_t_utils.SynTest):
834
834
  await core.nodes('trigger.add edge:del --verb r* --n2form test:int --query { [ +#del.two ] }')
835
835
  await core.nodes('trigger.add edge:del --verb no** --form test:int --n2form test:str --query { [ +#del.all ] }')
836
836
 
837
- with core.enterMigrationMode():
837
+ async with core.enterMigrationMode():
838
838
  nodes = await core.nodes('test:int=123 | [ -(foo:beep:boop)> { test:str=neato } ]')
839
839
  self.len(1, nodes)
840
840
  self.notin('del.none', nodes[0].tags)
@@ -0,0 +1,126 @@
1
+ import synapse.exc as s_exc
2
+ import synapse.common as s_common
3
+ import synapse.tests.utils as s_t_utils
4
+
5
+ class PlanModelTest(s_t_utils.SynTest):
6
+
7
+ async def test_model_planning(self):
8
+
9
+ async with self.getTestCore() as core:
10
+ nodes = await core.nodes('''
11
+ [ plan:system=*
12
+ :name="Woot CNO Planner"
13
+ :author={[ ps:contact=* :name=visi ]}
14
+ :created=20240202
15
+ :updated=20240203
16
+ :version=1.0.0
17
+ :url=https://vertex.link
18
+ ]
19
+ ''')
20
+ self.len(1, nodes)
21
+ self.eq('woot cno planner', nodes[0].get('name'))
22
+ self.eq(1706832000000, nodes[0].get('created'))
23
+ self.eq(1706918400000, nodes[0].get('updated'))
24
+ self.eq(1099511627776, nodes[0].get('version'))
25
+ self.eq('https://vertex.link', nodes[0].get('url'))
26
+
27
+ self.len(1, await core.nodes('plan:system :author -> ps:contact +:name=visi'))
28
+
29
+ nodes = await core.nodes('''
30
+ [ plan:phase=*
31
+ :system={ plan:system:name="Woot CNO Planner"}
32
+ :title="Recon"
33
+ :summary="Do some recon."
34
+ :index=17
35
+ :url=https://vertex.link/recon
36
+ ]
37
+ ''')
38
+
39
+ self.len(1, nodes)
40
+ self.eq('Recon', nodes[0].get('title'))
41
+ self.eq('Do some recon.', nodes[0].get('summary'))
42
+ self.eq(17, nodes[0].get('index'))
43
+ self.eq('https://vertex.link/recon', nodes[0].get('url'))
44
+
45
+ self.len(1, await core.nodes('plan:phase :system -> plan:system +:name="Woot CNO Planner"'))
46
+
47
+ nodes = await core.nodes('''
48
+ [ plan:procedure=*
49
+ :system={ plan:system:name="Woot CNO Planner"}
50
+ :title="Pwn Some Boxes"
51
+ :summary="Yoink."
52
+ :author={ ps:contact:name=visi }
53
+ :created=20240202
54
+ :updated=20240203
55
+ :version=1.0.0
56
+ :type=cno.offense
57
+ :system={ plan:system:name="Woot CNO Planner" }
58
+ ]
59
+
60
+ $guid = $node.value()
61
+
62
+ [
63
+ :inputs={[ plan:procedure:variable=*
64
+ :name=network
65
+ :type=cidr
66
+ :default=127.0.0.0/24
67
+ :procedure=$guid
68
+ ]}
69
+
70
+ :firststep={[ plan:procedure:step=*
71
+ :title="Are there vulnerable services?"
72
+ :summary="Scan the target network and identify available services."
73
+ :procedure=$guid
74
+ :phase={ plan:phase:title=Recon }
75
+ :outputs={[ plan:procedure:variable=* :name=services ]}
76
+ :techniques={[ ou:technique=* :name=netscan ]}
77
+
78
+ :links={[ plan:procedure:link=*
79
+ :condition=(true)
80
+ :procedure=$guid
81
+ :next={[ plan:procedure:step=*
82
+ :title="Exploit Services"
83
+ :summary="Gank that stuff."
84
+ :procedure=$guid
85
+ :outputs={[ plan:procedure:variable=* :name=shellz ]}
86
+ ]}
87
+
88
+ ]}
89
+ ]}
90
+ ]
91
+ ''')
92
+
93
+ self.len(1, nodes)
94
+ self.eq('Pwn Some Boxes', nodes[0].get('title'))
95
+ self.eq('Yoink.', nodes[0].get('summary'))
96
+ self.nn(nodes[0].get('author'))
97
+ self.eq(1706832000000, nodes[0].get('created'))
98
+ self.eq(1706918400000, nodes[0].get('updated'))
99
+ self.eq(1099511627776, nodes[0].get('version'))
100
+
101
+ self.len(1, await core.nodes('plan:procedure :type -> plan:procedure:type:taxonomy'))
102
+ self.len(1, await core.nodes('plan:procedure :system -> plan:system +:name="Woot CNO Planner"'))
103
+ self.len(1, await core.nodes('plan:procedure :firststep -> plan:procedure:step -> plan:procedure:link'))
104
+
105
+ nodes = await core.nodes('plan:procedure :inputs -> plan:procedure:variable')
106
+ self.len(1, nodes)
107
+ self.eq('network', nodes[0].get('name'))
108
+ self.eq('cidr', nodes[0].get('type'))
109
+ self.eq('127.0.0.0/24', nodes[0].get('default'))
110
+ self.nn(nodes[0].get('procedure'))
111
+
112
+ nodes = await core.nodes('plan:procedure :firststep -> plan:procedure:step')
113
+ self.len(1, nodes)
114
+ self.eq('Are there vulnerable services?', nodes[0].get('title'))
115
+ self.eq('Scan the target network and identify available services.', nodes[0].get('summary'))
116
+ self.nn(nodes[0].get('procedure'))
117
+
118
+ self.len(1, await core.nodes('plan:procedure :firststep -> plan:procedure:step -> plan:phase'))
119
+ self.len(1, await core.nodes('plan:procedure :firststep -> plan:procedure:step :techniques -> ou:technique'))
120
+ self.len(1, await core.nodes('plan:procedure :firststep -> plan:procedure:step :outputs -> plan:procedure:variable'))
121
+
122
+ nodes = await core.nodes('plan:procedure :firststep -> plan:procedure:step -> plan:procedure:link')
123
+ self.len(1, nodes)
124
+ self.eq(True, nodes[0].get('condition'))
125
+ self.nn(nodes[0].get('next'))
126
+ self.nn(nodes[0].get('procedure'))