Skip to content

Commit

Permalink
feat: api to add new loc scheme (#352)
Browse files Browse the repository at this point in the history
* feat: api to add new loc scheme

* test: update spec test
  • Loading branch information
iFergal authored Jan 20, 2025
1 parent d49436a commit cc7fd04
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 7 deletions.
88 changes: 88 additions & 0 deletions src/keria/app/aiding.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ def loadEnds(app, agency, authn):
endRoleEnd = EndRoleResourceEnd()
app.add_route("/identifiers/{name}/endroles/{role}/{eid}", endRoleEnd)

locSchemesEnd = LocSchemeCollectionEnd()
app.add_route("/identifiers/{name}/locschemes", locSchemesEnd)

rpyEscrowEnd = RpyEscrowCollectionEnd()
app.add_route("/escrows/rpy", rpyEscrowEnd)

Expand Down Expand Up @@ -1351,6 +1354,91 @@ def on_delete(self, req, rep):
pass


class LocSchemeCollectionEnd:

@staticmethod
def on_post(req, rep, name):
"""POST endpoint for loc scheme collection
Args:
req (Request): Falcon HTTP request object
rep (Response): Falcon HTTP response object
name (str): human readable alias or prefix for identifier
---
summary: Authorises a new location scheme.
description: This endpoint authorises a new location scheme (endpoint) for a particular endpoint identifier.
tags:
- Loc Scheme
parameters:
- in: path
name: name
schema:
type: string
required: true
description: The human-readable name of the identifier or its prefix.
requestBody:
content:
application/json:
schema:
type: object
properties:
rpy:
type: object
description: The reply object.
sigs:
type: array
items:
type: string
description: The signatures.
responses:
202:
description: Accepted. The loc scheme authorisation is in progress.
400:
description: Bad request. This could be due to missing or invalid parameters.
404:
description: Not found. The requested identifier was not found.
"""
agent = req.context.agent
body = req.get_media()

hab = agent.hby.habs[name] if name in agent.hby.habs else agent.hby.habByName(name)
if hab is None:
raise falcon.errors.HTTPNotFound(description=f"invalid alias or prefix {name}")

rpy = httping.getRequiredParam(body, "rpy")
rsigs = httping.getRequiredParam(body, "sigs")

rserder = serdering.SerderKERI(sad=rpy)
data = rserder.ked["a"]
eid = data["eid"]
scheme = data["scheme"]
url = data["url"]

rsigers = [core.Siger(qb64=rsig) for rsig in rsigs]
tsg = (
hab.kever.prefixer,
coring.Seqner(sn=hab.kever.sn),
coring.Saider(qb64=hab.kever.serder.said),
rsigers,
)
try:
agent.hby.rvy.processReply(rserder, tsgs=[tsg])
except kering.UnverifiedReplyError:
pass
except kering.ValidationError:
raise falcon.HTTPBadRequest(description="unable to verify end role reply message")

oid = ".".join([eid, scheme])
op = agent.monitor.submit(
oid, longrunning.OpTypes.locscheme, metadata=dict(eid=eid, scheme=scheme, url=url)
)

rep.content_type = "application/json"
rep.status = falcon.HTTP_202
rep.data = op.to_json().encode("utf-8")


class RpyEscrowCollectionEnd:

@staticmethod
Expand Down
22 changes: 19 additions & 3 deletions src/keria/core/longrunning.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
from keria.app import delegating

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

OpTypes = Typeage(oobi="oobi", witness='witness', delegation='delegation', group='group', query='query',
registry='registry', credential='credential', endrole='endrole', challenge='challenge',
registry='registry', credential='credential', endrole='endrole', locscheme='locscheme', challenge='challenge',
exchange='exchange', submit='submit', done='done')


Expand Down Expand Up @@ -397,6 +397,22 @@ def status(self, op):
else:
operation.done = False

elif op.type in (OpTypes.locscheme,):
if "eid" not in op.metadata or "scheme" not in op.metadata or "url" not in op.metadata:
raise kering.ValidationError(
f"invalid long running {op.type} operation, metadata missing required fields ('eid', 'scheme', 'url')")

eid = op.metadata['eid']
scheme = op.metadata['scheme']
url = op.metadata['url']

loc = self.hby.db.locs.get(keys=(eid, scheme))
if loc:
operation.done = True
operation.response = dict(eid=eid, scheme=scheme, url=url)
else:
operation.done = False

elif op.type in (OpTypes.challenge,):
if op.oid not in self.hby.kevers:
operation.done = False
Expand Down
5 changes: 5 additions & 0 deletions src/keria/testing/testing_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,11 @@ def endrole(cid, eid, role="agent"):
data = dict(cid=cid, role=role, eid=eid)
return eventing.reply(route="/end/role/add", data=data)

@staticmethod
def locscheme(eid, url, scheme="http"):
data = dict(eid=eid, url=url, scheme=scheme)
return eventing.reply(route="/loc/scheme", data=data)

@staticmethod
def middleware(agent):
return MockAgentMiddleware(agent=agent)
Expand Down
60 changes: 60 additions & 0 deletions tests/app/test_aiding.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ def test_load_ends(helpers):
assert isinstance(end, aiding.EndRoleCollectionEnd)
(end, *_) = app._router.find("/identifiers/NAME/endroles/witness/EID")
assert isinstance(end, aiding.EndRoleResourceEnd)
(end, *_) = app._router.find("/identifiers/NAME/locschemes")
assert isinstance(end, aiding.LocSchemeCollectionEnd)
(end, *_) = app._router.find("/challenges")
assert isinstance(end, aiding.ChallengeCollectionEnd)
(end, *_) = app._router.find("/challenges/NAME")
Expand Down Expand Up @@ -138,6 +140,64 @@ def test_endrole_ends(helpers):
'eid': 'EI7AkI40M11MS7lkTCb10JC9-nDt-tXwQh44OHAFlv_9'}


