Skip to content
Merged
6 changes: 6 additions & 0 deletions changes/61d4b40b996d788ca2e3f3634843713c.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
desc: Added ``:plus`` and ``:base`` properties to ``inet:email``.
desc:literal: false
prs: []
type: model
...
6 changes: 6 additions & 0 deletions changes/ba9dea8afe794ece1b40fa50f946abfd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
desc: Migrated email addresses with +<tag> user names to properly populate ``:plus`` and ``:base``.
desc:literal: false
prs: []
type: migration
...
7 changes: 6 additions & 1 deletion synapse/lib/modelrev.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

logger = logging.getLogger(__name__)

maxvers = (0, 2, 33)
maxvers = (0, 2, 34)

class ModelRev:

Expand Down Expand Up @@ -52,6 +52,7 @@ def __init__(self, core):
((0, 2, 31), self.revModel_0_2_31),
((0, 2, 32), self.revModel_0_2_32),
((0, 2, 33), self.revModel_0_2_33),
((0, 2, 34), self.revModel_0_2_34),
)

async def _uniqSortArray(self, todoprops, layers):
Expand Down Expand Up @@ -824,6 +825,10 @@ async def revModel_0_2_32(self, layers):
async def revModel_0_2_33(self, layers):
await self._propToForm(layers, 'transport:sea:vessel:name', 'entity:name')

async def revModel_0_2_34(self, layers):
await self._normFormSubs(layers, 'inet:email')
await self._propToForm(layers, 'inet:email:base', 'inet:email')

async def runStorm(self, text, opts=None):
'''
Run storm code in a schedcoro and log the output messages.
Expand Down
21 changes: 21 additions & 0 deletions synapse/models/inet.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,19 +288,30 @@ def _normPyStr(self, valu):
mesg = f'Email address expected in <user>@<fqdn> format, got "{valu}"'
raise s_exc.BadTypeValu(valu=valu, name=self.name, mesg=mesg) from None

plus = None
if len(parts := user.split('+', 1)) == 2:
baseuser, plus = parts
plus = plus.strip().lower()

try:
fqdnnorm, fqdninfo = self.modl.type('inet:fqdn').norm(fqdn)
usernorm, userinfo = self.modl.type('inet:user').norm(user)
except Exception as e:
raise s_exc.BadTypeValu(valu=valu, name=self.name, mesg=str(e)) from None

norm = f'{usernorm}@{fqdnnorm}'

info = {
'subs': {
'fqdn': fqdnnorm,
'user': usernorm,
}
}

if plus is not None:
info['subs']['plus'] = plus
info['subs']['base'] = f'{baseuser}@{fqdnnorm}'

return norm, info

class Fqdn(s_types.Type):
Expand Down Expand Up @@ -2117,12 +2128,22 @@ def getModelDefs(self):
)),

('inet:email', {}, (

('user', ('inet:user', {}), {
'ro': True,
'doc': 'The username of the email address.'}),

('fqdn', ('inet:fqdn', {}), {
'ro': True,
'doc': 'The domain of the email address.'}),

('plus', ('str', {'lower': True, 'strip': True}), {
'ro': True,
'doc': 'The optional email address "tag".'}),

('base', ('inet:email', {}), {
'ro': True,
'doc': 'The base email address which is populated if the email address contains a user with a +<tag>.'}),
)),

('inet:flow', {}, (
Expand Down
9 changes: 9 additions & 0 deletions synapse/tests/test_lib_modelrev.py
Original file line number Diff line number Diff line change
Expand Up @@ -1772,3 +1772,12 @@ async def test_modelrev_0_2_33(self):
nodes = await core.nodes('entity:name')
self.len(1, nodes)
self.eq('foo bar', nodes[0].repr())

async def test_modelrev_0_2_34(self):
async with self.getRegrCore('model-0.2.34') as core:
nodes = await core.nodes('inet:[email protected]')
self.len(1, nodes)
self.eq(nodes[0].get('user'), 'visi+synapse')
self.eq(nodes[0].get('plus'), 'synapse')
self.eq(nodes[0].get('base'), '[email protected]')
self.len(1, await core.nodes('inet:[email protected] :base -> inet:email +inet:[email protected]'))
56 changes: 56 additions & 0 deletions synapse/tests/test_model_inet.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,62 @@ async def test_email(self):
self.eq(node.get('fqdn'), 'vertex.link')
self.eq(node.get('user'), 'unittest')

nodes = await core.nodes('[ inet:[email protected] ]')
self.len(1, nodes)
self.eq(nodes[0].ndef[1], '[email protected]')
self.eq(nodes[0].get('user'), 'visi+synapse')
self.eq(nodes[0].get('plus'), 'synapse')
self.eq(nodes[0].get('base'), '[email protected]')
self.len(1, await core.nodes('inet:[email protected] :base -> inet:email +inet:[email protected]'))

nodes = await core.nodes('[ inet:[email protected] ]')
self.len(1, nodes)
self.eq(nodes[0].ndef[1], '[email protected]')
self.eq(nodes[0].get('user'), 'visi++synapse')
self.eq(nodes[0].get('plus'), '+synapse')
self.eq(nodes[0].get('base'), '[email protected]')
self.len(1, await core.nodes('inet:[email protected] :base -> inet:email +inet:[email protected]'))

nodes = await core.nodes('[ inet:[email protected] ]')
self.len(1, nodes)
self.eq(nodes[0].ndef[1], '[email protected]')
self.eq(nodes[0].get('user'), 'visi+synapse+foo')
self.eq(nodes[0].get('plus'), 'synapse+foo')
self.eq(nodes[0].get('base'), '[email protected]')
self.len(1, await core.nodes('inet:[email protected] :base -> inet:email +inet:[email protected]'))

nodes = await core.nodes('[ inet:[email protected] ]')
self.len(1, nodes)
self.eq(nodes[0].ndef[1], '[email protected]')
self.eq(nodes[0].get('user'), 'visi+')
self.eq(nodes[0].get('plus'), '')
self.eq(nodes[0].get('base'), '[email protected]')
self.len(1, await core.nodes('inet:[email protected] :base -> inet:email +inet:[email protected]'))

nodes = await core.nodes('[ inet:[email protected] ]')
self.len(1, nodes)
self.eq(nodes[0].ndef[1], '[email protected]')
self.eq(nodes[0].get('user'), '+')
self.eq(nodes[0].get('plus'), '')
self.eq(nodes[0].get('base'), '@vertex.link')
self.len(1, await core.nodes('inet:email="[email protected]" :base -> inet:email +inet:email="@vertex.link"'))

nodes = await core.nodes('[ inet:[email protected] ]')
self.len(1, nodes)
self.eq(nodes[0].ndef[1], '[email protected]')
self.eq(nodes[0].get('user'), '++')
self.eq(nodes[0].get('plus'), '+')
self.eq(nodes[0].get('base'), '@vertex.link')
self.len(1, await core.nodes('inet:email="[email protected]" :base -> inet:email +inet:email="@vertex.link"'))

nodes = await core.nodes('[ inet:[email protected] ]')
self.len(1, nodes)
self.eq(nodes[0].ndef[1], '[email protected]')
self.eq(nodes[0].get('user'), '+++')
self.eq(nodes[0].get('plus'), '++')
self.eq(nodes[0].get('base'), '@vertex.link')
self.len(1, await core.nodes('inet:email="[email protected]" :base -> inet:email +inet:email="@vertex.link"'))

async def test_flow(self):
async with self.getTestCore() as core:
valu = s_common.guid()
Expand Down