Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: api to add new loc scheme #352

Merged
merged 2 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading