synapse 2.186.0__py311-none-any.whl → 2.188.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 (58) hide show
  1. synapse/cortex.py +133 -9
  2. synapse/datamodel.py +20 -4
  3. synapse/exc.py +14 -1
  4. synapse/lib/ast.py +6 -4
  5. synapse/lib/auth.py +9 -0
  6. synapse/lib/hive.py +1 -1
  7. synapse/lib/httpapi.py +2 -1
  8. synapse/lib/modelrev.py +771 -11
  9. synapse/lib/nexus.py +6 -0
  10. synapse/lib/node.py +5 -3
  11. synapse/lib/scrape.py +18 -104
  12. synapse/lib/spooled.py +26 -3
  13. synapse/lib/storm.py +51 -28
  14. synapse/lib/stormlib/model.py +320 -250
  15. synapse/lib/stormlib/modelext.py +31 -0
  16. synapse/lib/stormlib/scrape.py +1 -4
  17. synapse/lib/stormtypes.py +53 -11
  18. synapse/lib/version.py +2 -2
  19. synapse/lib/view.py +9 -3
  20. synapse/models/base.py +27 -0
  21. synapse/models/files.py +22 -0
  22. synapse/models/inet.py +49 -4
  23. synapse/models/infotech.py +49 -22
  24. synapse/models/orgs.py +64 -2
  25. synapse/models/proj.py +1 -6
  26. synapse/models/risk.py +65 -0
  27. synapse/tests/test_cortex.py +21 -0
  28. synapse/tests/test_lib_agenda.py +13 -0
  29. synapse/tests/test_lib_auth.py +15 -0
  30. synapse/tests/test_lib_cell.py +2 -1
  31. synapse/tests/test_lib_httpapi.py +6 -0
  32. synapse/tests/test_lib_modelrev.py +918 -379
  33. synapse/tests/test_lib_nexus.py +26 -0
  34. synapse/tests/test_lib_scrape.py +14 -6
  35. synapse/tests/test_lib_spooled.py +34 -0
  36. synapse/tests/test_lib_storm.py +48 -0
  37. synapse/tests/test_lib_stormlib_model.py +0 -270
  38. synapse/tests/test_lib_stormlib_modelext.py +76 -1
  39. synapse/tests/test_lib_stormlib_scrape.py +0 -8
  40. synapse/tests/test_lib_stormtypes.py +12 -1
  41. synapse/tests/test_lib_trigger.py +8 -0
  42. synapse/tests/test_lib_view.py +24 -0
  43. synapse/tests/test_model_base.py +11 -0
  44. synapse/tests/test_model_files.py +19 -0
  45. synapse/tests/test_model_inet.py +33 -0
  46. synapse/tests/test_model_infotech.py +14 -11
  47. synapse/tests/test_model_orgs.py +39 -0
  48. synapse/tests/test_model_proj.py +11 -1
  49. synapse/tests/test_model_risk.py +32 -0
  50. synapse/tools/changelog.py +11 -3
  51. {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/METADATA +1 -1
  52. {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/RECORD +55 -58
  53. synapse/assets/__init__.py +0 -35
  54. synapse/assets/storm/migrations/model-0.2.28.storm +0 -355
  55. synapse/tests/test_assets.py +0 -25
  56. {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/LICENSE +0 -0
  57. {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/WHEEL +0 -0
  58. {synapse-2.186.0.dist-info → synapse-2.188.0.dist-info}/top_level.txt +0 -0
@@ -2,12 +2,11 @@ import synapse.exc as s_exc
2
2
  import synapse.common as s_common
3
3
 
4
4
  import synapse.lib.node as s_node
5
+ import synapse.lib.time as s_time
5
6
  import synapse.lib.cache as s_cache
6
7
  import synapse.lib.layer as s_layer
7
8
  import synapse.lib.stormtypes as s_stormtypes
8
9
 
9
- import synapse.models.infotech as s_infotech
10
-
11
10
  RISK_HASVULN_VULNPROPS = (
12
11
  'hardware',
13
12
  'host',
@@ -818,8 +817,6 @@ class LibModelMigration(s_stormtypes.Lib, MigrationEditorMixin):
818
817
  'copyEdges': self._methCopyEdges,
819
818
  'copyTags': self._methCopyTags,
820
819
  'copyExtProps': self._methCopyExtProps,
821
- 'liftByPropValuNoNorm': self._methLiftByPropValuNoNorm,
822
- 'setNodePropValuNoNorm': self._methSetNodePropValuNoNorm,
823
820
  }
824
821
 
825
822
  async def _methCopyData(self, src, dst, overwrite=False):
@@ -876,125 +873,12 @@ class LibModelMigration(s_stormtypes.Lib, MigrationEditorMixin):
876
873
  proto = editor.loadNode(dst)
877
874
  await self.copyExtProps(src, proto)
878
875
 
879
- async def _methLiftByPropValuNoNorm(self, formname, propname, valu, cmpr='=', reverse=False):
880
- '''
881
- No storm docs for this on purpose. It is restricted for use during model migrations only.
882
- '''
883
- formname = await s_stormtypes.tostr(formname)
884
- propname = await s_stormtypes.tostr(propname)
885
- valu = await s_stormtypes.toprim(valu)
886
-
887
- prop = self.runt.snap.core.model.prop(f'{formname}:{propname}')
888
- if prop is None:
889
- mesg = f'Could not find prop: {formname}:{propname}'
890
- raise s_exc.NoSuchProp(mesg=mesg, formname=formname, propname=propname)
891
-
892
- if not self.runt.snap.core.migration:
893
- mesg = '$lib.model.migration.liftByPropValuNoNorm() is restricted to model migrations only.'
894
- raise s_exc.AuthDeny(mesg=mesg, user=self.runt.user.iden, username=self.runt.user.name)
895
-
896
- stortype = prop.type.stortype
897
-
898
- # Normally we'd call proptype.getStorCmprs() here to get the cmprvals
899
- # but getStorCmprs() calls norm() which we're trying to avoid so build
900
- # cmprvals manually here.
901
-
902
- if prop.type.isarray:
903
- stortype &= (~s_layer.STOR_FLAG_ARRAY)
904
- liftfunc = self.runt.snap.wlyr.liftByPropArray
905
- else:
906
- liftfunc = self.runt.snap.wlyr.liftByPropValu
907
-
908
- cmprvals = ((cmpr, valu, stortype),)
909
-
910
- layriden = self.runt.snap.wlyr.iden
911
- async for _, buid, sode in liftfunc(formname, propname, cmprvals, reverse=reverse):
912
- yield await self.runt.snap._joinStorNode(buid, {layriden: sode})
913
-
914
- async def _methSetNodePropValuNoNorm(self, n, propname, valu):
915
- '''
916
- No storm docs for this on purpose. It is restricted for use during model migrations only.
917
- '''
918
-
919
- # NB: I'm sure there are all kinds of edges cases that this function doesn't account for. At the time of it's
920
- # creation, this was intended to be used to update array properties with bad it:sec:cpe values in them. It works
921
- # for that use case (see model migration 0.2.28). Any additional use of this function should perform heavy
922
- # testing.
923
-
924
- if not isinstance(n, s_node.Node):
925
- raise s_exc.BadArg(mesg='$lib.model.migration.setNodePropValuNoNorm() argument must be a node.')
926
-
927
- if not self.runt.snap.core.migration:
928
- mesg = '$lib.model.migration.setNodePropValuNoNorm() is restricted to model migrations only.'
929
- raise s_exc.AuthDeny(mesg=mesg, user=self.runt.user.iden, username=self.runt.user.name)
930
-
931
- propname = await s_stormtypes.tostr(propname)
932
- valu = await s_stormtypes.toprim(valu)
933
-
934
- async with self.runt.snap.getNodeEditor(n) as proto:
935
- await proto.set(propname, valu, norminfo={})
936
-
937
- return n
938
-
939
876
  @s_stormtypes.registry.registerLib
940
877
  class LibModelMigrations(s_stormtypes.Lib, MigrationEditorMixin):
941
878
  '''
942
879
  A Storm library for selectively migrating nodes in the current view.
943
880
  '''
944
881
  _storm_locals = (
945
- {'name': 'itSecCpe_2_170_0',
946
- 'desc': '''
947
- Versions of Synapse prior to v2.169.0 did not correctly parse and
948
- convert CPE strings from 2.2 -> 2.3 or 2.3 -> 2.2. This migration
949
- attempts to re-normalize `it:sec:cpe` nodes that may be fixable.
950
-
951
- NOTE: It is highly recommended to test the `it:sec:cpe` migrations
952
- in a fork first and confirm the migration was successful without any
953
- issues. Then run the migration in view deporder to migrate the
954
- entire cortex. E.g.::
955
-
956
- for $view in $lib.view.list(deporder=$lib.true) {
957
- view.exec $view.iden {
958
- for $n in $lib.layer.get().liftByProp(it:sec:cpe) {
959
- $lib.model.migration.s.itSecCpe_2_170_0($n)
960
- }
961
- }
962
- }
963
-
964
- Upon completion of the migration, nodedata will contain a
965
- `migration.s.itSecCpe_2_170_0` dict with information about the
966
- migration status. This dict may contain the following:
967
-
968
- - `status`: (required str) "success" or "failed"
969
- - `reason`: (optional str) if "status" is "failed", this key will
970
- explain why the migration failed.
971
- - `valu`: (optional str) if this key is present, it will contain
972
- an updated CPE2.3 string since the primary property cannot be
973
- changed.
974
- - `updated`: (optional list[str]) A list of properties that were
975
- updated by the migration.
976
-
977
- Failed or incorrect migrations may be helped by updating the :v2_2
978
- property to be a valid CPE2.2 string and then re-running the
979
- migration with `force=$lib.true`. If the primary property (CPE2.3)
980
- is valid but incorrect, users may update the :v2_2 property and then
981
- run the migration with `prefer_v22=$lib.true` to make the migration
982
- use the `:v2_2` string instead of the primary property for the
983
- migration process.
984
- ''',
985
- 'type': {'type': 'function', '_funcname': '_itSecCpe_2_170_0',
986
- 'args': (
987
- {'name': 'n', 'type': 'node', 'desc': 'The it:sec:cpe node to migrate.'},
988
- {'name': 'prefer_v22', 'type': 'bool', 'default': False,
989
- 'desc': '''
990
- Try to renormalize using the :v2_2 prop instead of the
991
- primary property. This can be especially useful when the
992
- primary property is a valid but incorrect CPE string.
993
- '''},
994
- {'name': 'force', 'type': 'bool', 'default': False,
995
- 'desc': 'Perform fixups even if the primary property and :v2_2 are valid.'},
996
- ),
997
- 'returns': {'type': 'boolean', 'desc': 'Boolean indicating if the migration was successful.'}}},
998
882
  {'name': 'riskHasVulnToVulnerable', 'desc': '''
999
883
  Create a risk:vulnerable node from the provided risk:hasvuln node.
1000
884
 
@@ -1076,142 +960,9 @@ class LibModelMigrations(s_stormtypes.Lib, MigrationEditorMixin):
1076
960
 
1077
961
  def getObjLocals(self):
1078
962
  return {
1079
- 'itSecCpe_2_170_0': self._itSecCpe_2_170_0,
1080
- 'itSecCpe_2_170_0_internal': self._itSecCpe_2_170_0_internal,
1081
963
  'riskHasVulnToVulnerable': self._riskHasVulnToVulnerable,
1082
964
  }
1083
965
 
1084
- async def _itSecCpe_2_170_0(self, n, prefer_v22=False, force=False):
1085
- info = await self._itSecCpe_2_170_0_internal(n, prefer_v22=prefer_v22, force=force, set_nodedata=True)
1086
- return info.get('status') == 'success'
1087
-
1088
- async def _itSecCpe_2_170_0_internal(self, n, prefer_v22=False, force=False, set_nodedata=False):
1089
-
1090
- if not isinstance(n, s_node.Node):
1091
- raise s_exc.BadArg(mesg='$lib.model.migration.s.itSecCpe_2_170_0() argument must be a node.')
1092
-
1093
- if n.form.name != 'it:sec:cpe':
1094
- raise s_exc.BadArg(f'itSecCpeFix only accepts it:sec:cpe nodes, not {n.form.name}')
1095
-
1096
- prefer_v22 = await s_stormtypes.tobool(prefer_v22)
1097
- force = await s_stormtypes.tobool(force)
1098
-
1099
- layr = self.runt.snap.wlyr
1100
- # We only need to check :v2_2 since that's the only property that's
1101
- # writable. Everthing else is readonly. And we can do it here once
1102
- # instead of in the loop below which will cause a perf hit.
1103
- self.runt.confirmPropSet(n.form.prop('v2_2'), layriden=layr.iden)
1104
-
1105
- curv = n.repr()
1106
- reprvalu = f'it:sec:cpe={curv}'
1107
-
1108
- nodedata = await n.getData('migration.s.itSecCpe_2_170_0', {})
1109
- if nodedata.get('status') == 'success' and not force:
1110
- if self.runt.debug:
1111
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): Node already migrated.'
1112
- await self.runt.printf(mesg)
1113
- return nodedata
1114
-
1115
- modl = self.runt.model.type('it:sec:cpe')
1116
-
1117
- valu23 = None
1118
- valu22 = None
1119
-
1120
- # Check the primary property for validity.
1121
- cpe23 = s_infotech.cpe23_regex.match(curv)
1122
- if cpe23 is not None and cpe23.group() == curv:
1123
- valu23 = curv
1124
-
1125
- # Check the v2_2 property for validity.
1126
- v2_2 = n.props.get('v2_2')
1127
- if v2_2 is not None:
1128
- rgx = s_infotech.cpe22_regex.match(v2_2)
1129
- if rgx is not None and rgx.group() == v2_2:
1130
- valu22 = v2_2
1131
-
1132
- async with self.runt.snap.getNodeEditor(n) as proto:
1133
-
1134
- # If both values are populated, this node is valid
1135
- if valu23 is not None and valu22 is not None and not force:
1136
- if self.runt.debug:
1137
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): Node is valid, no migration necessary.'
1138
- await self.runt.printf(mesg)
1139
-
1140
- nodedata = {'status': 'success'}
1141
- if set_nodedata:
1142
- await proto.setData('migration.s.itSecCpe_2_170_0', nodedata)
1143
-
1144
- return nodedata
1145
-
1146
- if valu23 is None and valu22 is None:
1147
- reason = 'Unable to migrate due to invalid data. Primary property and :v2_2 are both invalid.'
1148
- # Invalid 2.3 string and no/invalid v2_2 prop. Nothing
1149
- # we can do here so log, mark, and go around.
1150
- mesg = f'itSecCpe_2_170_0({reprvalu}): {reason}'
1151
- await self.runt.warn(mesg)
1152
-
1153
- nodedata = {
1154
- 'status': 'failed',
1155
- 'reason': reason,
1156
- }
1157
- if set_nodedata:
1158
- await proto.setData('migration.s.itSecCpe_2_170_0', nodedata)
1159
-
1160
- return nodedata
1161
-
1162
- if prefer_v22:
1163
- valu = valu22 or valu23
1164
- else:
1165
- valu = valu23 or valu22
1166
-
1167
- # Re-normalize the data from the 2.3 or 2.2 string, whichever was valid.
1168
- norm, info = modl.norm(valu)
1169
- subs = info.get('subs')
1170
-
1171
- nodedata = {'status': 'success'}
1172
-
1173
- if norm != curv:
1174
- # The re-normed value is not the same as the current value.
1175
- # Since we can't change the primary property, store the
1176
- # updated value in nodedata.
1177
- if self.runt.debug:
1178
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): Stored updated primary property value to nodedata: {curv} -> {norm}.'
1179
- await self.runt.printf(mesg)
1180
-
1181
- nodedata['valu'] = norm
1182
-
1183
- # Iterate over the existing properties
1184
- for propname, propcurv in n.props.items():
1185
- subscurv = subs.get(propname)
1186
- if subscurv is None:
1187
- continue
1188
-
1189
- if propname == 'v2_2' and isinstance(subscurv, list):
1190
- subscurv = s_infotech.zipCpe22(subscurv)
1191
-
1192
- # Values are the same, go around
1193
- if propcurv == subscurv:
1194
- continue
1195
-
1196
- nodedata.setdefault('updated', [])
1197
- nodedata['updated'].append(propname)
1198
-
1199
- # Update the existing property with the re-normalized property value.
1200
- await proto.set(propname, subscurv, ignore_ro=True)
1201
-
1202
- if set_nodedata:
1203
- await proto.setData('migration.s.itSecCpe_2_170_0', nodedata)
1204
-
1205
- if self.runt.debug:
1206
- if nodedata.get('updated'):
1207
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): Updated properties: {", ".join(nodedata["updated"])}.'
1208
- await self.runt.printf(mesg)
1209
- else:
1210
- mesg = f'DEBUG: itSecCpe_2_170_0({reprvalu}): No property updates required.'
1211
- await self.runt.printf(mesg)
1212
-
1213
- return nodedata
1214
-
1215
966
  async def _riskHasVulnToVulnerable(self, n, nodata=False):
1216
967
 
1217
968
  nodata = await s_stormtypes.tobool(nodata)
@@ -1269,3 +1020,322 @@ class LibModelMigrations(s_stormtypes.Lib, MigrationEditorMixin):
1269
1020
  await self.copyData(n, proto, overwrite=False)
1270
1021
 
1271
1022
  return retidens
1023
+
1024
+ @s_stormtypes.registry.registerLib
1025
+ class LibModelMigrations_0_2_31(s_stormtypes.Lib):
1026
+ '''
1027
+ A Storm library with helper functions for the 0.2.31 model it:sec:cpe migration.
1028
+ '''
1029
+ _storm_locals = (
1030
+ {'name': 'listNodes', 'desc': 'Yield queued nodes.',
1031
+ 'type': {'type': 'function', '_funcname': '_methListNodes',
1032
+ 'args': (
1033
+ {'name': 'form', 'type': 'form', 'default': None,
1034
+ 'desc': 'Only yield entries matching the specified form.'},
1035
+ {'name': 'source', 'type': 'str', 'default': None,
1036
+ 'desc': 'Only yield entries that were seen by the specified source.'},
1037
+ {'name': 'offset', 'type': 'int', 'default': 0,
1038
+ 'desc': 'Skip this many entries.'},
1039
+ {'name': 'size', 'type': 'int', 'default': None,
1040
+ 'desc': 'Only yield up to this many entries.'},
1041
+ ),
1042
+ 'returns': {'name': 'Yields', 'type': 'list',
1043
+ 'desc': 'A tuple of (offset, form, valu, sources) values for the specified node.', }}},
1044
+ {'name': 'printNode', 'desc': 'Print detailed queued node information.',
1045
+ 'type': {'type': 'function', '_funcname': '_methPrintNode',
1046
+ 'args': (
1047
+ {'name': 'offset', 'type': 'into', 'desc': 'The offset of the queued node to print.'},
1048
+ ),
1049
+ 'returns': {'type': 'null'}}},
1050
+ {'name': 'repairNode', 'desc': 'Repair a queued node.',
1051
+ 'type': {'type': 'function', '_funcname': '_methRepairNode',
1052
+ 'args': (
1053
+ {'name': 'offset', 'type': 'str', 'desc': 'The node queue offset to repair.'},
1054
+ {'name': 'newvalu', 'type': 'any', 'desc': 'The new (corrected) node value.'},
1055
+ {'name': 'remove', 'type': 'boolean', 'default': False,
1056
+ 'desc': 'Specify whether to delete the repaired node from the queue.'},
1057
+ ),
1058
+ 'returns': {'type': 'dict', 'desc': 'The queue node information'}}},
1059
+ )
1060
+ _storm_lib_path = ('model', 'migration', 's', 'model_0_2_31')
1061
+
1062
+ def getObjLocals(self):
1063
+ return {
1064
+ 'listNodes': self._methListNodes,
1065
+ 'printNode': self._methPrintNode,
1066
+ 'repairNode': self._methRepairNode,
1067
+ }
1068
+
1069
+ async def _hasCoreQueue(self, name):
1070
+ try:
1071
+ await self.runt.snap.core.getCoreQueue(name)
1072
+ return True
1073
+ except s_exc.NoSuchName:
1074
+ return False
1075
+
1076
+ async def _methListNodes(self, form=None, source=None, offset=0, size=None):
1077
+ form = await s_stormtypes.tostr(form, noneok=True)
1078
+ source = await s_stormtypes.tostr(source, noneok=True)
1079
+ offset = await s_stormtypes.toint(offset)
1080
+ size = await s_stormtypes.toint(size, noneok=True)
1081
+
1082
+ if not await self._hasCoreQueue('model_0_2_31:nodes'):
1083
+ await self.runt.printf('Queue model_0_2_31:nodes not found, no nodes to list.')
1084
+ return
1085
+
1086
+ nodes = self.runt.snap.core.coreQueueGets('model_0_2_31:nodes', offs=offset, cull=False, size=size)
1087
+ async for offs, node in nodes:
1088
+ if form is not None and node['formname'] != form:
1089
+ continue
1090
+
1091
+ if source is not None and source not in node['sources']:
1092
+ continue
1093
+
1094
+ yield (offs, node['formname'], node['formvalu'], node['sources'])
1095
+
1096
+ async def _methPrintNode(self, offset):
1097
+ offset = await s_stormtypes.toint(offset)
1098
+
1099
+ if not await self._hasCoreQueue('model_0_2_31:nodes'):
1100
+ await self.runt.printf('Queue model_0_2_31:nodes not found, no nodes to print.')
1101
+ return
1102
+
1103
+ node = await self.runt.snap.core.coreQueueGet('model_0_2_31:nodes', offs=offset, cull=False)
1104
+ if not node:
1105
+ await self.runt.warn(f'Queued node with offset {offset} not found.')
1106
+ return
1107
+
1108
+ node = node[1]
1109
+
1110
+ await self.runt.printf(f'{node["formname"]}={repr(node["formvalu"])}')
1111
+
1112
+ for layriden, sode in node['sodes'].items():
1113
+ await self.runt.printf(f' layer: {layriden}')
1114
+
1115
+ for propname, propvalu in sode.get('props', {}).items():
1116
+ if propname == '.seen':
1117
+ mintime, maxtime = propvalu[0]
1118
+ mindt = s_time.repr(mintime)
1119
+ maxdt = s_time.repr(maxtime)
1120
+ await self.runt.printf(f' .seen = ({mindt}, {maxdt})')
1121
+ else:
1122
+ await self.runt.printf(f' :{propname} = {propvalu[0]}')
1123
+
1124
+ for tagname, tagvalu in sode.get('tags', {}).items():
1125
+ if tagvalu == (None, None):
1126
+ await self.runt.printf(f' #{tagname}')
1127
+ else:
1128
+ mintime, maxtime = tagvalu
1129
+ mindt = s_time.repr(mintime)
1130
+ maxdt = s_time.repr(maxtime)
1131
+ await self.runt.printf(f' #{tagname} = ({mindt}, {maxdt})')
1132
+
1133
+ for tagprop, tagpropvalu in sode.get('tagprops', {}).items():
1134
+ for prop, valu in tagpropvalu.items():
1135
+ await self.runt.printf(f' #{tagprop}:{prop} = {valu[0]}')
1136
+
1137
+ if sources := node['sources']:
1138
+ await self.runt.printf(f' sources: {sorted(sources)}')
1139
+
1140
+ if noderefs := node['refs']:
1141
+ await self.runt.printf(' refs:')
1142
+
1143
+ for layriden, reflist in noderefs.items():
1144
+ await self.runt.printf(f' layer: {layriden}')
1145
+ for iden, refinfo in reflist:
1146
+ form, prop, *_ = refinfo
1147
+ await self.runt.printf(f' - {form}:{prop} (iden: {iden}')
1148
+
1149
+ n1edges = node['n1edges']
1150
+ n2edges = node['n2edges']
1151
+
1152
+ if n1edges or n2edges:
1153
+ await self.runt.printf(' edges:')
1154
+
1155
+ for layriden, edges in n1edges.items():
1156
+ for verb, iden in edges:
1157
+ await self.runt.printf(f' -({verb})> {iden}')
1158
+
1159
+ for layriden, edges in n2edges.items():
1160
+ for verb, iden, n2form in edges:
1161
+ await self.runt.printf(f' <({verb})- {iden}')
1162
+
1163
+ async def _repairNode(self, offset, newvalu):
1164
+ item = await self.runt.snap.core.coreQueueGet('model_0_2_31:nodes', offset, cull=False)
1165
+ if item is None:
1166
+ await self.runt.warn(f'Queued node with offset {offset} not found.')
1167
+ return False
1168
+
1169
+ node = item[1]
1170
+
1171
+ nodeform = node['formname']
1172
+ form = self.runt.snap.core.model.form(nodeform)
1173
+
1174
+ norm, info = form.type.norm(newvalu)
1175
+
1176
+ buid = s_common.buid((nodeform, norm))
1177
+
1178
+ nodeedits = {}
1179
+
1180
+ for layriden in node['layers']:
1181
+ nodeedits.setdefault(layriden, {})
1182
+
1183
+ layer = self.runt.snap.core.getLayer(layriden)
1184
+ if layer is None: # pragma: no cover
1185
+ await self.runt.warn(f'Layer does not exist to recreate node: {layriden}.')
1186
+ return False
1187
+
1188
+ await self.runt.printf(f'Repairing node at offset {offset} from {node["formvalu"]} -> {norm}')
1189
+
1190
+ # Create the node in the right layers
1191
+ for layriden in node['layers']:
1192
+ nodeedits[layriden][buid] = (
1193
+ buid, nodeform, [
1194
+ (s_layer.EDIT_NODE_ADD, (norm, form.type.stortype), ()),
1195
+ ])
1196
+
1197
+ for propname, propvalu in info.get('subs', {}).items():
1198
+ prop = form.prop(propname)
1199
+ if prop is None:
1200
+ continue
1201
+
1202
+ stortype = prop.type.stortype
1203
+
1204
+ nodeedits[layriden][buid][2].append(
1205
+ (s_layer.EDIT_PROP_SET, (propname, propvalu, None, stortype), ()),
1206
+ )
1207
+
1208
+ for layriden, sode in node['sodes'].items():
1209
+ nodeedits.setdefault(layriden, {})
1210
+ nodeedits[layriden].setdefault(buid, (buid, nodeform, []))
1211
+
1212
+ for propname, propvalu in sode.get('props', {}).items():
1213
+ propvalu, stortype = propvalu
1214
+
1215
+ nodeedits[layriden][buid][2].append(
1216
+ (s_layer.EDIT_PROP_SET, (propname, propvalu, None, stortype), ()),
1217
+ )
1218
+
1219
+ for tagname, tagvalu in sode.get('tags', {}).items():
1220
+ nodeedits[layriden][buid][2].append(
1221
+ (s_layer.EDIT_TAG_SET, (tagname, tagvalu, None), ()),
1222
+ )
1223
+
1224
+ for tagprop, tagpropvalu in sode.get('tagprops', {}).items():
1225
+ for propname, propvalu in tagpropvalu.items():
1226
+ propvalu, stortype = propvalu
1227
+ nodeedits[layriden][buid][2].append(
1228
+ (s_layer.EDIT_TAGPROP_SET, (tagname, propname, propvalu, None, stortype), ()),
1229
+ )
1230
+
1231
+ for layriden, data in node['nodedata'].items():
1232
+ nodeedits.setdefault(layriden, {})
1233
+ nodeedits[layriden].setdefault(buid, (buid, nodeform, []))
1234
+
1235
+ for name, valu in data:
1236
+ nodeedits[layriden][buid][2].append(
1237
+ (s_layer.EDIT_NODEDATA_SET, (name, valu, None), ()),
1238
+ )
1239
+
1240
+ for layriden, edges in node['n1edges'].items():
1241
+ nodeedits.setdefault(layriden, {})
1242
+ nodeedits[layriden].setdefault(buid, (buid, nodeform, []))
1243
+
1244
+ for verb, iden in edges:
1245
+ nodeedits[layriden][buid][2].append(
1246
+ (s_layer.EDIT_EDGE_ADD, (verb, iden), ()),
1247
+ )
1248
+
1249
+ for layriden, edges in node['n2edges'].items():
1250
+ n1iden = s_common.ehex(buid)
1251
+
1252
+ for verb, iden, n2form in edges:
1253
+ n2buid = s_common.uhex(iden)
1254
+
1255
+ nodeedits.setdefault(layriden, {})
1256
+ nodeedits[layriden].setdefault(n2buid, (n2buid, n2form, []))
1257
+
1258
+ nodeedits[layriden][n2buid][2].append(
1259
+ (s_layer.EDIT_EDGE_ADD, (verb, n1iden), ()),
1260
+ )
1261
+
1262
+ for layriden, reflist in node['refs'].items():
1263
+ layer = self.runt.snap.core.getLayer(layriden)
1264
+ if layer is None:
1265
+ continue
1266
+
1267
+ for iden, refinfo in reflist:
1268
+ refform, refprop, reftype, isarray, isro = refinfo
1269
+
1270
+ if isro:
1271
+ continue
1272
+
1273
+ refbuid = s_common.uhex(iden)
1274
+
1275
+ nodeedits.setdefault(layriden, {})
1276
+ nodeedits[layriden].setdefault(refbuid, (refbuid, refform, []))
1277
+
1278
+ if reftype == 'ndef':
1279
+ propvalu = (nodeform, norm)
1280
+ else:
1281
+ propvalu = norm
1282
+
1283
+ stortype = self.runt.snap.core.model.type(reftype).stortype
1284
+
1285
+ if isarray:
1286
+
1287
+ sode = await layer.getStorNode(refbuid)
1288
+ if not sode:
1289
+ continue
1290
+
1291
+ props = sode.get('props', {})
1292
+
1293
+ curv, _ = props.get(refprop, (None, None))
1294
+ _curv = curv
1295
+
1296
+ if _curv is None:
1297
+ _curv = []
1298
+
1299
+ newv = list(_curv).copy()
1300
+ newv.append(propvalu)
1301
+
1302
+ nodeedits[layriden][refbuid][2].append(
1303
+ (s_layer.EDIT_PROP_SET, (refprop, newv, curv, stortype | s_layer.STOR_FLAG_ARRAY), ()),
1304
+ )
1305
+
1306
+ else:
1307
+
1308
+ nodeedits[layriden][refbuid][2].append(
1309
+ (s_layer.EDIT_PROP_SET, (refprop, propvalu, None, stortype), ()),
1310
+ )
1311
+
1312
+ meta = {'time': s_common.now(), 'user': self.runt.snap.core.auth.rootuser.iden}
1313
+
1314
+ # Process all layer edits as a single batch
1315
+ for layriden, edits in nodeedits.items():
1316
+ layer = self.runt.snap.core.getLayer(layriden)
1317
+ if layer is None: # pragma: no cover
1318
+ continue
1319
+
1320
+ await layer.storNodeEditsNoLift(list(edits.values()), meta)
1321
+
1322
+ return True
1323
+
1324
+ async def _methRepairNode(self, offset, newvalu, remove=False):
1325
+ ok = False
1326
+
1327
+ if not await self._hasCoreQueue('model_0_2_31:nodes'):
1328
+ await self.runt.printf('Queue model_0_2_31:nodes not found, no nodes to repair.')
1329
+ return False
1330
+
1331
+ try:
1332
+ ok = await self._repairNode(offset, newvalu)
1333
+ except s_exc.SynErr as exc: # pragma: no cover
1334
+ mesg = exc.get('mesg')
1335
+ await self.runt.warn(f'Error when restoring node {offset}: {mesg}')
1336
+
1337
+ if ok and remove:
1338
+ await self.runt.printf(f'Removing queued node: {offset}.')
1339
+ await self.runt.snap.core.coreQueuePop('model_0_2_31:nodes', offset)
1340
+
1341
+ return ok
@@ -105,6 +105,21 @@ class LibModelExt(s_stormtypes.Lib):
105
105
  'desc': 'The form of the n2 node. May be "*" or null to specify "any".'},
106
106
  ),
107
107
  'returns': {'type': 'null'}}},
108
+ {'name': 'addType', 'desc': 'Add an extended type definition to the data model.',
109
+ 'type': {'type': 'function', '_funcname': 'addType',
110
+ 'args': (
111
+ {'name': 'typename', 'type': 'str', 'desc': 'The name of the type to add.'},
112
+ {'name': 'basetype', 'type': 'str', 'desc': 'The base type the type is derived from.'},
113
+ {'name': 'typeopts', 'type': 'dict', 'desc': 'A Synapse type opts dictionary.'},
114
+ {'name': 'typeinfo', 'type': 'dict', 'desc': 'A Synapse type info dictionary.'},
115
+ ),
116
+ 'returns': {'type': 'null'}}},
117
+ {'name': 'delType', 'desc': 'Remove an extended type definition from the model.',
118
+ 'type': {'type': 'function', '_funcname': 'delType',
119
+ 'args': (
120
+ {'name': 'typename', 'type': 'str', 'desc': 'The extended type to remove.'},
121
+ ),
122
+ 'returns': {'type': 'null'}}},
108
123
  )
109
124
  _storm_lib_path = ('model', 'ext')
110
125
 
@@ -122,6 +137,8 @@ class LibModelExt(s_stormtypes.Lib):
122
137
  'addExtModel': self.addExtModel,
123
138
  'addEdge': self.addEdge,
124
139
  'delEdge': self.delEdge,
140
+ 'addType': self.addType,
141
+ 'delType': self.delType,
125
142
  }
126
143
 
127
144
  # TODO type docs in the new convention
@@ -210,6 +227,7 @@ class LibModelExt(s_stormtypes.Lib):
210
227
  return await self.runt.snap.core.getExtModel()
211
228
 
212
229
  async def addExtModel(self, model):
230
+ self.runt.reqAdmin()
213
231
  model = await s_stormtypes.toprim(model)
214
232
  return await self.runt.snap.core.addExtModel(model)
215
233
 
@@ -249,3 +267,16 @@ class LibModelExt(s_stormtypes.Lib):
249
267
 
250
268
  s_stormtypes.confirm(('model', 'edge', 'del'))
251
269
  await self.runt.snap.core.delEdge((n1form, verb, n2form))
270
+
271
+ async def addType(self, typename, basetype, typeopts, typeinfo):
272
+ typename = await s_stormtypes.tostr(typename)
273
+ basetype = await s_stormtypes.tostr(basetype)
274
+ typeopts = await s_stormtypes.toprim(typeopts)
275
+ typeinfo = await s_stormtypes.toprim(typeinfo)
276
+ s_stormtypes.confirm(('model', 'type', 'add', typename))
277
+ await self.runt.snap.core.addType(typename, basetype, typeopts, typeinfo)
278
+
279
+ async def delType(self, typename):
280
+ typename = await s_stormtypes.tostr(typename)
281
+ s_stormtypes.confirm(('model', 'type', 'del', typename))
282
+ await self.runt.snap.core.delType(typename)
@@ -155,10 +155,7 @@ class LibScrape(s_stormtypes.Lib):
155
155
  if fangs:
156
156
  _fangs = {src: dst for (src, dst) in fangs}
157
157
  _fangre = s_scrape.genFangRegex(_fangs)
158
- if len(text) < s_scrape.SCRAPE_SPAWN_LENGTH:
159
- scrape_text, offsets = s_scrape.refang_text2(text, re=_fangre, fangs=_fangs)
160
- else:
161
- scrape_text, offsets = await s_coro.semafork(s_scrape.refang_text2, text, re=_fangre, fangs=_fangs)
158
+ scrape_text, offsets = await s_coro.semafork(s_scrape.refang_text2, text, re=_fangre, fangs=_fangs)
162
159
 
163
160
  async for info in s_scrape.genMatchesAsync(scrape_text, regx, opts=opts):
164
161
  valu = info.pop('valu')