Skip to content

Commit cc7fd04

Browse files
authored
feat: api to add new loc scheme (#352)
* feat: api to add new loc scheme * test: update spec test
1 parent d49436a commit cc7fd04

File tree

6 files changed

+209
-7
lines changed

6 files changed

+209
-7
lines changed

src/keria/app/aiding.py

+88
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ def loadEnds(app, agency, authn):
4747
endRoleEnd = EndRoleResourceEnd()
4848
app.add_route("/identifiers/{name}/endroles/{role}/{eid}", endRoleEnd)
4949

50+
locSchemesEnd = LocSchemeCollectionEnd()
51+
app.add_route("/identifiers/{name}/locschemes", locSchemesEnd)
52+
5053
rpyEscrowEnd = RpyEscrowCollectionEnd()
5154
app.add_route("/escrows/rpy", rpyEscrowEnd)
5255

@@ -1351,6 +1354,91 @@ def on_delete(self, req, rep):
13511354
pass
13521355

13531356

1357+
class LocSchemeCollectionEnd:
1358+
1359+
@staticmethod
1360+
def on_post(req, rep, name):
1361+
"""POST endpoint for loc scheme collection
1362+
1363+
Args:
1364+
req (Request): Falcon HTTP request object
1365+
rep (Response): Falcon HTTP response object
1366+
name (str): human readable alias or prefix for identifier
1367+
1368+
---
1369+
summary: Authorises a new location scheme.
1370+
description: This endpoint authorises a new location scheme (endpoint) for a particular endpoint identifier.
1371+
tags:
1372+
- Loc Scheme
1373+
parameters:
1374+
- in: path
1375+
name: name
1376+
schema:
1377+
type: string
1378+
required: true
1379+
description: The human-readable name of the identifier or its prefix.
1380+
requestBody:
1381+
content:
1382+
application/json:
1383+
schema:
1384+
type: object
1385+
properties:
1386+
rpy:
1387+
type: object
1388+
description: The reply object.
1389+
sigs:
1390+
type: array
1391+
items:
1392+
type: string
1393+
description: The signatures.
1394+
responses:
1395+
202:
1396+
description: Accepted. The loc scheme authorisation is in progress.
1397+
400:
1398+
description: Bad request. This could be due to missing or invalid parameters.
1399+
404:
1400+
description: Not found. The requested identifier was not found.
1401+
"""
1402+
agent = req.context.agent
1403+
body = req.get_media()
1404+
1405+
hab = agent.hby.habs[name] if name in agent.hby.habs else agent.hby.habByName(name)
1406+
if hab is None:
1407+
raise falcon.errors.HTTPNotFound(description=f"invalid alias or prefix {name}")
1408+
1409+
rpy = httping.getRequiredParam(body, "rpy")
1410+
rsigs = httping.getRequiredParam(body, "sigs")
1411+
1412+
rserder = serdering.SerderKERI(sad=rpy)
1413+
data = rserder.ked["a"]
1414+
eid = data["eid"]
1415+
scheme = data["scheme"]
1416+
url = data["url"]
1417+
1418+
rsigers = [core.Siger(qb64=rsig) for rsig in rsigs]
1419+
tsg = (
1420+
hab.kever.prefixer,
1421+
coring.Seqner(sn=hab.kever.sn),
1422+
coring.Saider(qb64=hab.kever.serder.said),
1423+
rsigers,
1424+
)
1425+
try:
1426+
agent.hby.rvy.processReply(rserder, tsgs=[tsg])
1427+
except kering.UnverifiedReplyError:
1428+
pass
1429+
except kering.ValidationError:
1430+
raise falcon.HTTPBadRequest(description="unable to verify end role reply message")
1431+
1432+
oid = ".".join([eid, scheme])
1433+
op = agent.monitor.submit(
1434+
oid, longrunning.OpTypes.locscheme, metadata=dict(eid=eid, scheme=scheme, url=url)
1435+
)
1436+
1437+
rep.content_type = "application/json"
1438+
rep.status = falcon.HTTP_202
1439+
rep.data = op.to_json().encode("utf-8")
1440+
1441+
13541442
class RpyEscrowCollectionEnd:
13551443

13561444
@staticmethod

src/keria/core/longrunning.py

+19-3
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
from keria.app import delegating
2121

2222
# long running operation types
23-
Typeage = namedtuple("Tierage", 'oobi witness delegation group query registry credential endrole challenge exchange submit '
24-
'done')
23+
Typeage = namedtuple("Tierage", 'oobi witness delegation group query registry credential endrole '
24+
'locscheme challenge exchange submit done')
2525

2626
OpTypes = Typeage(oobi="oobi", witness='witness', delegation='delegation', group='group', query='query',
27-
registry='registry', credential='credential', endrole='endrole', challenge='challenge',
27+
registry='registry', credential='credential', endrole='endrole', locscheme='locscheme', challenge='challenge',
2828
exchange='exchange', submit='submit', done='done')
2929

3030

@@ -397,6 +397,22 @@ def status(self, op):
397397
else:
398398
operation.done = False
399399

400+
elif op.type in (OpTypes.locscheme,):
401+
if "eid" not in op.metadata or "scheme" not in op.metadata or "url" not in op.metadata:
402+
raise kering.ValidationError(
403+
f"invalid long running {op.type} operation, metadata missing required fields ('eid', 'scheme', 'url')")
404+
405+
eid = op.metadata['eid']
406+
scheme = op.metadata['scheme']
407+
url = op.metadata['url']
408+
409+
loc = self.hby.db.locs.get(keys=(eid, scheme))
410+
if loc:
411+
operation.done = True
412+
operation.response = dict(eid=eid, scheme=scheme, url=url)
413+
else:
414+
operation.done = False
415+
400416
elif op.type in (OpTypes.challenge,):
401417
if op.oid not in self.hby.kevers:
402418
operation.done = False

src/keria/testing/testing_helper.py

+5
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,11 @@ def endrole(cid, eid, role="agent"):
540540
data = dict(cid=cid, role=role, eid=eid)
541541
return eventing.reply(route="/end/role/add", data=data)
542542

543+
@staticmethod
544+
def locscheme(eid, url, scheme="http"):
545+
data = dict(eid=eid, url=url, scheme=scheme)
546+
return eventing.reply(route="/loc/scheme", data=data)
547+
543548
@staticmethod
544549
def middleware(agent):
545550
return MockAgentMiddleware(agent=agent)

tests/app/test_aiding.py

+60
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ def test_load_ends(helpers):
5353
assert isinstance(end, aiding.EndRoleCollectionEnd)
5454
(end, *_) = app._router.find("/identifiers/NAME/endroles/witness/EID")
5555
assert isinstance(end, aiding.EndRoleResourceEnd)
56+
(end, *_) = app._router.find("/identifiers/NAME/locschemes")
57+
assert isinstance(end, aiding.LocSchemeCollectionEnd)
5658
(end, *_) = app._router.find("/challenges")
5759
assert isinstance(end, aiding.ChallengeCollectionEnd)
5860
(end, *_) = app._router.find("/challenges/NAME")
@@ -138,6 +140,64 @@ def test_endrole_ends(helpers):
138140
'eid': 'EI7AkI40M11MS7lkTCb10JC9-nDt-tXwQh44OHAFlv_9'}
139141

140142

143+
def test_locscheme_ends(helpers, mockHelpingNowUTC):
144+
with helpers.openKeria() as (agency, agent, app, client):
145+
locSchemesEnd = aiding.LocSchemeCollectionEnd()
146+
app.add_route("/identifiers/{name}/locschemes", locSchemesEnd)
147+
end = aiding.IdentifierCollectionEnd()
148+
app.add_route("/identifiers", end)
149+
150+
salt = b'0123456789abcdef'
151+
op = helpers.createAid(client, "user1", salt)
152+
aid = op["response"]
153+
recp = aid['i']
154+
assert recp == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY"
155+
156+
rpy = helpers.locscheme(recp, "http://testurl.com")
157+
sigs = ["AACOFnUk-lsVq0rLNdWCBtr51fnkXRdEzo8gnUwYF0F6xJPGL9_MXxezBc_P6e15-M1GpaHua_l3Hn4qKRMomRoM"]
158+
body = dict(rpy=rpy.ked, sigs=sigs)
159+
160+
res = client.simulate_post(path=f"/identifiers/unknown-user/locschemes", json=body)
161+
assert res.status_code == 404
162+
assert res.json == {'description': 'invalid alias or prefix unknown-user',
163+
'title': '404 Not Found'}
164+
165+
res = client.simulate_post(path=f"/identifiers/user1/locschemes", json=body)
166+
assert res.status_code == 400
167+
assert res.json == {'description': 'unable to verify end role reply message',
168+
'title': '400 Bad Request'}
169+
170+
sigs = helpers.sign(salt, 0, 0, rpy.raw)
171+
body = dict(rpy=rpy.ked, sigs=sigs)
172+
res = client.simulate_post(path=f"/identifiers/user1/locschemes", json=body)
173+
assert res.status_code == 202
174+
op = res.json
175+
assert op["done"]
176+
177+
keys = (recp, "http")
178+
loc = agent.hby.db.locs.get(keys=keys)
179+
assert loc is not None
180+
assert loc.url == "http://testurl.com"
181+
182+
lans = agent.hby.db.lans.get(keys=keys)
183+
assert lans is not None
184+
assert lans.qb64 == "EEnRKmN-5cRGkGEfS0Z8VDIECsD8DBMNPpHWFBW8CO4p"
185+
186+
# https
187+
rpy = helpers.locscheme(recp, "https://testurl.com", "https")
188+
sigs = helpers.sign(salt, 0, 0, rpy.raw)
189+
body = dict(rpy=rpy.ked, sigs=sigs)
190+
res = client.simulate_post(path=f"/identifiers/user1/locschemes", json=body)
191+
assert res.status_code == 202
192+
op = res.json
193+
assert op["done"]
194+
195+
keys = (recp, "https")
196+
loc = agent.hby.db.locs.get(keys=keys)
197+
assert loc is not None
198+
assert loc.url == "https://testurl.com"
199+
200+
141201
def test_agent_resource(helpers, mockHelpingNowUTC):
142202
with helpers.openKeria() as (agency, agent, app, client):
143203
agentEnd = aiding.AgentResourceEnd(agency=agency, authn=None)