def test_locscheme_ends(helpers, mockHelpingNowUTC):
with helpers.openKeria() as (agency, agent, app, client):
locSchemesEnd = aiding.LocSchemeCollectionEnd()
app.add_route("/identifiers/{name}/locschemes", locSchemesEnd)
end = aiding.IdentifierCollectionEnd()
app.add_route("/identifiers", end)

salt = b'0123456789abcdef'
op = helpers.createAid(client, "user1", salt)
aid = op["response"]
recp = aid['i']
assert recp == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY"

rpy = helpers.locscheme(recp, "http://testurl.com")
sigs = ["AACOFnUk-lsVq0rLNdWCBtr51fnkXRdEzo8gnUwYF0F6xJPGL9_MXxezBc_P6e15-M1GpaHua_l3Hn4qKRMomRoM"]
body = dict(rpy=rpy.ked, sigs=sigs)

res = client.simulate_post(path=f"/identifiers/unknown-user/locschemes", json=body)
assert res.status_code == 404
assert res.json == {'description': 'invalid alias or prefix unknown-user',
'title': '404 Not Found'}

res = client.simulate_post(path=f"/identifiers/user1/locschemes", json=body)
assert res.status_code == 400
assert res.json == {'description': 'unable to verify end role reply message',
'title': '400 Bad Request'}

sigs = helpers.sign(salt, 0, 0, rpy.raw)
body = dict(rpy=rpy.ked, sigs=sigs)
res = client.simulate_post(path=f"/identifiers/user1/locschemes", json=body)
assert res.status_code == 202
op = res.json
assert op["done"]

keys = (recp, "http")
loc = agent.hby.db.locs.get(keys=keys)
assert loc is not None
assert loc.url == "http://testurl.com"

lans = agent.hby.db.lans.get(keys=keys)
assert lans is not None
assert lans.qb64 == "EEnRKmN-5cRGkGEfS0Z8VDIECsD8DBMNPpHWFBW8CO4p"

# https
rpy = helpers.locscheme(recp, "https://testurl.com", "https")
sigs = helpers.sign(salt, 0, 0, rpy.raw)
body = dict(rpy=rpy.ked, sigs=sigs)
res = client.simulate_post(path=f"/identifiers/user1/locschemes", json=body)
assert res.status_code == 202
op = res.json
assert op["done"]

keys = (recp, "https")
loc = agent.hby.db.locs.get(keys=keys)
assert loc is not None
assert loc.url == "https://testurl.com"


def test_agent_resource(helpers, mockHelpingNowUTC):
with helpers.openKeria() as (agency, agent, app, client):
agentEnd = aiding.AgentResourceEnd(agency=agency, authn=None)
Expand Down
3 changes: 2 additions & 1 deletion tests/app/test_specing.py

Large diffs are not rendered by default.

38 changes: 35 additions & 3 deletions tests/core/test_longrunning.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def test_operations(helpers):
app.add_route("/identifiers", end)
endRolesEnd = aiding.EndRoleCollectionEnd()
app.add_route("/identifiers/{name}/endroles", endRolesEnd)
locSchemesEnd = aiding.LocSchemeCollectionEnd()
app.add_route("/identifiers/{name}/locschemes", locSchemesEnd)
opColEnd = longrunning.OperationCollectionEnd()
app.add_route("/operations", opColEnd)
opResEnd = longrunning.OperationResourceEnd()
Expand Down Expand Up @@ -169,6 +171,17 @@ def test_operations(helpers):
assert op.name == f"query.{recp}.4"
assert op.done is False

# add loc scheme

rpy = helpers.locscheme(recp, "http://testurl.com")
sigs = helpers.sign(salt, 0, 0, rpy.raw)
body = dict(rpy=rpy.ked, sigs=sigs)
res = client.simulate_post(
path=f"/identifiers/user2/locschemes", json=body)
op = res.json
assert op["done"] is True
assert op["name"] == "locscheme.EAyXphfc0qOLqEDAe0cCYCj-ovbSaEFgVgX6MrC_b5ZO.http"


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

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

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

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

# LocScheme
locop = longrunning.Op(type=longrunning.OpTypes.locscheme, oid="EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1",
start=helping.nowIso8601(), metadata={})

with pytest.raises(ValidationError) as err:
locop.metadata = {"eid": "EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1", "scheme": "http"}
agent.monitor.status(locop)
assert str(err.value) == "invalid long running locscheme operation, metadata missing required fields ('eid', 'scheme', 'url')"

with pytest.raises(ValidationError) as err:
locop.metadata = {"eid": "EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1", "url": "http://testurl.com"}
agent.monitor.status(locop)
assert str(err.value) == "invalid long running locscheme operation, metadata missing required fields ('eid', 'scheme', 'url')"

with pytest.raises(ValidationError) as err:
locop.metadata = {"scheme": "http", "url": "http://testurl.com"}
agent.monitor.status(locop)
assert str(err.value) == "invalid long running locscheme operation, metadata missing required fields ('eid', 'scheme', 'url')"

# Challenge
challengeop = longrunning.Op(type=longrunning.OpTypes.challenge, oid="EIsavDv6zpJDPauh24RSCx00jGc6VMe3l84Y8pPS8p-1",
start=helping.nowIso8601(), metadata={})
Expand Down

0 comments on commit cc7fd04

Please sign in to comment.