From 339a0d05ae6dcd9e040d45e60c0fbc6c2fc5bbbe Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Fri, 10 Jan 2025 14:11:57 -0800 Subject: [PATCH 1/3] add alts --- synapse/models/geopol.py | 1 + synapse/models/geospace.py | 1 + synapse/models/infotech.py | 2 ++ synapse/models/orgs.py | 5 +++++ synapse/models/person.py | 8 ++++++++ synapse/models/risk.py | 3 +++ synapse/tests/test_model_geopol.py | 4 ++++ synapse/tests/test_model_geospace.py | 6 ++++++ synapse/tests/test_model_infotech.py | 8 ++++++++ synapse/tests/test_model_orgs.py | 19 +++++++++++++++++-- synapse/tests/test_model_person.py | 22 +++++++++++++++++++++- synapse/tests/test_model_risk.py | 11 +++++++++++ 12 files changed, 87 insertions(+), 3 deletions(-) diff --git a/synapse/models/geopol.py b/synapse/models/geopol.py index 91622dcf66d..4bc4df28ee2 100644 --- a/synapse/models/geopol.py +++ b/synapse/models/geopol.py @@ -69,6 +69,7 @@ def getModelDefs(self): ('tld', ('inet:fqdn', {}), {}), ('name', ('geo:name', {}), { + 'alts': ('names',), 'doc': 'The name of the country.'}), ('names', ('array', {'type': 'geo:name', 'uniq': True, 'sorted': True}), { diff --git a/synapse/models/geospace.py b/synapse/models/geospace.py index b40f8aebca9..92103bb045a 100644 --- a/synapse/models/geospace.py +++ b/synapse/models/geospace.py @@ -495,6 +495,7 @@ def getModelDefs(self): ('geo:place', {}, ( ('name', ('geo:name', {}), { + 'alts': ('names',), 'doc': 'The name of the place.'}), ('type', ('geo:place:taxonomy', {}), { diff --git a/synapse/models/infotech.py b/synapse/models/infotech.py index 3772bbfdbf9..37661f0510c 100644 --- a/synapse/models/infotech.py +++ b/synapse/models/infotech.py @@ -2101,6 +2101,7 @@ def getModelDefs(self): 'doc': 'An ID for the software.'}), ('name', ('it:prod:softname', {}), { + 'alts': ('names',), 'doc': 'Name of the software.', }), ('type', ('it:prod:soft:taxonomy', {}), { @@ -2222,6 +2223,7 @@ def getModelDefs(self): 'doc': 'Deprecated. Please use it:prod:softver:name.', }), ('name', ('it:prod:softname', {}), { + 'alts': ('names',), 'doc': 'Name of the software version.', }), ('names', ('array', {'type': 'it:prod:softname', 'uniq': True, 'sorted': True}), { diff --git a/synapse/models/orgs.py b/synapse/models/orgs.py index a5b9a4bd025..a5f7e0d5f75 100644 --- a/synapse/models/orgs.py +++ b/synapse/models/orgs.py @@ -526,6 +526,7 @@ def getModelDefs(self): ('ou:goal', {}, ( ('name', ('ou:goalname', {}), { + 'alts': ('names',), 'doc': 'A terse name for the goal.'}), ('names', ('array', {'type': 'ou:goalname', 'sorted': True, 'uniq': True}), { @@ -570,6 +571,7 @@ def getModelDefs(self): 'doc': 'The FQDN of the org responsible for the campaign. Used for entity resolution.'}), ('goal', ('ou:goal', {}), { + 'alts': ('goals',), 'doc': 'The assessed primary goal of the campaign.'}), ('slogan', ('lang:phrase', {}), { @@ -585,6 +587,7 @@ def getModelDefs(self): 'doc': 'Records the success/failure status of the campaign if known.'}), ('name', ('ou:campname', {}), { + 'alts': ('names',), 'ex': 'operation overlord', 'doc': 'A terse name of the campaign.'}), @@ -924,6 +927,7 @@ def getModelDefs(self): ('ou:industry', {}, ( ('name', ('ou:industryname', {}), { + 'alts': ('names',), 'doc': 'The name of the industry.'}), ('type', ('ou:industry:type:taxonomy', {}), { @@ -1176,6 +1180,7 @@ def getModelDefs(self): 'doc': 'An array of contacts which sponsored the conference.', }), ('name', ('entity:name', {}), { + 'alts': ('names',), 'doc': 'The full name of the conference.', 'ex': 'defcon 2017'}), diff --git a/synapse/models/person.py b/synapse/models/person.py index 0f45796ec22..1693aa86c1e 100644 --- a/synapse/models/person.py +++ b/synapse/models/person.py @@ -254,6 +254,7 @@ def getModelDefs(self): 'doc': 'The most recent known vitals for the person.', }), ('name', ('ps:name', {}), { + 'alts': ('names',), 'doc': 'The localized name for the person.', }), ('name:sur', ('ps:tokn', {}), { @@ -350,6 +351,7 @@ def getModelDefs(self): 'doc': 'The most recent known vitals for the contact.', }), ('name', ('ps:name', {}), { + 'alts': ('names',), 'doc': 'The person name listed for the contact.'}), ('bio', ('str', {}), { @@ -359,6 +361,7 @@ def getModelDefs(self): 'doc': 'A description of this contact.'}), ('title', ('ou:jobtitle', {}), { + 'alts': ('titles',), 'doc': 'The job/org title listed for this contact.'}), ('titles', ('array', {'type': 'ou:jobtitle', 'sorted': True, 'uniq': True}), { @@ -368,12 +371,14 @@ def getModelDefs(self): 'doc': 'The photo listed for this contact.', }), ('orgname', ('ou:name', {}), { + 'alts': ('orgnames',), 'doc': 'The listed org/company name for this contact.', }), ('orgfqdn', ('inet:fqdn', {}), { 'doc': 'The listed org/company FQDN for this contact.', }), ('user', ('inet:user', {}), { + 'alts': ('users',), 'doc': 'The username or handle for this contact.'}), ('service:accounts', ('array', {'type': 'inet:service:account', 'sorted': True, 'uniq': True}), { @@ -415,6 +420,7 @@ def getModelDefs(self): 'doc': 'The home or main site for this contact.', }), ('email', ('inet:email', {}), { + 'alts': ('emails',), 'doc': 'The main email address for this contact.', }), ('email:work', ('inet:email', {}), { @@ -443,6 +449,7 @@ def getModelDefs(self): 'doc': 'The work phone number for this contact.', }), ('id:number', ('ou:id:number', {}), { + 'alts': ('id:numbers',), 'doc': 'An ID number issued by an org and associated with this contact.', }), ('adid', ('it:adid', {}), { @@ -482,6 +489,7 @@ def getModelDefs(self): }), ('lang', ('lang:language', {}), { + 'alts': ('langs',), 'doc': 'The language specified for the contact.'}), ('langs', ('array', {'type': 'lang:language'}), { diff --git a/synapse/models/risk.py b/synapse/models/risk.py index 347b44420a3..12614cdf07e 100644 --- a/synapse/models/risk.py +++ b/synapse/models/risk.py @@ -310,6 +310,7 @@ def getModelDefs(self): 'doc': "The reporting organization's assessed location of the threat cluster."}), ('org:name', ('ou:name', {}), { + 'alts': ('org:names',), 'ex': 'apt1', 'doc': "The reporting organization's name for the threat cluster."}), @@ -383,6 +384,7 @@ def getModelDefs(self): 'doc': 'The authoritative software family for the tool.'}), ('soft:name', ('it:prod:softname', {}), { + 'alts': ('soft:names',), 'doc': 'The reporting organization\'s name for the tool.'}), ('soft:names', ('array', {'type': 'it:prod:softname', 'uniq': True, 'sorted': True}), { @@ -441,6 +443,7 @@ def getModelDefs(self): ('risk:vuln', {}, ( ('name', ('risk:vulnname', {}), { + 'alts': ('names',), 'doc': 'A user specified name for the vulnerability.'}), ('names', ('array', {'type': 'risk:vulnname', 'sorted': True, 'uniq': True}), { diff --git a/synapse/tests/test_model_geopol.py b/synapse/tests/test_model_geopol.py index 075d1caeb0b..14a63c54138 100644 --- a/synapse/tests/test_model_geopol.py +++ b/synapse/tests/test_model_geopol.py @@ -18,6 +18,7 @@ async def test_geopol_country(self): ] ''') self.len(1, nodes) + node = nodes[0] self.eq('visiland', nodes[0].get('name')) self.eq(('visitopia',), nodes[0].get('names')) self.eq(1640995200000, nodes[0].get('founded')) @@ -29,6 +30,9 @@ async def test_geopol_country(self): self.len(2, await core.nodes('pol:country -> geo:name')) self.len(3, await core.nodes('pol:country -> econ:currency')) + self.len(1, nodes := await core.nodes('[ pol:country=({"name": "visitopia"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + nodes = await core.nodes(''' [ pol:vitals=* :country={pol:country:name=visiland} diff --git a/synapse/tests/test_model_geospace.py b/synapse/tests/test_model_geospace.py index 63078853eb1..571cae71a02 100644 --- a/synapse/tests/test_model_geospace.py +++ b/synapse/tests/test_model_geospace.py @@ -281,6 +281,12 @@ async def test_types_forms(self): nodes = await core.nodes('[ geo:place=(hehe, haha) :names=("Foo Bar ", baz) ] -> geo:name') self.eq(('baz', 'foo bar'), [n.ndef[1] for n in nodes]) + nodes = await core.nodes('geo:place=(hehe, haha)') + node = nodes[0] + + self.len(1, nodes := await core.nodes('[ geo:place=({"name": "baz"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + async def test_eq(self): async with self.getTestCore() as core: diff --git a/synapse/tests/test_model_infotech.py b/synapse/tests/test_model_infotech.py index 38dc5703ac1..1fa4b741e0e 100644 --- a/synapse/tests/test_model_infotech.py +++ b/synapse/tests/test_model_infotech.py @@ -787,6 +787,10 @@ async def test_it_forms_prodsoft(self): self.eq(node.get('url'), url0) self.len(1, await core.nodes('it:prod:soft:name="balloon maker" -> it:prod:soft:taxonomy')) self.len(2, await core.nodes('it:prod:softname="balloon maker" -> it:prod:soft -> it:prod:softname')) + + self.len(1, nodes := await core.nodes('[ it:prod:soft=({"name": "clowns inc"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + # it:prod:softver - this does test a bunch of property related callbacks ver0 = s_common.guid() url1 = 'https://vertex.link/products/balloonmaker/release_101-beta.exe' @@ -820,6 +824,10 @@ async def test_it_forms_prodsoft(self): self.eq(node.get('url'), url1) self.eq(node.get('name'), 'balloonmaker') self.eq(node.get('desc'), 'makes balloons') + + self.len(1, nodes := await core.nodes('[ it:prod:softver=({"name": "clowns inc"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + # callback node creation checks self.len(1, await core.nodes('it:dev:str=V1.0.1-beta+exp.sha.5114f85')) self.len(1, await core.nodes('it:dev:str=amd64')) diff --git a/synapse/tests/test_model_orgs.py b/synapse/tests/test_model_orgs.py index 9e453a3ef96..6de8497aa57 100644 --- a/synapse/tests/test_model_orgs.py +++ b/synapse/tests/test_model_orgs.py @@ -60,6 +60,9 @@ async def test_ou_simple(self): self.eq(node.get('desc'), 'MyDesc') self.eq(node.get('prev'), goal) + self.len(1, nodes := await core.nodes('[ ou:goal=({"name": "foo goal"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + nodes = await core.nodes('[(ou:hasgoal=$valu :stated=$lib.true :window="2019,2020")]', opts={'vars': {'valu': (org0, goal)}}) self.len(1, nodes) @@ -69,12 +72,13 @@ async def test_ou_simple(self): self.eq(node.get('stated'), True) self.eq(node.get('window'), (1546300800000, 1577836800000)) + altgoal = s_common.guid() timeline = s_common.guid() props = { 'org': org0, 'goal': goal, - 'goals': (goal,), + 'goals': (goal, altgoal), 'actors': (acto,), 'camptype': 'get.pizza', 'name': 'MyName', @@ -103,7 +107,7 @@ async def test_ou_simple(self): self.eq(node.get('tag'), 'cno.camp.31337') self.eq(node.get('org'), org0) self.eq(node.get('goal'), goal) - self.eq(node.get('goals'), (goal,)) + self.eq(node.get('goals'), sorted((goal, altgoal))) self.eq(node.get('actors'), (acto,)) self.eq(node.get('name'), 'myname') self.eq(node.get('names'), ('bar', 'foo')) @@ -120,6 +124,10 @@ async def test_ou_simple(self): self.eq(node.get('mitre:attack:campaign'), 'C0011') self.eq(node.get('slogan'), 'for the people') + opts = {'vars': {'altgoal': altgoal}} + self.len(1, nodes := await core.nodes('[ ou:campaign=({"name": "foo", "goal": $altgoal}) ]', opts=opts)) + self.eq(node.ndef, nodes[0].ndef) + self.len(1, await core.nodes(f'ou:campaign={camp} :slogan -> lang:phrase')) nodes = await core.nodes(f'ou:campaign={camp} -> it:mitre:attack:campaign') self.len(1, nodes) @@ -405,6 +413,9 @@ async def test_ou_simple(self): self.eq(node.get('place'), place0) self.eq(node.get('url'), 'http://arrowcon.org/2018') + self.len(1, nodes := await core.nodes('[ ou:conference=({"name": "arrcon18"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + props = { 'arrived': '201803010800', 'departed': '201803021500', @@ -870,6 +881,7 @@ async def test_ou_industry(self): ] ''' nodes = await core.nodes(q) self.len(1, nodes) + node = nodes[0] self.nn(nodes[0].get('reporter')) self.eq('foo bar', nodes[0].get('name')) self.eq('vertex', nodes[0].get('reporter:name')) @@ -884,6 +896,9 @@ async def test_ou_industry(self): self.len(3, nodes) self.len(3, await core.nodes('ou:industryname=baz -> ou:industry -> ou:industryname')) + self.len(1, nodes := await core.nodes('[ ou:industry=({"name": "faz"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + async def test_ou_opening(self): async with self.getTestCore() as core: diff --git a/synapse/tests/test_model_person.py b/synapse/tests/test_model_person.py index 8be1a427eb2..70e3f50c3f6 100644 --- a/synapse/tests/test_model_person.py +++ b/synapse/tests/test_model_person.py @@ -60,6 +60,9 @@ async def test_ps_simple(self): self.eq(node.get('names'), ['billy bob']) self.eq(node.get('photo'), file0) + self.len(1, nodes := await core.nodes('[ ps:person=({"name": "billy bob"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + props = { 'dob': '2000', 'img': file0, @@ -147,6 +150,7 @@ async def test_ps_simple(self): 'id:numbers': (('*', 'asdf'), ('*', 'qwer')), 'users': ('visi', 'invisigoth'), 'crypto:address': 'btc/1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2', + 'langs': (lang00 := s_common.guid(),), } opts = {'vars': {'valu': con0, 'p': props}} q = '''[(ps:contact=$valu @@ -166,7 +170,7 @@ async def test_ps_simple(self): :birth:place:name=$p."birth:place:name" :death:place=$p."death:place" :death:place:loc=$p."death:place:loc" :death:place:name=$p."death:place:name" - :service:accounts=(*, *) + :service:accounts=(*, *) :langs=$p.langs )]''' nodes = await core.nodes(q, opts=opts) self.len(1, nodes) @@ -213,6 +217,22 @@ async def test_ps_simple(self): self.len(1, await core.nodes('ps:contact :death:place -> geo:place')) self.len(2, await core.nodes('ps:contact :service:accounts -> inet:service:account')) + opts = { + 'vars': { + 'ctor': { + 'email': 'v@vtx.lk', + 'id:number': node.get('id:numbers')[0], + 'lang': lang00, + 'name': 'vi', + 'orgname': 'vertex', + 'title': 'haha', + 'user': 'invisigoth', + }, + }, + } + self.len(1, nodes := await core.nodes('[ ps:contact=$ctor ]', opts=opts)) + self.eq(node.ndef, nodes[0].ndef) + nodes = await core.nodes('''[ ps:achievement=* :award=* diff --git a/synapse/tests/test_model_risk.py b/synapse/tests/test_model_risk.py index 3a1d0baf521..29225e0cb66 100644 --- a/synapse/tests/test_model_risk.py +++ b/synapse/tests/test_model_risk.py @@ -253,6 +253,9 @@ async def addNode(text): self.len(1, await core.nodes('risk:attack :target -> ps:contact')) self.len(1, await core.nodes('risk:attack :attacker -> ps:contact')) + self.len(1, nodes := await core.nodes('[ risk:vuln=({"name": "hehe"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + node = await addNode(f'''[ risk:hasvuln={hasv} :vuln={vuln} @@ -399,6 +402,7 @@ async def addNode(text): ] ''') self.len(1, nodes) + node = nodes[0] self.eq('vtx-apt1', nodes[0].get('name')) self.eq('VTX-APT1', nodes[0].get('desc')) self.eq(40, nodes[0].get('activity')) @@ -424,6 +428,9 @@ async def addNode(text): self.len(1, await core.nodes('risk:threat:merged:isnow -> risk:threat')) self.len(1, await core.nodes('risk:threat -> it:mitre:attack:group')) + self.len(1, nodes := await core.nodes('[ risk:threat=({"org:name": "comment crew"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + nodes = await core.nodes('''[ risk:leak=* :name="WikiLeaks ACME Leak" :desc="WikiLeaks leaked ACME stuff." @@ -618,6 +625,7 @@ async def test_model_risk_tool_software(self): ] ''') self.len(1, nodes) + node = nodes[0] self.nn(nodes[0].get('soft')) self.nn(nodes[0].get('reporter')) @@ -640,6 +648,9 @@ async def test_model_risk_tool_software(self): self.len(1, await core.nodes('risk:tool:software -> syn:tag')) self.len(1, await core.nodes('risk:tool:software -> it:mitre:attack:software')) + self.len(1, nodes := await core.nodes('[ risk:tool:software=({"soft:name": "beacon"}) ]')) + self.eq(node.ndef, nodes[0].ndef) + nodes = await core.nodes(''' [ risk:vuln:soft:range=* :vuln={[ risk:vuln=* :name=woot ]} From fc86876e014d64d6a06131076027088840e07cd5 Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Fri, 10 Jan 2025 14:18:44 -0800 Subject: [PATCH 2/3] changelog --- changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml diff --git a/changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml b/changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml new file mode 100644 index 00000000000..3133548f89e --- /dev/null +++ b/changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml @@ -0,0 +1,9 @@ +--- +desc: | + Added ``alts`` definitions to the following forms: ``geo:place``, ``it:prod:soft``, + ``it:prod:softver``, ``ou:campaign``, ``ou:conference``, ``ou:goal``, ``ou:industry``, + ``pol:country``, ``ps:contact``, ``ps:person``, ``risk:threat``, ``risk:tool:software``, + and ``risk:vuln``. +prs: [] +type: model +... From 7e73148ba5ce611ff75a45cb3c8611a79d4abfb2 Mon Sep 17 00:00:00 2001 From: mikemoritz Date: Fri, 10 Jan 2025 16:20:25 -0800 Subject: [PATCH 3/3] fix changelog --- changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml b/changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml index 3133548f89e..2d77bd43676 100644 --- a/changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml +++ b/changes/4a4fe1c2eb09b8a712af72dc6ce41c38.yaml @@ -1,9 +1,8 @@ --- -desc: | - Added ``alts`` definitions to the following forms: ``geo:place``, ``it:prod:soft``, +desc: 'Added ``alts`` definitions to the following forms: ``geo:place``, ``it:prod:soft``, ``it:prod:softver``, ``ou:campaign``, ``ou:conference``, ``ou:goal``, ``ou:industry``, ``pol:country``, ``ps:contact``, ``ps:person``, ``risk:threat``, ``risk:tool:software``, - and ``risk:vuln``. + and ``risk:vuln``.' prs: [] type: model ...