tests/app/test_specing.py

+2-1
Large diffs are not rendered by default.

tests/core/test_longrunning.py

+35-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ def test_operations(helpers):
1313
app.add_route("/identifiers", end)
1414
endRolesEnd = aiding.EndRoleCollectionEnd()
1515
app.add_route("/identifiers/{name}/endroles", endRolesEnd)
16+
locSchemesEnd = aiding.LocSchemeCollectionEnd()
17+
app.add_route("/identifiers/{name}/locschemes", locSchemesEnd)
1618
opColEnd = longrunning.OperationCollectionEnd()
1719
app.add_route("/operations", opColEnd)
1820
opResEnd = longrunning.OperationResourceEnd()
@@ -169,6 +171,17 @@ def test_operations(helpers):
169171
assert op.name == f"query.{recp}.4"
170172
assert op.done is False
171173

174+
# add loc scheme
175+
176+
rpy = helpers.locscheme(recp, "http://testurl.com")
177+
sigs = helpers.sign(salt, 0, 0, rpy.raw)
178+
body = dict(rpy=rpy.ked, sigs=sigs)
179+
res = client.simulate_post(
180+
path=f"/identifiers/user2/locschemes", json=body)
181+
op = res.json
182+
assert op["done"] is True
183+
assert op["name"] == "locscheme.EAyXphfc0qOLqEDAe0cCYCj-ovbSaEFgVgX6MrC_b5ZO.http"
184+
172185

173186
def test_operation_bad_metadata(helpers):
174187
with helpers.openKeria() as (agency, agent, app, client):
@@ -250,20 +263,39 @@ def test_operation_bad_metadata(helpers):
250263
start=helping.nowIso8601(), metadata={})
251264

252265
with pytest.raises(ValidationError) as err:
253-
witop.metadata = {"cid": "EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1", "role": "agent"}
266+
endop.metadata = {"cid": "EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1", "role": "agent"}
254267
agent.monitor.status(endop)
255268
assert str(err.value) == "invalid long running endrole operation, metadata missing required fields ('cid', 'role', 'eid')"
256269

257270
with pytest.raises(ValidationError) as err:
258-
witop.metadata = {"cid": "EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1", "eid": "EI7AkI40M11MS7lkTCb10JC9-nDt-tXwQh44OHAFlv_9"}
271+
endop.metadata = {"cid": "EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1", "eid": "EI7AkI40M11MS7lkTCb10JC9-nDt-tXwQh44OHAFlv_9"}
259272
agent.monitor.status(endop)
260273
assert str(err.value) == "invalid long running endrole operation, metadata missing required fields ('cid', 'role', 'eid')"
261274

262275
with pytest.raises(ValidationError) as err:
263-
witop.metadata = {"role": "agent", "eid": "EI7AkI40M11MS7lkTCb10JC9-nDt-tXwQh44OHAFlv_9"}
276+
endop.metadata = {"role": "agent", "eid": "EI7AkI40M11MS7lkTCb10JC9-nDt-tXwQh44OHAFlv_9"}
264277
agent.monitor.status(endop)
265278
assert str(err.value) == "invalid long running endrole operation, metadata missing required fields ('cid', 'role', 'eid')"
266279

280+
# LocScheme
281+
locop = longrunning.Op(type=longrunning.OpTypes.locscheme, oid="EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1",
282+
start=helping.nowIso8601(), metadata={})
283+
284+
with pytest.raises(ValidationError) as err:
285+
locop.metadata = {"eid": "EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1", "scheme": "http"}
286+
agent.monitor.status(locop)
287+
assert str(err.value) == "invalid long running locscheme operation, metadata missing required fields ('eid', 'scheme', 'url')"
288+
289+
with pytest.raises(ValidationError) as err:
290+
locop.metadata = {"eid": "EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1", "url": "http://testurl.com"}
291+
agent.monitor.status(locop)
292+
assert str(err.value) == "invalid long running locscheme operation, metadata missing required fields ('eid', 'scheme', 'url')"
293+
294+
with pytest.raises(ValidationError) as err:
295+
locop.metadata = {"scheme": "http", "url": "http://testurl.com"}
296+
agent.monitor.status(locop)
297+
assert str(err.value) == "invalid long running locscheme operation, metadata missing required fields ('eid', 'scheme', 'url')"
298+
267299
# Challenge
268300
challengeop = longrunning.Op(type=longrunning.OpTypes.challenge, oid="EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1",
269301
start=helping.nowIso8601(), metadata={})

0 commit comments

Comments
 (0)