From 9a5e2dc5d191c10d0cb7351e77530d2ab70637dc Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Mon, 3 Jun 2024 08:21:11 -0400 Subject: [PATCH 01/21] improved design and logic per review comments, still need to track the dip process by the delegator Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/aiding.py | 72 +++++++++++++++++++ src/keria/app/delegating.py | 5 +- src/keria/core/longrunning.py | 17 ++++- tests/app/test_aiding.py | 127 +++++++++++++++++++++++++++++++++- 4 files changed, 212 insertions(+), 9 deletions(-) diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index fa8ef21a..f1278cb0 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -6,6 +6,7 @@ """ import json from dataclasses import asdict +import time from urllib.parse import urlparse, urljoin import falcon @@ -23,6 +24,7 @@ logger = ogler.getLogger() +ID_DELEGATOR_ROUTE = "/identifiers/{name}/delegator" def loadEnds(app, agency, authn): groupEnd = AgentResourceEnd(agency=agency, authn=authn) @@ -30,6 +32,10 @@ def loadEnds(app, agency, authn): aidsEnd = IdentifierCollectionEnd() app.add_route("/identifiers", aidsEnd) + + gatorEnd = IdentifierDelegatorEnd() + app.add_route(ID_DELEGATOR_ROUTE, gatorEnd) + aidEnd = IdentifierResourceEnd() app.add_route("/identifiers/{name}", aidEnd) app.add_route("/identifiers/{name}/events", aidEnd) @@ -438,6 +444,72 @@ def on_post(req, rep): except (kering.AuthError, ValueError) as e: raise falcon.HTTPBadRequest(description=e.args[0]) +class IdentifierDelegatorEnd: + """ Resource class for for handling delegator events""" + + def on_post(self, req, rep, name): + """ Identifier delegator approval POST endpoint to add the anchor and approve the delegation + + Parameters: + req (Request): falcon.Request HTTP request object + rep (Response): falcon.Response HTTP response object + name (str): human readable name for Hab to rename + + """ + if not name: + raise falcon.HTTPBadRequest(description="name is required") + agent = req.context.agent + hab = agent.hby.habByName(name) + + if hab is None: + raise falcon.HTTPNotFound(title=f"No AID with name {name} found") + + body = req.get_media() + + if body.get("ixn") is not None: + iop = IdentifierResourceEnd.interact(agent, name, body) + + while not hasattr(iop,'done') or not iop.done: + time.sleep(1) + iop = agent.monitor.status(iop) + + serder = serdering.SerderKERI(sad=iop.response) + gatepre = self.approveDelegation(agent, hab, serder.ked) + adop = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.delegable, + metadata=dict(response=gatepre, depends=iop)) + + try: + rep.status = falcon.HTTP_200 + rep.content_type = "application/json" + rep.data = adop.to_json().encode("utf-8") + return rep + except (kering.AuthError, ValueError) as e: + raise falcon.HTTPBadRequest(description=e.args[0]) + + else: + raise falcon.HTTPBadRequest(title="invalid request", + description=f"required field 'ixn' missing from request") + + @staticmethod + def approveDelegation(agent, hab, ixn) -> str: + serder = serdering.SerderKERI(sad=ixn) + + gatePre = ixn['a'][0]['i'] + gateSaid = ixn['a'][0]['d'] + + for (pre, sn), dig in hab.db.delegables.getItemIter(): + if pre == gatePre: + seqner = coring.Seqner(sn=serder.sn) + couple = seqner.qb64b + serder.saidb + dgkey = dbing.dgKey(coring.Saider(qb64=gatePre).qb64b, coring.Saider(qb64=gateSaid).qb64b) + # the dip event should have been received from the delegatee + # and will be sitting in escrows (hence the hab.db.delegables above) + # adding the authorize event seal will allow the dip to be processed + # and added to the delegator kever + hab.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) + return gatePre + + raise falcon.HTTPBadRequest(title=f"No delegables found for delegator {hab.pre} to approve delegatee {gatePre}") class IdentifierResourceEnd: """ Resource class for updating and deleting identifiers """ diff --git a/src/keria/app/delegating.py b/src/keria/app/delegating.py index 8bd5bf74..229036d6 100644 --- a/src/keria/app/delegating.py +++ b/src/keria/app/delegating.py @@ -109,10 +109,7 @@ def processEscrows(self): def processUnanchoredEscrow(self): """ - Process escrow of partially signed multisig group KEL events. Message - processing will send this local controllers signature to all other participants - then this escrow waits for signatures from all other participants - + Process escrow of unacnchored events that have been delegated and are waiting for delegator anchor/approval. """ for (pre, said), serder in self.hby.db.dune.getItemIter(): # group partial witness escrow kever = self.hby.kevers[pre] diff --git a/src/keria/core/longrunning.py b/src/keria/core/longrunning.py index 06016184..0fb6d4c6 100644 --- a/src/keria/core/longrunning.py +++ b/src/keria/core/longrunning.py @@ -17,11 +17,11 @@ from keri.db import dbing, koming from keri.help import helping -# long running operationt types -Typeage = namedtuple("Tierage", 'oobi witness delegation group query registry credential endrole challenge exchange ' +# long running operation types +Typeage = namedtuple("Tierage", 'oobi witness delegable delegation group query registry credential endrole challenge exchange ' 'done') -OpTypes = Typeage(oobi="oobi", witness='witness', delegation='delegation', group='group', query='query', +OpTypes = Typeage(oobi="oobi", witness='witness', delegable='delegable', delegation='delegation', group='group', query='query', registry='registry', credential='credential', endrole='endrole', challenge='challenge', exchange='exchange', done='done') @@ -265,6 +265,17 @@ def status(self, op): operation.response = serder.ked else: operation.done = False + + elif op.type in (OpTypes.delegable, ): + if op.oid not in self.hby.kevers: + raise kering.ValidationError(f"long running {op.type} operation identifier {op.oid} not found") + + gatepre = op.metadata['response'] + if gatepre in self.hby.kevers: + operation.done = True + operation.response = serder.ked + else: + operation.done = False elif op.type in (OpTypes.group, ): if "sn" not in op.metadata: diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index 267fe3f0..2b494fa6 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -9,6 +9,7 @@ import os from builtins import isinstance from dataclasses import asdict +import time import falcon from falcon import testing @@ -23,10 +24,12 @@ from keri import kering from keri.vdr import credentialing from hio.base import doing +import pytest -from keria.app import aiding, agenting -from keria.app.aiding import IdentifierOOBICollectionEnd, RpyEscrowCollectionEnd +from keria.app import aiding, agenting, delegating +from keria.app.aiding import ID_DELEGATOR_ROUTE, IdentifierOOBICollectionEnd, RpyEscrowCollectionEnd from keria.core import longrunning +from keria.end import ending from keria.testing.testing_helper import SCRIPTS_DIR @@ -959,6 +962,126 @@ def test_identifier_collection_end(helpers): assert res.status_code == 202 +def test_identifier_delegator_end(helpers): + torname = "delegator" + teename = "delegatee" + saltb = b"0123456789abcdef" + + with habbing.openHby(name="p1", temp=True) as hby, \ + helpers.openKeria() as (toragency, toragent, torapp, torclient): + + # Create Anchorer to test + anchorer = delegating.Anchorer(hby=hby) + + ending.loadEnds(app=torapp, agency=toragency) + + end = aiding.IdentifierCollectionEnd() + resend = aiding.IdentifierResourceEnd() + torapp.add_route("/identifiers", end) + torapp.add_route("/identifiers/{name}", resend) + torapp.add_route("/identifiers/{name}/events", resend) + torapp.add_route(ID_DELEGATOR_ROUTE, aiding.IdentifierDelegatorEnd()) + + salt = b"0123456789abcdef" + op = helpers.createAid(torclient, torname, salt) + aid = op["response"] + torpre = aid["i"] + assert torpre == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" + + endRolesEnd = aiding.EndRoleCollectionEnd() + torapp.add_route("/identifiers/{name}/endroles", endRolesEnd) + recp = aid['i'] + rpy = helpers.endrole(recp, toragent.agentHab.pre) + sigs = helpers.sign(salt, 0, 0, rpy.raw) + body = dict(rpy=rpy.ked, sigs=sigs) + + endres = torclient.simulate_post(path=f"/identifiers/{torname}/endroles", json=body) + op = endres.json + ked = op["response"] + serder = serdering.SerderKERI(sad=ked) + assert serder.raw == rpy.raw + + fakeproxy = hby.makeHab("proxy") + teehab = hby.makeHab(teename, delpre=torpre) + + # Use valid AID, role and EID + toroobi = torclient.simulate_get(path=f"/oobi/{torpre}/agent/{toragent.agentHab.pre}") + assert toroobi.status_code == 200 + assert toroobi.headers['Content-Type'] == "application/json+cesr" + + # Try before knowing delegator key state + with pytest.raises(kering.ValidationError): + anchorer.delegation(pre=teehab.pre, proxy=fakeproxy) + + teehab.psr.parse(ims=toroobi.content) + + # Doer hierarchy + doist = doing.Doist(tock=0.03125, real=True) + deeds = doist.enter(doers=([anchorer, toragent])) + + # Run delegation to escrow inception event + anchorer.delegation(pre=teehab.pre) + doist.recur(deeds=deeds) + + # normally postman would take care of this but we can do it manually here + teeser = teehab.kever.serder + for msg in teehab.db.clonePreIter(pre=teehab.pre): + parsing.Parser().parse(ims=bytearray(msg), kvy=toragent.kvy, local=True) # Local true otherwise it will be a misfit + + prefixer = coring.Prefixer(qb64=teehab.pre) + seqner = coring.Seqner(sn=0) + assert anchorer.complete(prefixer=prefixer, seqner=seqner) is False + + doist.recur(deeds=deeds) + assert teehab.pre not in toragent.agentHab.kevers + + # Anchor the seal in delegator's KEL, step 1 of approving the delegation + seal = dict(i=prefixer.qb64, s="0", d=prefixer.qb64) + iserder, isigers = helpers.interact(pre=aid["i"], bran=saltb, pidx=0, ridx=0, dig=aid["d"], sn='1', data=[seal]) + # body = {"ixn": iserder.ked, "sigs": isigers} + # ires = gatorclient.simulate_post( + # path=f"/identifiers/{gatorname}/events", body=json.dumps(body) + # ) + # Explicityly approve delegation, step 2 of approving the delegation + appDelBody = {"ixn": iserder.ked, "sigs": isigers} + apprDelRes = torclient.simulate_post(path=f"/identifiers/{torname}/delegator", body=json.dumps(appDelBody)) + assert apprDelRes.status_code == 200 + op = apprDelRes.json + assert op["metadata"]["response"] == iserder.ked['a'][0]['i'] + + opColEnd = longrunning.OperationCollectionEnd() + torapp.add_route("/operations", opColEnd) + opResEnd = longrunning.OperationResourceEnd() + torapp.add_route("/operations/{name}", opResEnd) + + count=0 + while not hasattr(op,'done') or not op.done: + time.sleep(1) + res = torclient.simulate_get(path=f"/operations/{op["name"]}") + assert res.status_code == 200 + op = res.json + count += 1 + if count > 10: + raise Exception("Delegator never processed the delegatee dip event") + + assert teehab.pre not in toragent.agentHab.kevers + + # update delegatee with delegator KEL w/ interaction event + toroobi = torclient.simulate_get(path=f"/oobi/{torpre}/agent/{toragent.agentHab.pre}") + teehab.psr.parse(ims=toroobi.content) + while anchorer.complete(prefixer=prefixer, seqner=seqner) is False: + doist.recur(deeds=deeds) + + assert teehab.pre not in toragent.agentHab.kevers + + doist.recur(deeds=deeds) + + # delegator recognizes the delegatee + while teehab.pre not in toragent.agentHab.kevers: + doist.recur(deeds=deeds) + + assert teehab.pre in toragent.agentHab.kevers + def test_challenge_ends(helpers): with helpers.openKeria() as (agency, agent, app, client): end = aiding.IdentifierCollectionEnd() From 9348519b1b1af8988e2822e1354fbe1346825112 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Mon, 3 Jun 2024 14:26:09 -0400 Subject: [PATCH 02/21] delegator endpoint processes dip as a long running processes. tests pass Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/core/longrunning.py | 2 +- tests/app/test_aiding.py | 25 +++++++------------------ tests/app/test_specing.py | 3 ++- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/keria/core/longrunning.py b/src/keria/core/longrunning.py index 0fb6d4c6..ce28b1b6 100644 --- a/src/keria/core/longrunning.py +++ b/src/keria/core/longrunning.py @@ -273,7 +273,7 @@ def status(self, op): gatepre = op.metadata['response'] if gatepre in self.hby.kevers: operation.done = True - operation.response = serder.ked + operation.response = op.metadata['response'] else: operation.done = False diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index 2b494fa6..04a94b2a 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -1038,11 +1038,6 @@ def test_identifier_delegator_end(helpers): # Anchor the seal in delegator's KEL, step 1 of approving the delegation seal = dict(i=prefixer.qb64, s="0", d=prefixer.qb64) iserder, isigers = helpers.interact(pre=aid["i"], bran=saltb, pidx=0, ridx=0, dig=aid["d"], sn='1', data=[seal]) - # body = {"ixn": iserder.ked, "sigs": isigers} - # ires = gatorclient.simulate_post( - # path=f"/identifiers/{gatorname}/events", body=json.dumps(body) - # ) - # Explicityly approve delegation, step 2 of approving the delegation appDelBody = {"ixn": iserder.ked, "sigs": isigers} apprDelRes = torclient.simulate_post(path=f"/identifiers/{torname}/delegator", body=json.dumps(appDelBody)) assert apprDelRes.status_code == 200 @@ -1055,7 +1050,8 @@ def test_identifier_delegator_end(helpers): torapp.add_route("/operations/{name}", opResEnd) count=0 - while not hasattr(op,'done') or not op.done: + while not op['done']: + doist.recur(deeds=deeds) time.sleep(1) res = torclient.simulate_get(path=f"/operations/{op["name"]}") assert res.status_code == 200 @@ -1064,23 +1060,16 @@ def test_identifier_delegator_end(helpers): if count > 10: raise Exception("Delegator never processed the delegatee dip event") - assert teehab.pre not in toragent.agentHab.kevers - + assert teehab.pre in toragent.agentHab.kevers + # update delegatee with delegator KEL w/ interaction event toroobi = torclient.simulate_get(path=f"/oobi/{torpre}/agent/{toragent.agentHab.pre}") teehab.psr.parse(ims=toroobi.content) + count = 0 while anchorer.complete(prefixer=prefixer, seqner=seqner) is False: doist.recur(deeds=deeds) - - assert teehab.pre not in toragent.agentHab.kevers - - doist.recur(deeds=deeds) - - # delegator recognizes the delegatee - while teehab.pre not in toragent.agentHab.kevers: - doist.recur(deeds=deeds) - - assert teehab.pre in toragent.agentHab.kevers + if count > 10: + raise Exception("Delegatee never saw the successful anchor") def test_challenge_ends(helpers): with helpers.openKeria() as (agency, agent, app, client): diff --git a/tests/app/test_specing.py b/tests/app/test_specing.py index 77115bd1..d38f90f3 100644 --- a/tests/app/test_specing.py +++ b/tests/app/test_specing.py @@ -46,7 +46,8 @@ def test_spec_resource(helpers): js = json.dumps(sd) print(js) # Assert on the entire JSON to ensure we are getting all the docs - assert js == """{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "array"}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign 'alias' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI", "required": false}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "parameters": [{"in": "body", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {}, "options": {}, "post": {}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "int"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/operations/{name}": {"delete": {}, "get": {}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {}, "put": {}}, "/identifiers/{name}": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/endroles/{aid}": {"get": {}, "post": {}}, "/escrows/rpy": {"get": {}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/oobi/{aid}": {"get": {}}, "/identifiers/{name}/events": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/identifiers/{name}/oobis": {"get": {}}, "/identifiers/{name}/endroles": {"get": {}, "post": {}}, "/identifiers/{name}/members": {"get": {}}, "/endroles/{aid}/{role}": {"get": {}, "post": {}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {}}, "/identifiers/{name}/endroles/{role}": {"get": {}, "post": {}}, "/oobi/{aid}/{role}/{eid}": {"get": {}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}""" + assert js == '{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "array"}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign \'alias\' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI", "required": false}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "parameters": [{"in": "body", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {}, "options": {}, "post": {}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "int"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/operations/{name}": {"delete": {}, "get": {}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {}, "put": {}}, "/identifiers/{name}": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/endroles/{aid}": {"get": {}, "post": {}}, "/escrows/rpy": {"get": {}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/oobi/{aid}": {"get": {}}, "/identifiers/{name}/delegator": {"post": {}}, "/identifiers/{name}/events": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/identifiers/{name}/oobis": {"get": {}}, "/identifiers/{name}/endroles": {"get": {}, "post": {}}, "/identifiers/{name}/members": {"get": {}}, "/endroles/{aid}/{role}": {"get": {}, "post": {}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {}}, "/identifiers/{name}/endroles/{role}": {"get": {}, "post": {}}, "/oobi/{aid}/{role}/{eid}": {"get": {}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}' + "" \ No newline at end of file From 042d55113884ac9c94a3f33da16790eec602355b Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Mon, 3 Jun 2024 14:53:47 -0400 Subject: [PATCH 03/21] small fixes to op checks Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/aiding.py | 2 +- tests/app/test_aiding.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index f1278cb0..60193823 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -448,7 +448,7 @@ class IdentifierDelegatorEnd: """ Resource class for for handling delegator events""" def on_post(self, req, rep, name): - """ Identifier delegator approval POST endpoint to add the anchor and approve the delegation + """ Identifier delegator enpoint POST to create the ixn anchor and approve the delegation Parameters: req (Request): falcon.Request HTTP request object diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index 04a94b2a..79d6668e 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -1050,10 +1050,10 @@ def test_identifier_delegator_end(helpers): torapp.add_route("/operations/{name}", opResEnd) count=0 - while not op['done']: + while not op or not "done" in op or not op["done"]: doist.recur(deeds=deeds) time.sleep(1) - res = torclient.simulate_get(path=f"/operations/{op["name"]}") + res = torclient.simulate_get(path=f'/operations/{op["name"]}') assert res.status_code == 200 op = res.json count += 1 From f589fab2e11429ecb69ad2ab0b1d61b405b3d4d8 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Mon, 3 Jun 2024 19:17:44 -0400 Subject: [PATCH 04/21] small fixes on checking dip success Signed-off-by: 2byrds <2byrds@gmail.com> --- tests/app/test_aiding.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index 79d6668e..09c2b9b6 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -1044,11 +1044,12 @@ def test_identifier_delegator_end(helpers): op = apprDelRes.json assert op["metadata"]["response"] == iserder.ked['a'][0]['i'] + assert teehab.pre not in toragent.agentHab.kevers + opColEnd = longrunning.OperationCollectionEnd() torapp.add_route("/operations", opColEnd) opResEnd = longrunning.OperationResourceEnd() torapp.add_route("/operations/{name}", opResEnd) - count=0 while not op or not "done" in op or not op["done"]: doist.recur(deeds=deeds) From 78cf6636fd834b65d5e51cfdf2b883008147c3d7 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Mon, 3 Jun 2024 19:30:47 -0400 Subject: [PATCH 05/21] remove unnecessary loop for interact operation Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/aiding.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index 60193823..8a66ab61 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -469,12 +469,7 @@ def on_post(self, req, rep, name): if body.get("ixn") is not None: iop = IdentifierResourceEnd.interact(agent, name, body) - while not hasattr(iop,'done') or not iop.done: - time.sleep(1) - iop = agent.monitor.status(iop) - - serder = serdering.SerderKERI(sad=iop.response) - gatepre = self.approveDelegation(agent, hab, serder.ked) + gatepre = self.approveDelegation(agent, hab, body.get("ixn")) adop = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.delegable, metadata=dict(response=gatepre, depends=iop)) From f9ab9a13448a824ba56726270720275aa8163318 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Mon, 3 Jun 2024 19:36:02 -0400 Subject: [PATCH 06/21] point to keri main for now, will use 1.2.0-dev5 when it's available Signed-off-by: 2byrds <2byrds@gmail.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0ce9edff..53918fbf 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ python_requires='>=3.12.2', install_requires=[ 'hio>=0.6.12', - 'keri==1.2.0-dev4', + 'keri @ git+https://github.com/WebOfTrust/keripy.git@main', 'mnemonic>=0.20', 'multicommand>=1.0.0', 'falcon>=3.1.3', From 2c6ce5f87efe16229cf6864c11c8673316b8726e Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Mon, 3 Jun 2024 20:31:43 -0400 Subject: [PATCH 07/21] refactored degator end to delegating.py and corresponding tests and used credentialing design for IdentifierResource end usage Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/agenting.py | 1 + src/keria/app/aiding.py | 68 ------------------- src/keria/app/delegating.py | 75 +++++++++++++++++++++ tests/app/test_aiding.py | 124 ++--------------------------------- tests/app/test_delegating.py | 117 ++++++++++++++++++++++++++++++++- tests/app/test_specing.py | 12 ++-- 6 files changed, 204 insertions(+), 193 deletions(-) diff --git a/src/keria/app/agenting.py b/src/keria/app/agenting.py index caae9c3f..4add8497 100644 --- a/src/keria/app/agenting.py +++ b/src/keria/app/agenting.py @@ -90,6 +90,7 @@ def setup(name, bran, adminPort, bootPort, base='', httpPort=None, configFile=No loadEnds(app=app) aidEnd = aiding.loadEnds(app=app, agency=agency, authn=authn) credentialing.loadEnds(app=app, identifierResource=aidEnd) + delegating.loadEnds(app=app, identifierResource=aidEnd) notifying.loadEnds(app=app) keriagrouping.loadEnds(app=app) keriaexchanging.loadEnds(app=app) diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index 8a66ab61..24f9743c 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -6,7 +6,6 @@ """ import json from dataclasses import asdict -import time from urllib.parse import urlparse, urljoin import falcon @@ -24,8 +23,6 @@ logger = ogler.getLogger() -ID_DELEGATOR_ROUTE = "/identifiers/{name}/delegator" - def loadEnds(app, agency, authn): groupEnd = AgentResourceEnd(agency=agency, authn=authn) app.add_route("/agent/{caid}", groupEnd) @@ -33,9 +30,6 @@ def loadEnds(app, agency, authn): aidsEnd = IdentifierCollectionEnd() app.add_route("/identifiers", aidsEnd) - gatorEnd = IdentifierDelegatorEnd() - app.add_route(ID_DELEGATOR_ROUTE, gatorEnd) - aidEnd = IdentifierResourceEnd() app.add_route("/identifiers/{name}", aidEnd) app.add_route("/identifiers/{name}/events", aidEnd) @@ -444,68 +438,6 @@ def on_post(req, rep): except (kering.AuthError, ValueError) as e: raise falcon.HTTPBadRequest(description=e.args[0]) -class IdentifierDelegatorEnd: - """ Resource class for for handling delegator events""" - - def on_post(self, req, rep, name): - """ Identifier delegator enpoint POST to create the ixn anchor and approve the delegation - - Parameters: - req (Request): falcon.Request HTTP request object - rep (Response): falcon.Response HTTP response object - name (str): human readable name for Hab to rename - - """ - if not name: - raise falcon.HTTPBadRequest(description="name is required") - agent = req.context.agent - hab = agent.hby.habByName(name) - - if hab is None: - raise falcon.HTTPNotFound(title=f"No AID with name {name} found") - - body = req.get_media() - - if body.get("ixn") is not None: - iop = IdentifierResourceEnd.interact(agent, name, body) - - gatepre = self.approveDelegation(agent, hab, body.get("ixn")) - adop = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.delegable, - metadata=dict(response=gatepre, depends=iop)) - - try: - rep.status = falcon.HTTP_200 - rep.content_type = "application/json" - rep.data = adop.to_json().encode("utf-8") - return rep - except (kering.AuthError, ValueError) as e: - raise falcon.HTTPBadRequest(description=e.args[0]) - - else: - raise falcon.HTTPBadRequest(title="invalid request", - description=f"required field 'ixn' missing from request") - - @staticmethod - def approveDelegation(agent, hab, ixn) -> str: - serder = serdering.SerderKERI(sad=ixn) - - gatePre = ixn['a'][0]['i'] - gateSaid = ixn['a'][0]['d'] - - for (pre, sn), dig in hab.db.delegables.getItemIter(): - if pre == gatePre: - seqner = coring.Seqner(sn=serder.sn) - couple = seqner.qb64b + serder.saidb - dgkey = dbing.dgKey(coring.Saider(qb64=gatePre).qb64b, coring.Saider(qb64=gateSaid).qb64b) - # the dip event should have been received from the delegatee - # and will be sitting in escrows (hence the hab.db.delegables above) - # adding the authorize event seal will allow the dip to be processed - # and added to the delegator kever - hab.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) - return gatePre - - raise falcon.HTTPBadRequest(title=f"No delegables found for delegator {hab.pre} to approve delegatee {gatePre}") - class IdentifierResourceEnd: """ Resource class for updating and deleting identifiers """ diff --git a/src/keria/app/delegating.py b/src/keria/app/delegating.py index 229036d6..d17a1dd1 100644 --- a/src/keria/app/delegating.py +++ b/src/keria/app/delegating.py @@ -1,9 +1,18 @@ +import falcon + from hio.base import doing from keri import kering from keri.app import forwarding, agenting, habbing from keri.core import coring, serdering from keri.db import dbing +from keria.core import httping, longrunning + +DELEGATOR_ROUTE = "/delegator/{name}" + +def loadEnds(app, identifierResource): + gatorEnd = DelegatorEnd(identifierResource) + app.add_route(DELEGATOR_ROUTE, gatorEnd) class Anchorer(doing.DoDoer): """ @@ -169,4 +178,70 @@ def processPartialWitnessEscrow(self): self.hby.db.dpwe.rem(keys=(pre, said)) self.hby.db.dune.pin(keys=(srdr.pre, srdr.said), val=srdr) + +class DelegatorEnd: + """ Resource class for for handling delegator events""" + + def __init__(self, identifierResource) -> None: + """ + + Parameters: + identifierResource (IdentifierResourceEnd): endpoint class for creating rotation and interaction events + """ + self.identifierResource = identifierResource + + def on_post(self, req, rep, name): + """ Identifier delegator enpoint POST to create the ixn anchor and approve the delegation + + Parameters: + req (Request): falcon.Request HTTP request object + rep (Response): falcon.Response HTTP response object + name (str): human readable name for Hab to rename + + """ + if not name: + raise falcon.HTTPBadRequest(description="name is required") + agent = req.context.agent + hab = agent.hby.habByName(name) + + if hab is None: + raise falcon.HTTPNotFound(title=f"No AID with name {name} found") + + body = req.get_media() + anc = httping.getRequiredParam(body, "ixn") + + iop = self.identifierResource.interact(agent, name, body) + + gatepre = self.approveDelegation(agent, hab, anc) + adop = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.delegable, + metadata=dict(response=gatepre, depends=iop)) + + try: + rep.status = falcon.HTTP_200 + rep.content_type = "application/json" + rep.data = adop.to_json().encode("utf-8") + return rep + except (kering.AuthError, ValueError) as e: + raise falcon.HTTPBadRequest(description=e.args[0]) + + @staticmethod + def approveDelegation(agent, hab, ixn) -> str: + serder = serdering.SerderKERI(sad=ixn) + + gatePre = ixn['a'][0]['i'] + gateSaid = ixn['a'][0]['d'] + + for (pre, sn), dig in hab.db.delegables.getItemIter(): + if pre == gatePre: + seqner = coring.Seqner(sn=serder.sn) + couple = seqner.qb64b + serder.saidb + dgkey = dbing.dgKey(coring.Saider(qb64=gatePre).qb64b, coring.Saider(qb64=gateSaid).qb64b) + # the dip event should have been received from the delegatee via a postman call + # and will be sitting in the delegator escrows (hence the hab.db.delegables above) + # adding the authorize event seal will allow the dip to be processed + # and added to the delegator kever + hab.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) + return gatePre + + raise falcon.HTTPBadRequest(title=f"No delegables found for delegator {hab.pre} to approve delegatee {gatePre}") \ No newline at end of file diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index 09c2b9b6..af29237c 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -5,11 +5,11 @@ Testing the Mark II Agent """ -import json -import os from builtins import isinstance from dataclasses import asdict -import time +import json +import os +import pytest import falcon from falcon import testing @@ -24,12 +24,11 @@ from keri import kering from keri.vdr import credentialing from hio.base import doing -import pytest -from keria.app import aiding, agenting, delegating -from keria.app.aiding import ID_DELEGATOR_ROUTE, IdentifierOOBICollectionEnd, RpyEscrowCollectionEnd + +from keria.app import aiding, agenting +from keria.app.aiding import IdentifierOOBICollectionEnd, RpyEscrowCollectionEnd from keria.core import longrunning -from keria.end import ending from keria.testing.testing_helper import SCRIPTS_DIR @@ -961,117 +960,6 @@ def test_identifier_collection_end(helpers): res = client.simulate_post(path="/identifiers", body=json.dumps(body)) assert res.status_code == 202 - -def test_identifier_delegator_end(helpers): - torname = "delegator" - teename = "delegatee" - saltb = b"0123456789abcdef" - - with habbing.openHby(name="p1", temp=True) as hby, \ - helpers.openKeria() as (toragency, toragent, torapp, torclient): - - # Create Anchorer to test - anchorer = delegating.Anchorer(hby=hby) - - ending.loadEnds(app=torapp, agency=toragency) - - end = aiding.IdentifierCollectionEnd() - resend = aiding.IdentifierResourceEnd() - torapp.add_route("/identifiers", end) - torapp.add_route("/identifiers/{name}", resend) - torapp.add_route("/identifiers/{name}/events", resend) - torapp.add_route(ID_DELEGATOR_ROUTE, aiding.IdentifierDelegatorEnd()) - - salt = b"0123456789abcdef" - op = helpers.createAid(torclient, torname, salt) - aid = op["response"] - torpre = aid["i"] - assert torpre == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" - - endRolesEnd = aiding.EndRoleCollectionEnd() - torapp.add_route("/identifiers/{name}/endroles", endRolesEnd) - recp = aid['i'] - rpy = helpers.endrole(recp, toragent.agentHab.pre) - sigs = helpers.sign(salt, 0, 0, rpy.raw) - body = dict(rpy=rpy.ked, sigs=sigs) - - endres = torclient.simulate_post(path=f"/identifiers/{torname}/endroles", json=body) - op = endres.json - ked = op["response"] - serder = serdering.SerderKERI(sad=ked) - assert serder.raw == rpy.raw - - fakeproxy = hby.makeHab("proxy") - teehab = hby.makeHab(teename, delpre=torpre) - - # Use valid AID, role and EID - toroobi = torclient.simulate_get(path=f"/oobi/{torpre}/agent/{toragent.agentHab.pre}") - assert toroobi.status_code == 200 - assert toroobi.headers['Content-Type'] == "application/json+cesr" - - # Try before knowing delegator key state - with pytest.raises(kering.ValidationError): - anchorer.delegation(pre=teehab.pre, proxy=fakeproxy) - - teehab.psr.parse(ims=toroobi.content) - - # Doer hierarchy - doist = doing.Doist(tock=0.03125, real=True) - deeds = doist.enter(doers=([anchorer, toragent])) - - # Run delegation to escrow inception event - anchorer.delegation(pre=teehab.pre) - doist.recur(deeds=deeds) - - # normally postman would take care of this but we can do it manually here - teeser = teehab.kever.serder - for msg in teehab.db.clonePreIter(pre=teehab.pre): - parsing.Parser().parse(ims=bytearray(msg), kvy=toragent.kvy, local=True) # Local true otherwise it will be a misfit - - prefixer = coring.Prefixer(qb64=teehab.pre) - seqner = coring.Seqner(sn=0) - assert anchorer.complete(prefixer=prefixer, seqner=seqner) is False - - doist.recur(deeds=deeds) - assert teehab.pre not in toragent.agentHab.kevers - - # Anchor the seal in delegator's KEL, step 1 of approving the delegation - seal = dict(i=prefixer.qb64, s="0", d=prefixer.qb64) - iserder, isigers = helpers.interact(pre=aid["i"], bran=saltb, pidx=0, ridx=0, dig=aid["d"], sn='1', data=[seal]) - appDelBody = {"ixn": iserder.ked, "sigs": isigers} - apprDelRes = torclient.simulate_post(path=f"/identifiers/{torname}/delegator", body=json.dumps(appDelBody)) - assert apprDelRes.status_code == 200 - op = apprDelRes.json - assert op["metadata"]["response"] == iserder.ked['a'][0]['i'] - - assert teehab.pre not in toragent.agentHab.kevers - - opColEnd = longrunning.OperationCollectionEnd() - torapp.add_route("/operations", opColEnd) - opResEnd = longrunning.OperationResourceEnd() - torapp.add_route("/operations/{name}", opResEnd) - count=0 - while not op or not "done" in op or not op["done"]: - doist.recur(deeds=deeds) - time.sleep(1) - res = torclient.simulate_get(path=f'/operations/{op["name"]}') - assert res.status_code == 200 - op = res.json - count += 1 - if count > 10: - raise Exception("Delegator never processed the delegatee dip event") - - assert teehab.pre in toragent.agentHab.kevers - - # update delegatee with delegator KEL w/ interaction event - toroobi = torclient.simulate_get(path=f"/oobi/{torpre}/agent/{toragent.agentHab.pre}") - teehab.psr.parse(ims=toroobi.content) - count = 0 - while anchorer.complete(prefixer=prefixer, seqner=seqner) is False: - doist.recur(deeds=deeds) - if count > 10: - raise Exception("Delegatee never saw the successful anchor") - def test_challenge_ends(helpers): with helpers.openKeria() as (agency, agent, app, client): end = aiding.IdentifierCollectionEnd() diff --git a/tests/app/test_delegating.py b/tests/app/test_delegating.py index bf353869..521e7e62 100644 --- a/tests/app/test_delegating.py +++ b/tests/app/test_delegating.py @@ -5,14 +5,18 @@ Testing the Mark II Agent Anchorer """ +import json +import time import pytest + from hio.base import doing from keri import kering from keri.app import habbing -from keri.core import coring, eventing - -from keria.app import delegating +from keri.core import coring, eventing, parsing, serdering +from keria.app import aiding, delegating +from keria.core import longrunning +from keria.end import ending def test_sealer(): with habbing.openHby(name="p1", temp=True) as hby: @@ -54,7 +58,114 @@ def test_sealer(): anchorer.complete(prefixer=prefixer, seqner=seqner, saider=saider) assert anchorer.complete(prefixer=prefixer, seqner=seqner) is True + +def test_delegator_end(helpers): + torname = "delegator" + teename = "delegatee" + saltb = b"0123456789abcdef" + + with habbing.openHby(name="p1", temp=True) as hby, \ + helpers.openKeria() as (toragency, toragent, torapp, torclient): + # Create Anchorer to test + anchorer = delegating.Anchorer(hby=hby) + + ending.loadEnds(app=torapp, agency=toragency) + + end = aiding.IdentifierCollectionEnd() + resend = aiding.IdentifierResourceEnd() + torapp.add_route("/identifiers", end) + torapp.add_route("/identifiers/{name}", resend) + torapp.add_route("/identifiers/{name}/events", resend) + torend = delegating.DelegatorEnd(resend) + torapp.add_route(delegating.DELEGATOR_ROUTE, torend) + + salt = b"0123456789abcdef" + op = helpers.createAid(torclient, torname, salt) + aid = op["response"] + torpre = aid["i"] + assert torpre == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" + + endRolesEnd = aiding.EndRoleCollectionEnd() + torapp.add_route("/identifiers/{name}/endroles", endRolesEnd) + recp = aid['i'] + rpy = helpers.endrole(recp, toragent.agentHab.pre) + sigs = helpers.sign(salt, 0, 0, rpy.raw) + body = dict(rpy=rpy.ked, sigs=sigs) + + endres = torclient.simulate_post(path=f"/identifiers/{torname}/endroles", json=body) + op = endres.json + ked = op["response"] + serder = serdering.SerderKERI(sad=ked) + assert serder.raw == rpy.raw + + fakeproxy = hby.makeHab("proxy") + teehab = hby.makeHab(teename, delpre=torpre) + + # Use valid AID, role and EID + toroobi = torclient.simulate_get(path=f"/oobi/{torpre}/agent/{toragent.agentHab.pre}") + assert toroobi.status_code == 200 + assert toroobi.headers['Content-Type'] == "application/json+cesr" + + # Try before knowing delegator key state + with pytest.raises(kering.ValidationError): + anchorer.delegation(pre=teehab.pre, proxy=fakeproxy) + teehab.psr.parse(ims=toroobi.content) + # Doer hierarchy + doist = doing.Doist(tock=0.03125, real=True) + deeds = doist.enter(doers=([anchorer, toragent])) + # Run delegation to escrow inception event + anchorer.delegation(pre=teehab.pre) + doist.recur(deeds=deeds) + + # normally postman would take care of this but we can do it manually here + teeser = teehab.kever.serder + for msg in teehab.db.clonePreIter(pre=teehab.pre): + parsing.Parser().parse(ims=bytearray(msg), kvy=toragent.kvy, local=True) # Local true otherwise it will be a misfit + + prefixer = coring.Prefixer(qb64=teehab.pre) + seqner = coring.Seqner(sn=0) + assert anchorer.complete(prefixer=prefixer, seqner=seqner) is False + + doist.recur(deeds=deeds) + assert teehab.pre not in toragent.agentHab.kevers + + # Anchor the seal in delegator's KEL, step 1 of approving the delegation + seal = dict(i=prefixer.qb64, s="0", d=prefixer.qb64) + iserder, isigers = helpers.interact(pre=aid["i"], bran=saltb, pidx=0, ridx=0, dig=aid["d"], sn='1', data=[seal]) + appDelBody = {"ixn": iserder.ked, "sigs": isigers} + apprDelRes = torclient.simulate_post(path=f"/delegator/{torname}", body=json.dumps(appDelBody)) + assert apprDelRes.status_code == 200 + op = apprDelRes.json + assert op["metadata"]["response"] == iserder.ked['a'][0]['i'] + + assert teehab.pre not in toragent.agentHab.kevers + + opColEnd = longrunning.OperationCollectionEnd() + torapp.add_route("/operations", opColEnd) + opResEnd = longrunning.OperationResourceEnd() + torapp.add_route("/operations/{name}", opResEnd) + count=0 + while not op or not "done" in op or not op["done"]: + doist.recur(deeds=deeds) + time.sleep(1) + res = torclient.simulate_get(path=f'/operations/{op["name"]}') + assert res.status_code == 200 + op = res.json + count += 1 + if count > 10: + raise Exception("Delegator never processed the delegatee dip event") + + assert teehab.pre in toragent.agentHab.kevers + + # update delegatee with delegator KEL w/ interaction event + toroobi = torclient.simulate_get(path=f"/oobi/{torpre}/agent/{toragent.agentHab.pre}") + teehab.psr.parse(ims=toroobi.content) + count = 0 + while anchorer.complete(prefixer=prefixer, seqner=seqner) is False: + doist.recur(deeds=deeds) + if count > 10: + raise Exception("Delegatee never saw the successful anchor") \ No newline at end of file diff --git a/tests/app/test_specing.py b/tests/app/test_specing.py index d38f90f3..961bb0db 100644 --- a/tests/app/test_specing.py +++ b/tests/app/test_specing.py @@ -1,6 +1,6 @@ import json -from keria.app import agenting, aiding, notifying, indirecting, specing +from keria.app import agenting, aiding, delegating, notifying, indirecting, specing from keria.end import ending @@ -9,10 +9,11 @@ def test_spec_resource(helpers): # Add all the endpoints similar to the agenting.setup function agenting.loadEnds(app) aiding.loadEnds(app, agency, authn=None) - notifying.loadEnds(app) + delegating.loadEnds(app=app, identifierResource=aiding.IdentifierResourceEnd()) ending.loadEnds(agency=agency, app=app) indirecting.loadEnds(agency=agency, app=app) - + notifying.loadEnds(app) + specRes = specing.AgentSpecResource(app, title='KERIA Interactive Web Interface API') sd = specRes.spec.to_dict() @@ -25,6 +26,7 @@ def test_spec_resource(helpers): assert "/challenges/{name}" in paths assert "/contacts/{prefix}" in paths assert "/contacts/{prefix}/img" in paths + assert delegating.DELEGATOR_ROUTE in paths assert "/events" in paths assert "/identifiers" in paths assert "/identifiers/{name}" in paths @@ -46,7 +48,9 @@ def test_spec_resource(helpers): js = json.dumps(sd) print(js) # Assert on the entire JSON to ensure we are getting all the docs - assert js == '{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "array"}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign \'alias\' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI", "required": false}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "parameters": [{"in": "body", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {}, "options": {}, "post": {}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "int"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/operations/{name}": {"delete": {}, "get": {}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {}, "put": {}}, "/identifiers/{name}": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/endroles/{aid}": {"get": {}, "post": {}}, "/escrows/rpy": {"get": {}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/oobi/{aid}": {"get": {}}, "/identifiers/{name}/delegator": {"post": {}}, "/identifiers/{name}/events": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/identifiers/{name}/oobis": {"get": {}}, "/identifiers/{name}/endroles": {"get": {}, "post": {}}, "/identifiers/{name}/members": {"get": {}}, "/endroles/{aid}/{role}": {"get": {}, "post": {}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {}}, "/identifiers/{name}/endroles/{role}": {"get": {}, "post": {}}, "/oobi/{aid}/{role}/{eid}": {"get": {}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}' + assert js == '{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "array"}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign \'alias\' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI", "required": false}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "parameters": [{"in": "body", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {}, "options": {}, "post": {}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "int"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/operations/{name}": {"delete": {}, "get": {}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {}, "put": {}}, "/identifiers/{name}": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/endroles/{aid}": {"get": {}, "post": {}}, "/escrows/rpy": {"get": {}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/delegator/{name}": {"post": {}}, "/oobi/{aid}": {"get": {}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/identifiers/{name}/events": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/identifiers/{name}/oobis": {"get": {}}, "/identifiers/{name}/endroles": {"get": {}, "post": {}}, "/identifiers/{name}/members": {"get": {}}, "/endroles/{aid}/{role}": {"get": {}, "post": {}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {}}, "/identifiers/{name}/endroles/{role}": {"get": {}, "post": {}}, "/oobi/{aid}/{role}/{eid}": {"get": {}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}' + + From f4de29d0246ad8972845461889b2a5a3e73fbf15 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Mon, 3 Jun 2024 22:38:35 -0400 Subject: [PATCH 08/21] consolidate to optype of 'delegation' and clean up tests Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/delegating.py | 29 ++++++++++--------- src/keria/core/longrunning.py | 53 ++++++++++++++++++----------------- tests/app/test_delegating.py | 33 +++++++++++----------- tests/app/test_specing.py | 2 +- 4 files changed, 59 insertions(+), 58 deletions(-) diff --git a/src/keria/app/delegating.py b/src/keria/app/delegating.py index d17a1dd1..ab2f62df 100644 --- a/src/keria/app/delegating.py +++ b/src/keria/app/delegating.py @@ -8,7 +8,7 @@ from keria.core import httping, longrunning -DELEGATOR_ROUTE = "/delegator/{name}" +DELEGATOR_ROUTE = "/delegation/{name}" def loadEnds(app, identifierResource): gatorEnd = DelegatorEnd(identifierResource) @@ -209,13 +209,14 @@ def on_post(self, req, rep, name): raise falcon.HTTPNotFound(title=f"No AID with name {name} found") body = req.get_media() - anc = httping.getRequiredParam(body, "ixn") - iop = self.identifierResource.interact(agent, name, body) + op = self.identifierResource.interact(agent, name, body) + anc = httping.getRequiredParam(body, "ixn") - gatepre = self.approveDelegation(agent, hab, anc) - adop = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.delegable, - metadata=dict(response=gatepre, depends=iop)) + # successful approval returns the delegatee prefix + teepre = self.approveDelegation(agent, hab, anc) + adop = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.delegation, + metadata=dict(teepre=teepre, depends=op)) try: rep.status = falcon.HTTP_200 @@ -226,22 +227,22 @@ def on_post(self, req, rep, name): raise falcon.HTTPBadRequest(description=e.args[0]) @staticmethod - def approveDelegation(agent, hab, ixn) -> str: - serder = serdering.SerderKERI(sad=ixn) + def approveDelegation(agent, hab, anc) -> str: + serder = serdering.SerderKERI(sad=anc) - gatePre = ixn['a'][0]['i'] - gateSaid = ixn['a'][0]['d'] + teepre = anc['a'][0]['i'] + teesaid = anc['a'][0]['d'] for (pre, sn), dig in hab.db.delegables.getItemIter(): - if pre == gatePre: + if pre == teepre: seqner = coring.Seqner(sn=serder.sn) couple = seqner.qb64b + serder.saidb - dgkey = dbing.dgKey(coring.Saider(qb64=gatePre).qb64b, coring.Saider(qb64=gateSaid).qb64b) + dgkey = dbing.dgKey(coring.Saider(qb64=teepre).qb64b, coring.Saider(qb64=teesaid).qb64b) # the dip event should have been received from the delegatee via a postman call # and will be sitting in the delegator escrows (hence the hab.db.delegables above) # adding the authorize event seal will allow the dip to be processed # and added to the delegator kever hab.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) - return gatePre + return teepre - raise falcon.HTTPBadRequest(title=f"No delegables found for delegator {hab.pre} to approve delegatee {gatePre}") \ No newline at end of file + raise falcon.HTTPBadRequest(title=f"No delegables found for delegator {hab.pre} to approve delegatee {teepre}") \ No newline at end of file diff --git a/src/keria/core/longrunning.py b/src/keria/core/longrunning.py index ce28b1b6..975177b0 100644 --- a/src/keria/core/longrunning.py +++ b/src/keria/core/longrunning.py @@ -18,10 +18,10 @@ from keri.help import helping # long running operation types -Typeage = namedtuple("Tierage", 'oobi witness delegable delegation group query registry credential endrole challenge exchange ' +Typeage = namedtuple("Tierage", 'oobi witness delegation group query registry credential endrole challenge exchange ' 'done') -OpTypes = Typeage(oobi="oobi", witness='witness', delegable='delegable', delegation='delegation', group='group', query='query', +OpTypes = Typeage(oobi="oobi", witness='witness', delegation='delegation', group='group', query='query', registry='registry', credential='credential', endrole='endrole', challenge='challenge', exchange='exchange', done='done') @@ -249,33 +249,34 @@ def status(self, op): if op.oid not in self.hby.kevers: raise kering.ValidationError(f"long running {op.type} operation identifier {op.oid} not found") - if "sn" not in op.metadata: - raise kering.ValidationError(f"invalid long running {op.type} operation, metadata missing 'sn' field") - kever = self.hby.kevers[op.oid] - sn = op.metadata["sn"] - seqner = coring.Seqner(sn=sn) - sdig = self.hby.db.getKeLast(key=dbing.snKey(pre=op.oid, sn=sn)) - - if self.swain.complete(kever.prefixer, seqner): - evt = self.hby.db.getEvt(dbing.dgKey(pre=kever.prefixer.qb64, dig=bytes(sdig))) - serder = serdering.SerderKERI(raw=bytes(evt)) - - operation.done = True - operation.response = serder.ked - else: - operation.done = False - - elif op.type in (OpTypes.delegable, ): - if op.oid not in self.hby.kevers: - raise kering.ValidationError(f"long running {op.type} operation identifier {op.oid} not found") + + reqsn = "sn" + reqtee = "teepre" + required = [reqsn, reqtee] + if reqsn in op.metadata: #delegatee detects successful delegation + sn = op.metadata["sn"] + seqner = coring.Seqner(sn=sn) + sdig = self.hby.db.getKeLast(key=dbing.snKey(pre=op.oid, sn=sn)) + + if self.swain.complete(kever.prefixer, seqner): + evt = self.hby.db.getEvt(dbing.dgKey(pre=kever.prefixer.qb64, dig=bytes(sdig))) + serder = serdering.SerderKERI(raw=bytes(evt)) - gatepre = op.metadata['response'] - if gatepre in self.hby.kevers: - operation.done = True - operation.response = op.metadata['response'] + operation.done = True + operation.response = serder.ked + else: + operation.done = False + elif reqtee in op.metadata: #delegator detects delegatee delegation success + teepre = op.metadata[reqtee] + # Once the delegatee dip is processed by the delegator, the + if teepre in self.hby.kevers: + operation.done = True + operation.response = op.metadata[reqtee] + else: + operation.done = False else: - operation.done = False + raise falcon.HTTPBadRequest(description=f"longrunning operation type {op.type} requires one of {required}, but are missing from request") elif op.type in (OpTypes.group, ): if "sn" not in op.metadata: diff --git a/tests/app/test_delegating.py b/tests/app/test_delegating.py index 521e7e62..2923c19a 100644 --- a/tests/app/test_delegating.py +++ b/tests/app/test_delegating.py @@ -70,8 +70,8 @@ def test_delegator_end(helpers): # Create Anchorer to test anchorer = delegating.Anchorer(hby=hby) + #setup agency endpoints ending.loadEnds(app=torapp, agency=toragency) - end = aiding.IdentifierCollectionEnd() resend = aiding.IdentifierResourceEnd() torapp.add_route("/identifiers", end) @@ -80,25 +80,14 @@ def test_delegator_end(helpers): torend = delegating.DelegatorEnd(resend) torapp.add_route(delegating.DELEGATOR_ROUTE, torend) + # Create delegator salt = b"0123456789abcdef" op = helpers.createAid(torclient, torname, salt) aid = op["response"] torpre = aid["i"] assert torpre == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" - endRolesEnd = aiding.EndRoleCollectionEnd() - torapp.add_route("/identifiers/{name}/endroles", endRolesEnd) - recp = aid['i'] - rpy = helpers.endrole(recp, toragent.agentHab.pre) - sigs = helpers.sign(salt, 0, 0, rpy.raw) - body = dict(rpy=rpy.ked, sigs=sigs) - - endres = torclient.simulate_post(path=f"/identifiers/{torname}/endroles", json=body) - op = endres.json - ked = op["response"] - serder = serdering.SerderKERI(sad=ked) - assert serder.raw == rpy.raw - + # setup delegatee fakeproxy = hby.makeHab("proxy") teehab = hby.makeHab(teename, delpre=torpre) @@ -111,6 +100,7 @@ def test_delegator_end(helpers): with pytest.raises(kering.ValidationError): anchorer.delegation(pre=teehab.pre, proxy=fakeproxy) + #introduce delegator to delegatee teehab.psr.parse(ims=toroobi.content) # Doer hierarchy @@ -126,24 +116,29 @@ def test_delegator_end(helpers): for msg in teehab.db.clonePreIter(pre=teehab.pre): parsing.Parser().parse(ims=bytearray(msg), kvy=toragent.kvy, local=True) # Local true otherwise it will be a misfit + # Delegatee hasn't seen delegator anchor prefixer = coring.Prefixer(qb64=teehab.pre) seqner = coring.Seqner(sn=0) assert anchorer.complete(prefixer=prefixer, seqner=seqner) is False + # Delegator still hasn't processed the delegatee dip event doist.recur(deeds=deeds) assert teehab.pre not in toragent.agentHab.kevers - # Anchor the seal in delegator's KEL, step 1 of approving the delegation + # Anchor the seal in delegator's KEL and approve the escrowed dip event seal = dict(i=prefixer.qb64, s="0", d=prefixer.qb64) iserder, isigers = helpers.interact(pre=aid["i"], bran=saltb, pidx=0, ridx=0, dig=aid["d"], sn='1', data=[seal]) appDelBody = {"ixn": iserder.ked, "sigs": isigers} - apprDelRes = torclient.simulate_post(path=f"/delegator/{torname}", body=json.dumps(appDelBody)) + apprDelRes = torclient.simulate_post(path=f"/delegation/{torname}", body=json.dumps(appDelBody)) assert apprDelRes.status_code == 200 op = apprDelRes.json - assert op["metadata"]["response"] == iserder.ked['a'][0]['i'] + assert op["metadata"]["teepre"] == iserder.ked['a'][0]['i'] + # Delegator still hasn't processed the delegatee dip event assert teehab.pre not in toragent.agentHab.kevers + # Monitor long running operation indicating escrowed delegatee + # dip event was successfully processed opColEnd = longrunning.OperationCollectionEnd() torapp.add_route("/operations", opColEnd) opResEnd = longrunning.OperationResourceEnd() @@ -159,8 +154,12 @@ def test_delegator_end(helpers): if count > 10: raise Exception("Delegator never processed the delegatee dip event") + # Delegator escrows completed and now aknowledges the delegatee dip event assert teehab.pre in toragent.agentHab.kevers + # Delegatee hasn't seen the anchor yet + assert anchorer.complete(prefixer=prefixer, seqner=seqner) is False + # update delegatee with delegator KEL w/ interaction event toroobi = torclient.simulate_get(path=f"/oobi/{torpre}/agent/{toragent.agentHab.pre}") teehab.psr.parse(ims=toroobi.content) diff --git a/tests/app/test_specing.py b/tests/app/test_specing.py index 961bb0db..e164aa5f 100644 --- a/tests/app/test_specing.py +++ b/tests/app/test_specing.py @@ -48,7 +48,7 @@ def test_spec_resource(helpers): js = json.dumps(sd) print(js) # Assert on the entire JSON to ensure we are getting all the docs - assert js == '{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "array"}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign \'alias\' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI", "required": false}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "parameters": [{"in": "body", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {}, "options": {}, "post": {}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "int"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/operations/{name}": {"delete": {}, "get": {}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {}, "put": {}}, "/identifiers/{name}": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/endroles/{aid}": {"get": {}, "post": {}}, "/escrows/rpy": {"get": {}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/delegator/{name}": {"post": {}}, "/oobi/{aid}": {"get": {}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/identifiers/{name}/events": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/identifiers/{name}/oobis": {"get": {}}, "/identifiers/{name}/endroles": {"get": {}, "post": {}}, "/identifiers/{name}/members": {"get": {}}, "/endroles/{aid}/{role}": {"get": {}, "post": {}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {}}, "/identifiers/{name}/endroles/{role}": {"get": {}, "post": {}}, "/oobi/{aid}/{role}/{eid}": {"get": {}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}' + assert js == '{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "array"}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign \'alias\' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI", "required": false}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "parameters": [{"in": "body", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {}, "options": {}, "post": {}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "int"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/operations/{name}": {"delete": {}, "get": {}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {}, "put": {}}, "/identifiers/{name}": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/endroles/{aid}": {"get": {}, "post": {}}, "/escrows/rpy": {"get": {}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/delegation/{name}": {"post": {}}, "/oobi/{aid}": {"get": {}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/identifiers/{name}/events": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/identifiers/{name}/oobis": {"get": {}}, "/identifiers/{name}/endroles": {"get": {}, "post": {}}, "/identifiers/{name}/members": {"get": {}}, "/endroles/{aid}/{role}": {"get": {}, "post": {}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {}}, "/identifiers/{name}/endroles/{role}": {"get": {}, "post": {}}, "/oobi/{aid}/{role}/{eid}": {"get": {}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}' From 414744f1e7a436fd1cf69f2022f9156f0479648e Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Tue, 4 Jun 2024 00:27:44 -0400 Subject: [PATCH 09/21] improve naming to be consistent with credentialing, and tested with signify-ts delegation updates Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/delegating.py | 4 ++-- tests/app/test_delegating.py | 4 ++-- tests/app/test_specing.py | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/keria/app/delegating.py b/src/keria/app/delegating.py index ab2f62df..45d80c97 100644 --- a/src/keria/app/delegating.py +++ b/src/keria/app/delegating.py @@ -8,11 +8,11 @@ from keria.core import httping, longrunning -DELEGATOR_ROUTE = "/delegation/{name}" +DELEGATION_ROUTE = "/identifiers/{name}/delegation" def loadEnds(app, identifierResource): gatorEnd = DelegatorEnd(identifierResource) - app.add_route(DELEGATOR_ROUTE, gatorEnd) + app.add_route(DELEGATION_ROUTE, gatorEnd) class Anchorer(doing.DoDoer): """ diff --git a/tests/app/test_delegating.py b/tests/app/test_delegating.py index 2923c19a..01b44387 100644 --- a/tests/app/test_delegating.py +++ b/tests/app/test_delegating.py @@ -78,7 +78,7 @@ def test_delegator_end(helpers): torapp.add_route("/identifiers/{name}", resend) torapp.add_route("/identifiers/{name}/events", resend) torend = delegating.DelegatorEnd(resend) - torapp.add_route(delegating.DELEGATOR_ROUTE, torend) + torapp.add_route(delegating.DELEGATION_ROUTE, torend) # Create delegator salt = b"0123456789abcdef" @@ -129,7 +129,7 @@ def test_delegator_end(helpers): seal = dict(i=prefixer.qb64, s="0", d=prefixer.qb64) iserder, isigers = helpers.interact(pre=aid["i"], bran=saltb, pidx=0, ridx=0, dig=aid["d"], sn='1', data=[seal]) appDelBody = {"ixn": iserder.ked, "sigs": isigers} - apprDelRes = torclient.simulate_post(path=f"/delegation/{torname}", body=json.dumps(appDelBody)) + apprDelRes = torclient.simulate_post(path=f"/identifiers/{torname}/delegation", body=json.dumps(appDelBody)) assert apprDelRes.status_code == 200 op = apprDelRes.json assert op["metadata"]["teepre"] == iserder.ked['a'][0]['i'] diff --git a/tests/app/test_specing.py b/tests/app/test_specing.py index e164aa5f..7b9e62a4 100644 --- a/tests/app/test_specing.py +++ b/tests/app/test_specing.py @@ -26,7 +26,7 @@ def test_spec_resource(helpers): assert "/challenges/{name}" in paths assert "/contacts/{prefix}" in paths assert "/contacts/{prefix}/img" in paths - assert delegating.DELEGATOR_ROUTE in paths + assert delegating.DELEGATION_ROUTE in paths assert "/events" in paths assert "/identifiers" in paths assert "/identifiers/{name}" in paths @@ -48,7 +48,8 @@ def test_spec_resource(helpers): js = json.dumps(sd) print(js) # Assert on the entire JSON to ensure we are getting all the docs - assert js == '{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "array"}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign \'alias\' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI", "required": false}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "parameters": [{"in": "body", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {}, "options": {}, "post": {}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "int"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/operations/{name}": {"delete": {}, "get": {}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {}, "put": {}}, "/identifiers/{name}": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/endroles/{aid}": {"get": {}, "post": {}}, "/escrows/rpy": {"get": {}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/delegation/{name}": {"post": {}}, "/oobi/{aid}": {"get": {}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/identifiers/{name}/events": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/identifiers/{name}/oobis": {"get": {}}, "/identifiers/{name}/endroles": {"get": {}, "post": {}}, "/identifiers/{name}/members": {"get": {}}, "/endroles/{aid}/{role}": {"get": {}, "post": {}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {}}, "/identifiers/{name}/endroles/{role}": {"get": {}, "post": {}}, "/oobi/{aid}/{role}/{eid}": {"get": {}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}' + assert js == '{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "array"}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign \'alias\' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI", "required": false}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "parameters": [{"in": "body", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {}, "options": {}, "post": {}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "int"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/operations/{name}": {"delete": {}, "get": {}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {}, "put": {}}, "/identifiers/{name}": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/endroles/{aid}": {"get": {}, "post": {}}, "/escrows/rpy": {"get": {}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/oobi/{aid}": {"get": {}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/identifiers/{name}/events": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/identifiers/{name}/oobis": {"get": {}}, "/identifiers/{name}/endroles": {"get": {}, "post": {}}, "/identifiers/{name}/members": {"get": {}}, "/identifiers/{name}/delegation": {"post": {}}, "/endroles/{aid}/{role}": {"get": {}, "post": {}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {}}, "/identifiers/{name}/endroles/{role}": {"get": {}, "post": {}}, "/oobi/{aid}/{role}/{eid}": {"get": {}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}' + From c900b854d76864ef3b7490d43d00c61671a44f19 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Fri, 7 Jun 2024 16:24:43 -0400 Subject: [PATCH 10/21] fixes and tests rotation. also tested with signify-ts integration tests that do rotation Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/aiding.py | 6 +- src/keria/testing/testing_helper.py | 29 +++++ tests/app/test_aiding.py | 178 +++++++++++++++++++++------- 3 files changed, 166 insertions(+), 47 deletions(-) diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index 24f9743c..d17a48bb 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -360,9 +360,9 @@ def on_post(req, rep): states = httping.getRequiredParam(body, "smids") rstates = httping.getRequiredParam(body, "rmids") - smids = [state['i'] for state in states] - rmids = [rstate['i'] for rstate in rstates] - hab = agent.hby.makeSignifyGroupHab(name, mhab=mhab, smids=smids, rmids=rmids, serder=serder, + # smids = [state['i'] for state in states] + # rmids = [rstate['i'] for rstate in rstates] + hab = agent.hby.makeSignifyGroupHab(name, mhab=mhab, smids=states, rmids=rstates, serder=serder, sigers=sigers) try: agent.inceptGroup(pre=serder.pre, mpre=mhab.pre, verfers=verfers, digers=digers) diff --git a/src/keria/testing/testing_helper.py b/src/keria/testing/testing_helper.py index 42a122e4..444d1f6f 100644 --- a/src/keria/testing/testing_helper.py +++ b/src/keria/testing/testing_helper.py @@ -422,6 +422,35 @@ def interact(pre, bran, pidx, ridx, sn, dig, data): signers = creator.create(pidx=pidx, ridx=ridx, tier=coring.Tiers.low, temp=False, count=1) sigers = [signer.sign(ser=serder.raw, index=0).qb64 for signer in signers] return serder, sigers + + @staticmethod + def createRotate(aid, salt, signers, pidx, ridx, kidx, wits, toad): + salter = core.Salter(raw=salt) + creator = keeping.SaltyCreator(salt=salter.qb64, stem="signify:aid", tier=coring.Tiers.low) + encrypter = core.Encrypter(verkey=signers[0].verfer.qb64) + sxlt = encrypter.encrypt(salter.qb64).qb64 + + rsigners = creator.create(pidx=pidx, ridx=ridx, tier=coring.Tiers.low, temp=False, count=1) + rnsigners = creator.create(pidx=pidx, ridx=ridx+1, tier=coring.Tiers.low, temp=False, count=1) + + rkeys = [signer.verfer.qb64 for signer in rsigners] + rndigs = [coring.Diger(ser=nsigner.verfer.qb64b) for nsigner in rnsigners] + + serder = eventing.rotate(pre=aid["prefix"], + keys=rkeys, + dig=aid["prefix"], + ndigs=[diger.qb64 for diger in rndigs], + wits=wits, + toad=toad + ) + sigers = [signer.sign(ser=serder.raw, index=0).qb64 for signer in rsigners] + body = { + 'rot': serder.ked, + 'sigs': sigers, + 'salty': {'stem': 'signify:aid', 'pidx': pidx, 'tier': 'low', 'sxlt': sxlt, 'transferable': True, 'kidx': kidx, + 'icodes': [MtrDex.Ed25519_Seed], 'ncodes': [MtrDex.Ed25519_Seed]} + } + return body @staticmethod def sign(bran, pidx, ridx, ser): diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index af29237c..f07e0c95 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -1565,62 +1565,152 @@ def test_approve_delegation(helpers): def test_rotation(helpers): - caid = "ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose" salt = b'0123456789abcdef' salter = core.Salter(raw=salt) - cf = configing.Configer(name="keria", headDirPath=SCRIPTS_DIR, temp=True, reopen=True, clear=False) - with habbing.openHby(name="keria", salt=salter.qb64, temp=True, cf=cf) as hby: - hab = hby.makeHab(name="test") - agency = agenting.Agency(name="agency", bran=None, temp=True) - agentHab = hby.makeHab(caid, ns="agent", transferable=True, data=[caid]) + with helpers.openKeria() as (agency, agent, app, client), \ + habbing.openHby(name="p1", temp=True, salt=salter.qb64) as p1hby, \ + habbing.openHby(name="p2", temp=True, salt=salter.qb64) as p2hby: + end = aiding.IdentifierCollectionEnd() + resend = aiding.IdentifierResourceEnd() + app.add_route("/identifiers", end) + app.add_route("/identifiers/{name}", resend) + app.add_route("/identifiers/{name}/events", resend) - rgy = credentialing.Regery(hby=hby, name=agentHab.name, base=hby.base, temp=True) - agent = agenting.Agent(hby=hby, rgy=rgy, agentHab=agentHab, agency=agency, caid=caid) + groupEnd = aiding.GroupMemberCollectionEnd() + app.add_route("/identifiers/{name}/members", groupEnd) - doist = doing.Doist(limit=1.0, tock=0.03125, real=True) - doist.enter(doers=[agency]) + opColEnd = longrunning.OperationCollectionEnd() + app.add_route("/operations", opColEnd) + opResEnd = longrunning.OperationResourceEnd() + app.add_route("/operations/{name}", opResEnd) - end = agenting.KeyStateCollectionEnd() + client = testing.TestClient(app) - app = falcon.App() - app.add_middleware(helpers.middleware(agent)) - app.add_route("/states", end) + salt = b'0123456789abcdef' + serder1, signers1 = helpers.incept(salt, "signify:aid", pidx=0) + assert len(signers1) == 1 - client = testing.TestClient(app) + sigers1 = [signer.sign(ser=serder1.raw, index=0).qb64 for signer in signers1] - bran = b'abcdefghijk0123456789' - serder, signers = helpers.incept(bran=bran, stem="signify:controller", pidx=0) - sigers = [signers[0].sign(ser=serder.raw, index=0)] - controllerAID = "EEnUbC_nMt-2uGQMp7jq_xUS9nc8SQ0q7eX_w49CG7jb" - assert serder.pre == controllerAID + salter = core.Salter(raw=salt) + encrypter = core.Encrypter(verkey=signers1[0].verfer.qb64) + sxlt = encrypter.encrypt(salter.qb64).qb64 - bootEnd = agenting.BootEnd(agency) - app.add_route("/boot", bootEnd) - agentEnd = aiding.AgentResourceEnd(agency=agency, authn=None) - app.add_route("/agent/{caid}", agentEnd) - end = aiding.IdentifierCollectionEnd() - app.add_route("/identifiers", end) - idResEnd = aiding.IdentifierResourceEnd() - app.add_route("/identifiers/{name}", idResEnd) + bodyid1 = {'name': 'aid1', + 'icp': serder1.ked, + 'sigs': sigers1, + "salty": { + 'stem': 'signify:aid', 'pidx': 0, 'tier': 'low', 'sxlt': sxlt, 'transferable': True, 'kidx': 0, + 'icodes': [MtrDex.Ed25519_Seed], 'ncodes': [MtrDex.Ed25519_Seed]} + } - body = dict( - icp=serder.ked, - sig=sigers[0].qb64, - salty=dict( - stem='signify:aid', pidx=0, tier='low', sxlt='OBXYZ', - icodes=[MtrDex.Ed25519_Seed], ncodes=[MtrDex.Ed25519_Seed] - ) - ) + res = client.simulate_post(path="/identifiers", body=json.dumps(bodyid1)) + assert res.status_code == 202 - rep = client.simulate_post("/boot", body=json.dumps(body).encode("utf-8")) - assert rep.status_code == 202 + res = client.simulate_get(path="/identifiers") + assert res.status_code == 200 + assert len(res.json) == 1 + aid = res.json[0] + assert aid["name"] == "aid1" + assert aid["prefix"] == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" - res = client.simulate_get(path=f"/agent/{serder.pre}") + serder2, signers2 = helpers.incept(salt, "signify:aid", pidx=1, count=3) + sigers2 = [signer.sign(ser=serder2.raw, index=0).qb64 for signer in signers2] + + bodyid2 = {'name': 'aid2', + 'icp': serder2.ked, + 'sigs': sigers2, + 'salty': {'stem': 'signify:aid', 'pidx': 1, 'tier': 'low', 'sxlt': sxlt, + 'icodes': [MtrDex.Ed25519_Seed], 'ncodes': [MtrDex.Ed25519_Seed]}} + res = client.simulate_post(path="/identifiers", body=json.dumps(bodyid2)) + assert res.status_code == 202 + + res = client.simulate_get(path="/identifiers") assert res.status_code == 200 - ctrl = res.json["controller"] - assert ctrl["state"]["i"] == controllerAID + assert len(res.json) == 2 + aid1 = res.json[0] + assert aid1["name"] == "aid1" + assert aid1["prefix"] == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" + ss = aid1[Algos.salty] + assert ss["pidx"] == 0 - op = helpers.createAid(client, name="salty_aid", salt=bran) - aid = op["response"] - assert aid['i'] == "EKYCAqyMMllSeGowQJUGMbRJpvLnhNMbF1qEIPCSpmOM" + aid2 = res.json[1] + assert aid2["name"] == "aid2" + assert aid2["prefix"] == "ECL8abFVW_0RTZXFhiiA4rkRobNvjTfJ6t-T8UdBRV1e" + ss = aid2[Algos.salty] + assert ss["pidx"] == 1 + + # Rotate aid1 + bodyrot1 = helpers.createRotate(aid1, salt, signers1, pidx=0, ridx=1, kidx=1, wits=[], toad=0) + res = client.simulate_post(path=f"/identifiers/{aid1['name']}/events", body=json.dumps(bodyrot1)) + assert res.status_code == 200 + + # Try with missing arguments + bodybad = { + 'rot': serder1.ked, + 'sigs': sigers1, + 'salty': {'stem': 'signify:aid', 'pidx': 0, 'tier': 'low', 'sxlt': sxlt, + 'icodes': [MtrDex.Ed25519_Seed], 'ncodes': [MtrDex.Ed25519_Seed]} + } + res = client.simulate_post(path="/identifiers/aid1/events", body=json.dumps(bodybad)) + assert res.status_code == 500 + + # Test with witnesses + url = "http://127.0.0.1:9999" + agent.hby.db.locs.put(keys=("BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha", kering.Schemes.http), + val=basing.LocationRecord(url=url)) + agent.hby.db.locs.put(keys=("BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM", kering.Schemes.http), + val=basing.LocationRecord(url=url)) + agent.hby.db.locs.put(keys=("BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX", kering.Schemes.http), + val=basing.LocationRecord(url=url)) + wits3 = ["BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha", + "BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM", + "BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX", ] + toad3 = "2" + serder3, signers3 = helpers.incept(salt, "signify:aid", pidx=3, + wits=wits3, + toad=toad3) + sigers3 = [signer.sign(ser=serder3.raw, index=0).qb64 for signer in signers3] + + bodyid3 = {'name': 'aid3', + 'icp': serder3.ked, + 'sigs': sigers3, + 'salty': {'stem': 'signify:aid', 'pidx': 3, 'tier': 'low', 'sxlt': sxlt, + 'icodes': [MtrDex.Ed25519_Seed], 'ncodes': [MtrDex.Ed25519_Seed]}} + + res = client.simulate_post(path="/identifiers", body=json.dumps(bodyid3)) + assert res.status_code == 202 + + op = res.json + name = op['name'] + + res = client.simulate_get(path=f"/operations/{name}") + assert res.status_code == 200 + assert res.json['done'] is False + + assert len(agent.witners) == 1 + res = client.simulate_get(path="/identifiers") + assert res.status_code == 200 + assert len(res.json) == 3 + aid3 = res.json[2] + assert aid3["name"] == "aid3" + assert aid3["prefix"] == serder3.pre + ss = aid3[Algos.salty] + assert ss["pidx"] == 3 + + # Add fake witness receipts to test satified witnessing + dgkey = dbing.dgKey(serder3.preb, serder3.preb) + agent.hby.db.putWigs(dgkey, vals=[b'A', b'B', b'C']) + res = client.simulate_get(path=f"/operations/{name}") + assert res.status_code == 200 + assert res.json['done'] is True + + res = client.simulate_get(path=f"/identifiers/{aid1['name']}") + mhab = res.json + agent0 = mhab["state"] + + # rotate aid3 + body = helpers.createRotate(aid=aid3, salt=salt, signers=signers3, pidx=3, ridx=1, kidx=3, wits=wits3, toad=toad3) + res = client.simulate_post(path=f"/identifiers/{aid3['name']}/events", body=json.dumps(body)) + assert res.status_code == 200 \ No newline at end of file From 48046fd766a3663fd4e2dbef96104f02464d507b Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Wed, 12 Jun 2024 15:13:06 -0400 Subject: [PATCH 11/21] fixed group end smids/rmids Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/aiding.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index d17a48bb..79ae22d0 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -1543,13 +1543,15 @@ def on_get(req, rep, name): signing = [] for smid in smids: - ends = hab.endsFor(smid) - signing.append(dict(aid=smid, ends=ends)) + spre = smid['i'] + ends = hab.endsFor(spre) + signing.append(dict(aid=spre, ends=ends)) rotation = [] for rmid in rmids: - ends = hab.endsFor(rmid) - rotation.append(dict(aid=rmid, ends=ends)) + rpre = rmid['i'] + ends = hab.endsFor(rpre) + rotation.append(dict(aid=rpre, ends=ends)) data = dict(signing=signing, rotation=rotation) rep.status = falcon.HTTP_200 From 6475a17c88d6f58eac4b3fb81a6318c34bad8d40 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Wed, 12 Jun 2024 15:38:23 -0400 Subject: [PATCH 12/21] fix smid list in multisig request on_post Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/grouping.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/keria/app/grouping.py b/src/keria/app/grouping.py index 3b7ae821..f59695f9 100644 --- a/src/keria/app/grouping.py +++ b/src/keria/app/grouping.py @@ -70,7 +70,8 @@ def on_post(req, rep, name): # now get rid of the event so we can pass it as atc to send del ims[:serder.size] - smids = hab.db.signingMembers(pre=hab.pre) + slist = hab.db.signingMembers(pre=hab.pre) + smids = [d['i'] for d in slist if 'i' in d] smids.remove(hab.mhab.pre) agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=smids, topic='multisig')) From 4a12a941fcc86a648329d6726396012a489bd58b Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Thu, 13 Jun 2024 05:55:59 -0400 Subject: [PATCH 13/21] fix for intermittent smid/rmid dict or str Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/aiding.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index 79ae22d0..dcb45e98 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -1543,15 +1543,17 @@ def on_get(req, rep, name): signing = [] for smid in smids: - spre = smid['i'] - ends = hab.endsFor(spre) - signing.append(dict(aid=spre, ends=ends)) + if isinstance(smid,dict): + smid = smid['i'] + ends = hab.endsFor(smid) + signing.append(dict(aid=smid, ends=ends)) rotation = [] for rmid in rmids: - rpre = rmid['i'] - ends = hab.endsFor(rpre) - rotation.append(dict(aid=rpre, ends=ends)) + if isinstance(rmid,dict): + rmid = rmid['i'] + ends = hab.endsFor(rmid) + rotation.append(dict(aid=rmid, ends=ends)) data = dict(signing=signing, rotation=rotation) rep.status = falcon.HTTP_200 From f41c79f679c0af61ce62e5664c9896eaf454405d Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Thu, 13 Jun 2024 08:19:43 -0400 Subject: [PATCH 14/21] moving approval into longrunning operation, need to make approveDelegation idempotent Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/delegating.py | 48 ++++++++++++++++++----------------- src/keria/core/longrunning.py | 9 +++++-- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/keria/app/delegating.py b/src/keria/app/delegating.py index 45d80c97..557e52f9 100644 --- a/src/keria/app/delegating.py +++ b/src/keria/app/delegating.py @@ -1,3 +1,4 @@ +import time import falcon from hio.base import doing @@ -209,14 +210,15 @@ def on_post(self, req, rep, name): raise falcon.HTTPNotFound(title=f"No AID with name {name} found") body = req.get_media() - - op = self.identifierResource.interact(agent, name, body) anc = httping.getRequiredParam(body, "ixn") + if not agent.hby.db.findAnchoringSealEvent(hab.pre, seal=anc): + op = self.identifierResource.interact(agent, name, body) + # successful approval returns the delegatee prefix - teepre = self.approveDelegation(agent, hab, anc) + teepre = approveDelegation(hab, anc) adop = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.delegation, - metadata=dict(teepre=teepre, depends=op)) + metadata=dict(teepre=teepre, anchor=anc, depends=op)) try: rep.status = falcon.HTTP_200 @@ -226,23 +228,23 @@ def on_post(self, req, rep, name): except (kering.AuthError, ValueError) as e: raise falcon.HTTPBadRequest(description=e.args[0]) - @staticmethod - def approveDelegation(agent, hab, anc) -> str: - serder = serdering.SerderKERI(sad=anc) +@staticmethod +def approveDelegation(hab, anc) -> str: + serder = serdering.SerderKERI(sad=anc) + + teepre = anc['a'][0]['i'] + teesaid = anc['a'][0]['d'] + + for (pre, sn), dig in hab.db.delegables.getItemIter(): + if pre == teepre: + seqner = coring.Seqner(sn=serder.sn) + couple = seqner.qb64b + serder.saidb + dgkey = dbing.dgKey(coring.Saider(qb64=teepre).qb64b, coring.Saider(qb64=teesaid).qb64b) + # the dip event should have been received from the delegatee via a postman call + # and will be sitting in the delegator escrows (hence the hab.db.delegables above) + # adding the authorize event seal will allow the dip to be processed + # and added to the delegator kever + hab.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) - teepre = anc['a'][0]['i'] - teesaid = anc['a'][0]['d'] - - for (pre, sn), dig in hab.db.delegables.getItemIter(): - if pre == teepre: - seqner = coring.Seqner(sn=serder.sn) - couple = seqner.qb64b + serder.saidb - dgkey = dbing.dgKey(coring.Saider(qb64=teepre).qb64b, coring.Saider(qb64=teesaid).qb64b) - # the dip event should have been received from the delegatee via a postman call - # and will be sitting in the delegator escrows (hence the hab.db.delegables above) - # adding the authorize event seal will allow the dip to be processed - # and added to the delegator kever - hab.db.setAes(dgkey, couple) # authorizer event seal (delegator/issuer) - return teepre - - raise falcon.HTTPBadRequest(title=f"No delegables found for delegator {hab.pre} to approve delegatee {teepre}") \ No newline at end of file + return teepre + # raise falcon.HTTPBadRequest(title=f"No delegables found for delegator {hab.pre} to approve delegatee {teepre}") \ No newline at end of file diff --git a/src/keria/core/longrunning.py b/src/keria/core/longrunning.py index 975177b0..7f169574 100644 --- a/src/keria/core/longrunning.py +++ b/src/keria/core/longrunning.py @@ -17,6 +17,8 @@ from keri.db import dbing, koming from keri.help import helping +from keria.app import delegating + # long running operation types Typeage = namedtuple("Tierage", 'oobi witness delegation group query registry credential endrole challenge exchange ' 'done') @@ -253,6 +255,7 @@ def status(self, op): reqsn = "sn" reqtee = "teepre" + anchor = "anchor" required = [reqsn, reqtee] if reqsn in op.metadata: #delegatee detects successful delegation sn = op.metadata["sn"] @@ -269,11 +272,13 @@ def status(self, op): operation.done = False elif reqtee in op.metadata: #delegator detects delegatee delegation success teepre = op.metadata[reqtee] - # Once the delegatee dip is processed by the delegator, the - if teepre in self.hby.kevers: + anc = op.metadata[anchor] + if teepre in self.hby.kevers: # delegatee dip has been processed by the delegator operation.done = True operation.response = op.metadata[reqtee] else: + hab = self.hby.habByPre(kever.prefixer.qb64) + delegating.approveDelegation(hab,anc) operation.done = False else: raise falcon.HTTPBadRequest(description=f"longrunning operation type {op.type} requires one of {required}, but are missing from request") From 32fc74fae03f1af9fe2e8af59c8e043c19402d86 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Thu, 13 Jun 2024 16:33:47 -0400 Subject: [PATCH 15/21] successful delegated multisig integration testing with Signify-ts Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/core/longrunning.py | 3 --- tests/app/test_specing.py | 9 +-------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/keria/core/longrunning.py b/src/keria/core/longrunning.py index 7543f401..dec4b0fc 100644 --- a/src/keria/core/longrunning.py +++ b/src/keria/core/longrunning.py @@ -262,9 +262,6 @@ def status(self, op): seqner = coring.Seqner(sn=sn) sdig = self.hby.db.getKeLast(key=dbing.snKey(pre=op.oid, sn=sn)) - if self.swain.complete(kever.prefixer, seqner): - evt = self.hby.db.getEvt(dbing.dgKey(pre=kever.prefixer.qb64, dig=bytes(sdig))) - serder = serdering.SerderKERI(raw=bytes(evt)) if self.swain.complete(kever.prefixer, seqner): evt = self.hby.db.getEvt(dbing.dgKey(pre=kever.prefixer.qb64, dig=bytes(sdig))) serder = serdering.SerderKERI(raw=bytes(evt)) diff --git a/tests/app/test_specing.py b/tests/app/test_specing.py index fa34a847..f6331690 100644 --- a/tests/app/test_specing.py +++ b/tests/app/test_specing.py @@ -49,11 +49,4 @@ def test_spec_resource(helpers): js = json.dumps(sd) print(js) # Assert on the entire JSON to ensure we are getting all the docs - assert js == '{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "array"}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign \'alias\' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI", "required": false}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "parameters": [{"in": "body", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {}, "options": {}, "post": {}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "int"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/operations/{name}": {"delete": {}, "get": {}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {}, "put": {}}, "/identifiers/{name}": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/endroles/{aid}": {"get": {}, "post": {}}, "/escrows/rpy": {"get": {}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/oobi/{aid}": {"get": {}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/identifiers/{name}/events": {"delete": {}, "get": {}, "post": {}, "put": {}}, "/identifiers/{name}/oobis": {"get": {}}, "/identifiers/{name}/endroles": {"get": {}, "post": {}}, "/identifiers/{name}/members": {"get": {}}, "/identifiers/{name}/delegation": {"post": {}}, "/endroles/{aid}/{role}": {"get": {}, "post": {}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {}}, "/identifiers/{name}/endroles/{role}": {"get": {}, "post": {}}, "/oobi/{aid}/{role}/{eid}": {"get": {}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}' - - - - - - -"" + assert js == '{"paths": {"/operations": {"get": {"summary": "Get list of long running operations", "parameters": [{"in": "query", "name": "type", "schema": {"type": "string"}, "required": false, "description": "filter list of long running operations by type"}], "responses": {"200": {"description": "list of long running operations", "content": {"application/json": {"schema": {"type": "array", "items": {"properties": {"name": {"type": "string"}, "metadata": {"type": "object"}, "done": {"type": "boolean"}, "error": {"type": "object"}, "response": {"type": "object"}}}}}}}}}}, "/oobis": {"post": {"summary": "Resolve OOBI and assign an alias for the remote identifier", "description": "Resolve OOBI URL or `rpy` message by process results of request and assign \'alias\' in contact data for resolved identifier", "tags": ["OOBIs"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "OOBI", "oneOf": [{"type": "object", "properties": {"oobialias": {"type": "string", "description": "alias to assign to the identifier resolved from this OOBI"}, "url": {"type": "string", "description": "URL OOBI"}, "rpy": {"type": "object", "description": "unsigned KERI `rpy` event message with endpoints"}}}]}}}}, "responses": {"202": {"description": "OOBI resolution to key state successful"}}}}, "/states": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "pre", "description": "qb64 identifier prefix of KEL to load", "schema": {"type": "string"}, "required": true}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/events": {"get": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Key Event Log"], "parameters": [{"in": "path", "name": "pre", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of KEL to load"}], "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/queries": {"post": {"summary": "Display key event log (KEL) for given identifier prefix", "description": "If provided qb64 identifier prefix is in Kevers, return the current state of the identifier along with the KEL and all associated signatures and receipts", "tags": ["Query"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["pre"], "properties": {"pre": {"type": "string", "description": "qb64 identifier prefix of KEL to load"}, "anchor": {"type": "string", "description": "Anchor"}, "sn": {"type": "string", "description": "Serial number"}}}}}}, "responses": {"200": {"description": "Key event log and key state of identifier"}, "404": {"description": "Identifier not found in Key event database"}}}}, "/identifiers": {"get": {"summary": "Retrieve a list of identifiers associated with the agent.", "description": "This endpoint retrieves a list of identifiers associated with the agent. It supports pagination through the \'Range\' header.", "tags": ["Identifier"], "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "The \'Range\' header is used for pagination. The default range is 0-9."}], "responses": {"200": {"description": "Successfully retrieved identifiers."}, "206": {"description": "Successfully retrieved identifiers within the specified range."}}}, "options": {}, "post": {"summary": "Create an identifier.", "description": "This endpoint creates an identifier with the provided inception event, name, and signatures.", "tags": ["Identifier"], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"icp": {"type": "object", "description": "The inception event for the identifier."}, "name": {"type": "string", "description": "The name of the identifier."}, "sigs": {"type": "array", "items": {"type": "string"}, "description": "The signatures for the inception event."}, "group": {"type": "object", "description": "Multisig group information."}, "salty": {"type": "object", "description": "Salty parameters."}, "randy": {"type": "object", "description": "Randomly generated materials."}, "extern": {"type": "object", "description": "External parameters."}}}}}}, "responses": {"202": {"description": "Identifier creation is in progress. The response is a long running operation."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}}}}, "/challenges": {"get": {"summary": "Get random list of words for a 2 factor auth challenge", "description": "Get the list of identifiers associated with this agent", "tags": ["Challenge/Response"], "parameters": [{"in": "query", "name": "strength", "schema": {"type": "integer"}, "description": "cryptographic strength of word list", "required": false}], "responses": {"200": {"description": "An array of random words", "content": {"application/json": {"schema": {"description": "Random word list", "type": "object", "properties": {"words": {"type": "array", "description": "random challenge word list", "items": {"type": "string"}}}}}}}}}}, "/contacts": {"get": {"summary": "Get list of contact information associated with remote identifiers", "description": "Get list of contact information associated with remote identifiers. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "query", "name": "group", "schema": {"type": "string"}, "required": false, "description": "field name to group results by"}, {"in": "query", "name": "filter_field", "schema": {"type": "string"}, "description": "field name to search", "required": false}, {"in": "query", "name": "filter_value", "schema": {"type": "string"}, "description": "value to search for", "required": false}], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/oobi": {"get": {"summary": "Retrieve OOBI resource.", "description": "This endpoint retrieves the OOBI resource based on the provided aid, role, and eid.", "tags": ["OOBI Resource"], "parameters": [{"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of OOBI."}, {"in": "path", "name": "role", "schema": {"type": "string"}, "required": true, "description": "The requested role for OOBI rpy message."}, {"in": "path", "name": "eid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of participant in role."}], "responses": {"200": {"description": "Successfully retrieved the OOBI resource."}, "400": {"description": "Bad request. This could be due to invalid or missing parameters."}, "404": {"description": "The requested OOBI resource was not found."}}}}, "/": {"post": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"204": {"description": "KEL EXN, QRY, RPY event accepted."}}}, "put": {"summary": "Accept KERI events with attachment headers and parse", "description": "Accept KERI events with attachment headers and parse.", "tags": ["Events"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "description": "KERI event message"}}}}, "responses": {"200": {"description": "Mailbox query response for server sent events"}, "204": {"description": "KEL or EXN event accepted."}}}}, "/notifications": {"get": {"summary": "Get list of notifications for the controller of the agent", "description": "Get list of notifications for the controller of the agent. Notifications will be sorted by creation date/time", "parameters": [{"in": "header", "name": "Range", "schema": {"type": "string"}, "required": false, "description": "size of the result list. Defaults to 25"}], "tags": ["Notifications"], "responses": {"200": {"description": "List of contact information for remote identifiers"}}}}, "/operations/{name}": {"delete": {"summary": "Remove a specific long running operation.", "description": "This endpoint removes a long running operation by its name.", "tags": ["Operation"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The name of the long running operation to remove."}], "responses": {"204": {"description": "Successfully removed the long running operation."}, "404": {"description": "The requested long running operation was not found."}, "500": {"description": "Internal server error. This could be due to an issue with removing the operation."}}}, "get": {"summary": "Retrieve a specific long running operation.", "description": "This endpoint retrieves the status of a long running operation by its name.", "tags": ["Operation"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The name of the long running operation to retrieve."}], "responses": {"200": {"description": "Successfully retrieved the status of the long running operation."}, "404": {"description": "The requested long running operation was not found."}}}}, "/oobis/{alias}": {"get": {"summary": "Get OOBI for specific identifier", "description": "Generate OOBI for the identifier of the specified alias and role", "tags": ["OOBIs"], "parameters": [{"in": "path", "name": "alias", "schema": {"type": "string"}, "required": true, "description": "human readable alias for the identifier generate OOBI for"}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "role for which to generate OOBI"}], "responses": {"200": {"description": "An array of Identifier key state information", "content": {"application/json": {"schema": {"description": "Key state information for current identifiers", "type": "object"}}}}}}}, "/agent/{caid}": {"get": {"summary": "Retrieve key state record of an agent by controller AID.", "description": "This endpoint retrieves the key state record for a given controller of an agent.", "tags": ["Agent"], "parameters": [{"in": "path", "name": "caid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of Controller."}], "responses": {"200": {"description": "Successfully retrieved the key state record."}, "400": {"description": "Bad request. This could be due to an invalid agent or controller configuration."}, "404": {"description": "The requested controller or agent was not found."}}}, "put": {"summary": "Update agent configuration by controller AID.", "description": "This endpoint updates the agent configuration based on the provided request parameters and body.", "tags": ["Agent"], "parameters": [{"in": "path", "name": "caid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of Controller."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["rot", "sigs", "sxlt", "kyes"], "properties": {"rot": {"type": "object", "description": "The rotation event."}, "sigs": {"type": "array", "items": {"type": "string"}, "description": "The signatures."}, "sxlt": {"type": "string", "description": "The salty parameters."}, "keys": {"type": "object", "description": "The keys."}}}}}}, "responses": {"204": {"description": "Successfully updated the agent configuration."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}, "404": {"description": "The requested agent was not found."}, "500": {"description": "Internal server error. This could be due to an issue with updating the agent configuration."}}}}, "/identifiers/{name}": {"delete": {"summary": "Delete an identifier.", "description": "This endpoint deletes an identifier by its name.", "tags": ["Identifier"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}], "responses": {"200": {"description": "Successfully deleted the identifier."}, "400": {"description": "Bad request. This could be due to a missing or invalid name parameter."}, "404": {"description": "The requested identifier was not found."}}}, "get": {"summary": "Retrieve an identifier.", "description": "This endpoint retrieves an identifier by its human-readable name.", "tags": ["Identifier"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}], "responses": {"200": {"description": "Successfully retrieved the identifier details."}, "400": {"description": "Bad request. This could be due to a missing or invalid name parameter."}, "404": {"description": "The requested identifier was not found."}}}, "post": {"summary": "Process identifier events.", "description": "This endpoint handles the \'rot\' or \'ixn\' events of an identifier based on the provided request.", "tags": ["Identifier"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"rot": {"type": "object", "description": "The rotation event details."}, "ixn": {"type": "object", "description": "The interaction event details."}}, "oneOf": [{"required": ["rot"]}, {"required": ["ixn"]}]}}}}, "responses": {"200": {"description": "Successfully processed the identifier\'s event."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}}}, "put": {"summary": "Rename an identifier.", "description": "This endpoint renames an identifier with the provided new name.", "tags": ["Identifier"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The current human-readable name of the identifier."}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The new name for the identifier."}}, "required": ["name"]}}}}, "responses": {"200": {"description": "Successfully renamed the identifier and returns the updated information."}, "400": {"description": "Bad request. This could be due to a missing or invalid name parameter."}, "404": {"description": "The requested identifier was not found."}}}}, "/endroles/{aid}": {"get": {"summary": "Retrieve end roles.", "description": "This endpoint retrieves the end roles associated with AID or human-readable name. It can also filter the end roles based on a specific role.", "tags": ["End Role"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}, {"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "The identifier (AID)."}, {"in": "path", "name": "role", "schema": {"type": "string"}, "required": true, "description": "The specific role to filter the end roles."}], "responses": {"200": {"description": "Successfully retrieved the end roles. The response body contains the end roles."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}, "404": {"description": "The requested identifier was not found."}}}, "post": {"summary": "Create an end role.", "description": "This endpoint creates an end role associated with a given identifier (AID) or name.", "tags": ["End Role"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}, {"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "Not supported for POST. If provided, a 404 is returned."}], "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 end role creation 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."}}}}, "/escrows/rpy": {"get": {"summary": "Retrieve reply escrows.", "description": "This endpoint retrieves the reply escrows and can filter the collection based on a specific route.", "tags": ["Reply Escrow"], "parameters": [{"in": "query", "name": "route", "schema": {"type": "string"}, "required": false, "description": "The specific route to filter the reply escrow collection."}], "responses": {"200": {"description": "Successfully retrieved the reply escrows."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}}}}, "/challenges/{name}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/challenges_verify/{source}": {"post": {"summary": "Sign challenge message and forward to peer identifier", "description": "Sign a challenge word list received out of bands and send `exn` peer to peer message to recipient", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "source", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"recipient": {"type": "string", "description": "human readable alias recipient identifier to send signed challenge to"}, "words": {"type": "array", "description": "challenge in form of word list", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}, "put": {"summary": "Mark challenge response exn message as signed", "description": "Mark challenge response exn message as signed", "tags": ["Challenge/Response"], "parameters": [{"in": "path", "name": "source", "schema": {"type": "string"}, "required": true, "description": "Human readable alias for the identifier to create"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Challenge response", "properties": {"aid": {"type": "string", "description": "aid of signer of accepted challenge response"}, "said": {"type": "array", "description": "SAID of challenge message signed", "items": {"type": "string"}}}}}}}, "responses": {"202": {"description": "Success submission of signed challenge/response"}}}}, "/contacts/{prefix}": {"delete": {"summary": "Delete contact information associated with remote identifier", "description": "Delete contact information associated with remote identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to delete"}], "responses": {"202": {"description": "Contact information successfully deleted for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "get": {"summary": "Get contact information associated with single remote identifier", "description": "Get contact information associated with single remote identifier. All information is meta-data and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix"}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Create new contact information for an identifier", "description": "Creates new information for an identifier, overwriting all existing information for that identifier", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}, "put": {"summary": "Update provided fields in contact information associated with remote identifier prefix", "description": "Update provided fields in contact information associated with remote identifier prefix. All information is metadata and kept in local storage only", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix to add contact metadata to"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"description": "Contact information", "type": "object"}}}}, "responses": {"200": {"description": "Updated contact information for remote identifier"}, "400": {"description": "Invalid identifier used to update contact information"}, "404": {"description": "Prefix not found in identifier contact information"}}}}, "/oobi/{aid}": {"get": {"summary": "Retrieve OOBI resource.", "description": "This endpoint retrieves the OOBI resource based on the provided aid, role, and eid.", "tags": ["OOBI Resource"], "parameters": [{"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of OOBI."}, {"in": "path", "name": "role", "schema": {"type": "string"}, "required": true, "description": "The requested role for OOBI rpy message."}, {"in": "path", "name": "eid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of participant in role."}], "responses": {"200": {"description": "Successfully retrieved the OOBI resource."}, "400": {"description": "Bad request. This could be due to invalid or missing parameters."}, "404": {"description": "The requested OOBI resource was not found."}}}}, "/notifications/{said}": {"delete": {"summary": "Delete notification", "description": "Delete notification", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to delete"}], "responses": {"202": {"description": "Notification successfully deleted for prefix"}, "404": {"description": "No notification information found for prefix"}}}, "put": {"summary": "Mark notification as read", "description": "Mark notification as read", "tags": ["Notifications"], "parameters": [{"in": "path", "name": "said", "schema": {"type": "string"}, "required": true, "description": "qb64 said of note to mark as read"}], "responses": {"202": {"description": "Notification successfully marked as read for prefix"}, "404": {"description": "No notification information found for SAID"}}}}, "/identifiers/{name}/events": {"delete": {"summary": "Delete an identifier.", "description": "This endpoint deletes an identifier by its name.", "tags": ["Identifier"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}], "responses": {"200": {"description": "Successfully deleted the identifier."}, "400": {"description": "Bad request. This could be due to a missing or invalid name parameter."}, "404": {"description": "The requested identifier was not found."}}}, "get": {"summary": "Retrieve an identifier.", "description": "This endpoint retrieves an identifier by its human-readable name.", "tags": ["Identifier"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}], "responses": {"200": {"description": "Successfully retrieved the identifier details."}, "400": {"description": "Bad request. This could be due to a missing or invalid name parameter."}, "404": {"description": "The requested identifier was not found."}}}, "post": {"summary": "Process identifier events.", "description": "This endpoint handles the \'rot\' or \'ixn\' events of an identifier based on the provided request.", "tags": ["Identifier"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"rot": {"type": "object", "description": "The rotation event details."}, "ixn": {"type": "object", "description": "The interaction event details."}}, "oneOf": [{"required": ["rot"]}, {"required": ["ixn"]}]}}}}, "responses": {"200": {"description": "Successfully processed the identifier\'s event."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}}}, "put": {"summary": "Rename an identifier.", "description": "This endpoint renames an identifier with the provided new name.", "tags": ["Identifier"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The current human-readable name of the identifier."}], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "The new name for the identifier."}}, "required": ["name"]}}}}, "responses": {"200": {"description": "Successfully renamed the identifier and returns the updated information."}, "400": {"description": "Bad request. This could be due to a missing or invalid name parameter."}, "404": {"description": "The requested identifier was not found."}}}}, "/identifiers/{name}/oobis": {"get": {"summary": "Fetch OOBI URLs of an identifier.", "description": "This endpoint fetches the OOBI URLs for a specific role associated with an identifier.", "tags": ["Identifier"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}, {"in": "query", "name": "role", "schema": {"type": "string"}, "required": true, "description": "The role for which to fetch the OOBI URLs. Can be a witness, controller, agent, or mailbox."}], "responses": {"200": {"description": "Successfully fetched the OOBI URLs. The response body contains the OOBI URLs."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}, "404": {"description": "The requested identifier was not found."}}}}, "/identifiers/{name}/endroles": {"get": {"summary": "Retrieve end roles.", "description": "This endpoint retrieves the end roles associated with AID or human-readable name. It can also filter the end roles based on a specific role.", "tags": ["End Role"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}, {"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "The identifier (AID)."}, {"in": "path", "name": "role", "schema": {"type": "string"}, "required": true, "description": "The specific role to filter the end roles."}], "responses": {"200": {"description": "Successfully retrieved the end roles. The response body contains the end roles."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}, "404": {"description": "The requested identifier was not found."}}}, "post": {"summary": "Create an end role.", "description": "This endpoint creates an end role associated with a given identifier (AID) or name.", "tags": ["End Role"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}, {"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "Not supported for POST. If provided, a 404 is returned."}], "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 end role creation 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."}}}}, "/identifiers/{name}/members": {"get": {"summary": "Fetch group member information.", "description": "This endpoint retrieves the signing and rotation members for a specific group associated with an identifier.", "tags": ["Group Member"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}], "responses": {"200": {"description": "Successfully fetched the group member information."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}, "404": {"description": "The requested identifier was not found."}}}}, "/identifiers/{name}/delegation": {"post": {}}, "/endroles/{aid}/{role}": {"get": {"summary": "Retrieve end roles.", "description": "This endpoint retrieves the end roles associated with AID or human-readable name. It can also filter the end roles based on a specific role.", "tags": ["End Role"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}, {"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "The identifier (AID)."}, {"in": "path", "name": "role", "schema": {"type": "string"}, "required": true, "description": "The specific role to filter the end roles."}], "responses": {"200": {"description": "Successfully retrieved the end roles. The response body contains the end roles."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}, "404": {"description": "The requested identifier was not found."}}}, "post": {"summary": "Create an end role.", "description": "This endpoint creates an end role associated with a given identifier (AID) or name.", "tags": ["End Role"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}, {"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "Not supported for POST. If provided, a 404 is returned."}], "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 end role creation 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."}}}}, "/contacts/{prefix}/img": {"get": {"summary": "Get contact image for identifer prefix", "description": "Get contact image for identifer prefix", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "required": true, "description": "qb64 identifier prefix of contact image to get"}], "responses": {"200": {"description": "Contact information successfully retrieved for prefix", "content": {"image/jpg": {"schema": {"description": "Image", "type": "binary"}}}}, "404": {"description": "No contact information found for prefix"}}}, "post": {"summary": "Uploads an image to associate with identifier.", "description": "Uploads an image to associate with identifier.", "tags": ["Contacts"], "parameters": [{"in": "path", "name": "prefix", "schema": {"type": "string"}, "description": "identifier prefix to associate image to", "required": true}], "requestBody": {"required": true, "content": {"image/jpg": {"schema": {"type": "string", "format": "binary"}}, "image/png": {"schema": {"type": "string", "format": "binary"}}}}, "responses": {"200": {"description": "Image successfully uploaded"}}}}, "/oobi/{aid}/{role}": {"get": {"summary": "Retrieve OOBI resource.", "description": "This endpoint retrieves the OOBI resource based on the provided aid, role, and eid.", "tags": ["OOBI Resource"], "parameters": [{"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of OOBI."}, {"in": "path", "name": "role", "schema": {"type": "string"}, "required": true, "description": "The requested role for OOBI rpy message."}, {"in": "path", "name": "eid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of participant in role."}], "responses": {"200": {"description": "Successfully retrieved the OOBI resource."}, "400": {"description": "Bad request. This could be due to invalid or missing parameters."}, "404": {"description": "The requested OOBI resource was not found."}}}}, "/identifiers/{name}/endroles/{role}": {"get": {"summary": "Retrieve end roles.", "description": "This endpoint retrieves the end roles associated with AID or human-readable name. It can also filter the end roles based on a specific role.", "tags": ["End Role"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}, {"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "The identifier (AID)."}, {"in": "path", "name": "role", "schema": {"type": "string"}, "required": true, "description": "The specific role to filter the end roles."}], "responses": {"200": {"description": "Successfully retrieved the end roles. The response body contains the end roles."}, "400": {"description": "Bad request. This could be due to missing or invalid parameters."}, "404": {"description": "The requested identifier was not found."}}}, "post": {"summary": "Create an end role.", "description": "This endpoint creates an end role associated with a given identifier (AID) or name.", "tags": ["End Role"], "parameters": [{"in": "path", "name": "name", "schema": {"type": "string"}, "required": true, "description": "The human-readable name of the identifier."}, {"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "Not supported for POST. If provided, a 404 is returned."}], "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 end role creation 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."}}}}, "/oobi/{aid}/{role}/{eid}": {"get": {"summary": "Retrieve OOBI resource.", "description": "This endpoint retrieves the OOBI resource based on the provided aid, role, and eid.", "tags": ["OOBI Resource"], "parameters": [{"in": "path", "name": "aid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of OOBI."}, {"in": "path", "name": "role", "schema": {"type": "string"}, "required": true, "description": "The requested role for OOBI rpy message."}, {"in": "path", "name": "eid", "schema": {"type": "string"}, "required": true, "description": "The qb64 identifier prefix of participant in role."}], "responses": {"200": {"description": "Successfully retrieved the OOBI resource."}, "400": {"description": "Bad request. This could be due to invalid or missing parameters."}, "404": {"description": "The requested OOBI resource was not found."}}}}, "/identifiers/{name}/endroles/{role}/{eid}": {"delete": {}}}, "info": {"title": "KERIA Interactive Web Interface API", "version": "1.0.1"}, "openapi": "3.1.0"}' From 54de5a7ddd526f40cab809e20d2607a728be32d1 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Fri, 14 Jun 2024 08:23:57 -0400 Subject: [PATCH 16/21] set to tagged keripy 1.2.0.dev6 Signed-off-by: 2byrds <2byrds@gmail.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 53918fbf..3e05e7dc 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ python_requires='>=3.12.2', install_requires=[ 'hio>=0.6.12', - 'keri @ git+https://github.com/WebOfTrust/keripy.git@main', + 'keri>=1.2.0.dev6', 'mnemonic>=0.20', 'multicommand>=1.0.0', 'falcon>=3.1.3', From fcaf2fb5c0ec783db1dfd481bd8b0f9a44049bcc Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Fri, 14 Jun 2024 11:00:27 -0400 Subject: [PATCH 17/21] removed duplicate comment and simplified keria.dockerfile to leverage keripy image Signed-off-by: 2byrds <2byrds@gmail.com> --- images/keria.dockerfile | 55 ++++--------------------------------- src/keria/app/delegating.py | 1 - 2 files changed, 6 insertions(+), 50 deletions(-) diff --git a/images/keria.dockerfile b/images/keria.dockerfile index 5d2f0bb5..6f70f3b1 100644 --- a/images/keria.dockerfile +++ b/images/keria.dockerfile @@ -1,55 +1,12 @@ -# Builder stage -FROM python:3.12-alpine3.19 as builder +FROM weboftrust/keri:1.2.0-dev6 -# Install compilation dependencies -RUN apk --no-cache add \ - bash \ - alpine-sdk \ - libffi-dev \ - libsodium \ - libsodium-dev +WORKDIR /usr/local/var -SHELL ["/bin/bash", "-c"] +RUN mkdir keria +COPY . /usr/local/var/keria -# Install Rust for blake3 dependency build -RUN curl https://sh.rustup.rs -sSf | bash -s -- -y +WORKDIR /usr/local/var/keria -WORKDIR /keria - -RUN python -m venv venv -ENV PATH=/keria/venv/bin:${PATH} -RUN pip install --upgrade pip - -# Copy in Python dependency files -COPY requirements.txt setup.py ./ -# "src/" dir required for installation of dependencies with setup.py -RUN mkdir /keria/src -# Install Python dependencies -RUN . "$HOME/.cargo/env" && \ - pip install -r requirements.txt - -# Runtime stage -FROM python:3.12-alpine3.19 - -# Install runtime dependencies -RUN apk --no-cache add \ - bash \ - alpine-sdk \ - libsodium-dev - -WORKDIR /keria - -# Copy over compiled dependencies -COPY --from=builder /keria /keria -# Copy in KERIA source files - enables near instantaneous builds for source only changes -RUN mkdir -p /usr/local/var/keri -ENV KERI_AGENT_CORS=${KERI_AGENT_CORS:-false} -ENV PATH=/keria/venv/bin:${PATH} - -EXPOSE 3901 -EXPOSE 3902 -EXPOSE 3903 - -COPY src/ src/ +RUN pip install -r requirements.txt ENTRYPOINT ["keria", "start", "--config-file", "demo-witness-oobis", "--config-dir", "./scripts"] diff --git a/src/keria/app/delegating.py b/src/keria/app/delegating.py index a2280cc8..08d25dc8 100644 --- a/src/keria/app/delegating.py +++ b/src/keria/app/delegating.py @@ -119,7 +119,6 @@ def processEscrows(self): def processUnanchoredEscrow(self): """ Process escrow of unacnchored events that have been delegated and are waiting for delegator anchor/approval. - Process escrow of unacnchored events that have been delegated and are waiting for delegator anchor/approval. """ for (pre, said), serder in self.hby.db.dune.getItemIter(): # group partial witness escrow kever = self.hby.kevers[pre] From 66b3e23f2bf225ce284582e8d70ed1c86ad28ba1 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Fri, 21 Jun 2024 13:31:17 -0400 Subject: [PATCH 18/21] Remove any smids/rmids state dict test problems Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/aiding.py | 685 +++++++++++++++++++++++-------------- src/keria/app/grouping.py | 2 +- tests/app/test_aiding.py | 21 +- tests/app/test_grouping.py | 5 +- tests/app/test_ipexing.py | 25 +- 5 files changed, 466 insertions(+), 272 deletions(-) diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index f30e7372..5f575d62 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -23,6 +23,7 @@ logger = ogler.getLogger() + def loadEnds(app, agency, authn): groupEnd = AgentResourceEnd(agency=agency, authn=authn) app.add_route("/agent/{caid}", groupEnd) @@ -70,14 +71,14 @@ def loadEnds(app, agency, authn): class AgentResourceEnd: - """ Resource class for getting agent specific launch information """ + """Resource class for getting agent specific launch information""" def __init__(self, agency, authn): self.agency = agency self.authn = authn def on_get(self, _, rep, caid): - """ GET endpoint for Keystores + """GET endpoint for Keystores Get keystore status @@ -108,13 +109,19 @@ def on_get(self, _, rep, caid): """ agent = self.agency.get(caid) if agent is None: - raise falcon.HTTPNotFound(description=f"not agent found for controller {caid}") + raise falcon.HTTPNotFound( + description=f"not agent found for controller {caid}" + ) if agent.pre not in agent.hby.kevers: - raise falcon.HTTPBadRequest(description=f"invalid agent configuration, {agent.pre} not found") + raise falcon.HTTPBadRequest( + description=f"invalid agent configuration, {agent.pre} not found" + ) if agent.caid not in agent.hby.kevers: - raise falcon.HTTPBadRequest(description=f"invalid controller configuration, {agent.caid} not found") + raise falcon.HTTPBadRequest( + description=f"invalid controller configuration, {agent.caid} not found" + ) pidx = 0 for name, _ in agent.hby.db.names.getItemIter(): @@ -124,17 +131,14 @@ def on_get(self, _, rep, caid): # pidx = agent.hby.db.habs.cntAll() state = asdict(agent.hby.kevers[agent.caid].state()) - key = dbing.dgKey(state['i'], state['ee']['d']) # digest key + key = dbing.dgKey(state["i"], state["ee"]["d"]) # digest key msg = agent.hby.db.getEvt(key) eserder = serdering.SerderKERI(raw=bytes(msg)) body = dict( agent=asdict(agent.hby.kevers[agent.pre].state()), - controller=dict( - state=state, - ee=eserder.ked - ), - pidx=pidx + controller=dict(state=state, ee=eserder.ked), + pidx=pidx, ) if (sxlt := agent.mgr.sxlt) is not None: @@ -213,16 +217,24 @@ def on_put(self, req, rep, caid): body = req.get_media() if "rot" not in body: - raise falcon.HTTPBadRequest(description="required field 'rot' missing from body") + raise falcon.HTTPBadRequest( + description="required field 'rot' missing from body" + ) if "sigs" not in body: - raise falcon.HTTPBadRequest(description="required field 'sigs' missing from body") + raise falcon.HTTPBadRequest( + description="required field 'sigs' missing from body" + ) if "sxlt" not in body: - raise falcon.HTTPBadRequest(description="required field 'sxlt' missing from body") + raise falcon.HTTPBadRequest( + description="required field 'sxlt' missing from body" + ) if "keys" not in body: - raise falcon.HTTPBadRequest(description="required field 'keys' missing from body") + raise falcon.HTTPBadRequest( + description="required field 'keys' missing from body" + ) rot = serdering.SerderKERI(sad=body["rot"]) sigs = body["sigs"] @@ -240,12 +252,18 @@ def on_put(self, req, rep, caid): for pre, val in keys.items(): if "sxlt" in val: if (sp := agent.mgr.rb.sprms.get(pre)) is None: - raise ValueError("Attempt to update sxlt for nonexistent or invalid pre={}.".format(pre)) + raise ValueError( + "Attempt to update sxlt for nonexistent or invalid pre={}.".format( + pre + ) + ) sp.sxlt = val["sxlt"] if not agent.mgr.rb.sprms.pin(pre, val=sp): - raise ValueError("Unable to update sxlt prms for pre={}.".format(pre)) + raise ValueError( + "Unable to update sxlt prms for pre={}.".format(pre) + ) elif "prxs" in val: hab = agent.hby.habs[pre] @@ -260,7 +278,9 @@ def on_put(self, req, rep, caid): if "nxts" in val: nxts = val["nxts"] if len(nxts) != len(digers): - raise ValueError("If encrypted private next keys are provided, must match digers") + raise ValueError( + "If encrypted private next keys are provided, must match digers" + ) for idx, prx in enumerate(nxts): cipher = core.Cipher(qb64=prx) @@ -275,13 +295,17 @@ def interact(req, rep, agent, caid): body = req.get_media() if "ixn" not in body: - raise falcon.HTTPBadRequest(description="required field 'ixn' missing from body") + raise falcon.HTTPBadRequest( + description="required field 'ixn' missing from body" + ) if "sigs" not in body: - raise falcon.HTTPBadRequest(description="required field 'sigs' missing from body") + raise falcon.HTTPBadRequest( + description="required field 'sigs' missing from body" + ) - ked = body['ixn'] - sigs = body['sigs'] + ked = body["ixn"] + sigs = body["sigs"] ixn = serdering.SerderKERI(sad=ked) sigers = [core.Siger(qb64=sig) for sig in sigs] @@ -300,11 +324,11 @@ def anchorSeals(agent, ixn): if len(a) == 0: return - delegator = coring.Saider(qb64=ixn.ked['d']) - delegatorsn = coring.Seqner(snh=ixn.ked['s']) + delegator = coring.Saider(qb64=ixn.ked["d"]) + delegatorsn = coring.Seqner(snh=ixn.ked["s"]) seal = a[0] - prefixer = coring.Prefixer(qb64=seal['i']) + prefixer = coring.Prefixer(qb64=seal["i"]) saider = coring.Saider(qb64=seal["d"]) couple = delegatorsn.qb64b + delegator.qb64b @@ -313,7 +337,7 @@ def anchorSeals(agent, ixn): class IdentifierCollectionEnd: - """ Resource class for creating and managing identifiers """ + """Resource class for creating and managing identifiers""" @staticmethod def on_options(req, rep): @@ -322,7 +346,7 @@ def on_options(req, rep): @staticmethod def on_get(req, rep): - """ Identifier List GET endpoint + """Identifier List GET endpoint Parameters: req: falcon.Request HTTP request @@ -386,7 +410,7 @@ def on_get(req, rep): @staticmethod def on_post(req, rep): - """ Inception event POST endpoint + """Inception event POST endpoint Parameters: req (Request): falcon.Request HTTP request object @@ -444,58 +468,91 @@ def on_post(req, rep): sigers = [core.Siger(qb64=sig) for sig in sigs] if agent.hby.habByName(name) is not None: - raise falcon.HTTPBadRequest(title=f"AID with name {name} already incepted") + raise falcon.HTTPBadRequest( + title=f"AID with name {name} already incepted" + ) - if 'b' in icp: - for wit in icp['b']: + if "b" in icp: + for wit in icp["b"]: urls = agent.agentHab.fetchUrls(eid=wit, scheme=kering.Schemes.http) if not urls and wit not in agent.hby.kevers: - raise falcon.HTTPBadRequest(description=f'unknown witness {wit}') + raise falcon.HTTPBadRequest( + description=f"unknown witness {wit}" + ) - if 'di' in icp and icp["di"] not in agent.hby.kevers: - raise falcon.HTTPBadRequest(description=f'unknown delegator {icp["di"]}') + if "di" in icp and icp["di"] not in agent.hby.kevers: + raise falcon.HTTPBadRequest( + description=f'unknown delegator {icp["di"]}' + ) # client is requesting agent to join multisig group if "group" in body: group = body["group"] if "mhab" not in group: - raise falcon.HTTPBadRequest(description=f'required field "mhab" missing from body.group') + raise falcon.HTTPBadRequest( + description=f'required field "mhab" missing from body.group' + ) mpre = group["mhab"]["prefix"] if mpre not in agent.hby.habs: - raise falcon.HTTPBadRequest(description=f'signing member {mpre} not a local AID') + raise falcon.HTTPBadRequest( + description=f"signing member {mpre} not a local AID" + ) mhab = agent.hby.habs[mpre] if "keys" not in group: - raise falcon.HTTPBadRequest(description=f'required field "keys" missing from body.group') + raise falcon.HTTPBadRequest( + description=f'required field "keys" missing from body.group' + ) keys = group["keys"] verfers = [coring.Verfer(qb64=key) for key in keys] if mhab.kever.fetchLatestContribTo(verfers=verfers) is None: - raise falcon.HTTPBadRequest(description=f"Member hab={mhab.pre} not a participant in " - f"event for this group hab.") + raise falcon.HTTPBadRequest( + description=f"Member hab={mhab.pre} not a participant in " + f"event for this group hab." + ) if "ndigs" not in group: - raise falcon.HTTPBadRequest(description=f'required field "ndigs" missing from body.group') + raise falcon.HTTPBadRequest( + description=f'required field "ndigs" missing from body.group' + ) ndigs = group["ndigs"] digers = [coring.Diger(qb64=ndig) for ndig in ndigs] states = httping.getRequiredParam(body, "smids") rstates = httping.getRequiredParam(body, "rmids") - # smids = [state['i'] for state in states] - # rmids = [rstate['i'] for rstate in rstates] - hab = agent.hby.makeSignifyGroupHab(name, mhab=mhab, smids=states, rmids=rstates, serder=serder, - sigers=sigers) + + hab = agent.hby.makeSignifyGroupHab( + name, + mhab=mhab, + smids=states, + rmids=rstates, + serder=serder, + sigers=sigers, + ) try: - agent.inceptGroup(pre=serder.pre, mpre=mhab.pre, verfers=verfers, digers=digers) + agent.inceptGroup( + pre=serder.pre, mpre=mhab.pre, verfers=verfers, digers=digers + ) except ValueError as e: agent.hby.deleteHab(name=name) raise falcon.HTTPInternalServerError(description=f"{e.args[0]}") # Generate response, a long running operaton indicator for the type - agent.groups.append(dict(pre=hab.pre, serder=serder, sigers=sigers, smids=states, rmids=rstates)) - op = agent.monitor.submit(serder.pre, longrunning.OpTypes.group, metadata=dict(sn=0)) + agent.groups.append( + dict( + pre=hab.pre, + serder=serder, + sigers=sigers, + smids=states, + rmids=rstates, + ) + ) + op = agent.monitor.submit( + serder.pre, longrunning.OpTypes.group, metadata=dict(sn=0) + ) rep.content_type = "application/json" rep.status = falcon.HTTP_202 @@ -517,7 +574,12 @@ def on_post(req, rep): rand = body[Algos.randy] hab = agent.hby.makeSignifyHab(name, serder=serder, sigers=sigers) try: - agent.inceptRandy(pre=serder.pre, verfers=serder.verfers, digers=serder.ndigers, **rand) + agent.inceptRandy( + pre=serder.pre, + verfers=serder.verfers, + digers=serder.ndigers, + **rand, + ) except ValueError as e: agent.hby.deleteHab(name=name) raise falcon.HTTPInternalServerError(description=f"{e.args[0]}") @@ -526,47 +588,63 @@ def on_post(req, rep): extern = body[Algos.extern] hab = agent.hby.makeSignifyHab(name, serder=serder, sigers=sigers) try: - agent.inceptExtern(pre=serder.pre, verfers=serder.verfers, digers=serder.ndigers, **extern) + agent.inceptExtern( + pre=serder.pre, + verfers=serder.verfers, + digers=serder.ndigers, + **extern, + ) except ValueError as e: agent.hby.deleteHab(name=name) raise falcon.HTTPInternalServerError(description=f"{e.args[0]}") else: raise falcon.HTTPBadRequest( - description="invalid request: one of group, rand or salt field required") + description="invalid request: one of group, rand or salt field required" + ) # create Hab and incept the key store (if any) # Generate response, either the serder or a long running operaton indicator for the type rep.content_type = "application/json" if hab.kever.delpre: agent.anchors.append(dict(pre=hab.pre, sn=0)) - op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.delegation, - metadata=dict(pre=hab.pre, sn=0)) + op = agent.monitor.submit( + hab.kever.prefixer.qb64, + longrunning.OpTypes.delegation, + metadata=dict(pre=hab.pre, sn=0), + ) rep.status = falcon.HTTP_202 rep.data = op.to_json().encode("utf-8") elif hab.kever.wits: agent.witners.append(dict(serder=serder)) - op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.witness, - metadata=dict(sn=0)) + op = agent.monitor.submit( + hab.kever.prefixer.qb64, + longrunning.OpTypes.witness, + metadata=dict(sn=0), + ) rep.status = falcon.HTTP_202 rep.data = op.to_json().encode("utf-8") else: rep.status = falcon.HTTP_202 - op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.done, - metadata=dict(response=serder.ked)) + op = agent.monitor.submit( + hab.kever.prefixer.qb64, + longrunning.OpTypes.done, + metadata=dict(response=serder.ked), + ) rep.data = op.to_json().encode("utf-8") except (kering.AuthError, ValueError) as e: raise falcon.HTTPBadRequest(description=e.args[0]) + class IdentifierResourceEnd: - """ Resource class for updating and deleting identifiers """ + """Resource class for updating and deleting identifiers""" @staticmethod def on_get(req, rep, name): - """ Identifier GET endpoint + """Identifier GET endpoint Parameters: req: falcon.Request HTTP request @@ -599,7 +677,9 @@ def on_get(req, rep, name): agent = req.context.agent hab = agent.hby.habByName(name) if hab is None: - raise falcon.HTTPNotFound(description=f"{name} is not a valid identifier name") + raise falcon.HTTPNotFound( + description=f"{name} is not a valid identifier name" + ) data = info(hab, agent.mgr, full=True) rep.status = falcon.HTTP_200 @@ -607,7 +687,7 @@ def on_get(req, rep, name): rep.data = json.dumps(data).encode("utf-8") def on_put(self, req, rep, name): - """ Identifier rename endpoint + """Identifier rename endpoint Parameters: req (Request): falcon.Request HTTP request object @@ -656,8 +736,7 @@ def on_put(self, req, rep, name): newName = body.get("name") habord = hab.db.habs.get(keys=(hab.pre,)) habord.name = newName - hab.db.habs.pin(keys=(hab.pre,), - val=habord) + hab.db.habs.pin(keys=(hab.pre,), val=habord) hab.db.names.pin(keys=("", newName), val=hab.pre) hab.db.names.rem(keys=("", name)) hab.name = newName @@ -668,7 +747,7 @@ def on_put(self, req, rep, name): rep.data = json.dumps(data).encode("utf-8") def on_delete(self, req, rep, name): - """ Identifier delete endpoint + """Identifier delete endpoint Parameters: req (Request): falcon.Request HTTP request object @@ -704,7 +783,7 @@ def on_delete(self, req, rep, name): rep.status = falcon.HTTP_200 def on_post(self, req, rep, name): - """ Identifier events endpoint + """Identifier events endpoint Parameters: req (Request): falcon.Request HTTP request object @@ -756,8 +835,10 @@ def on_post(self, req, rep, name): elif body.get("ixn") is not None: op = self.interact(agent, name, body) else: - raise falcon.HTTPBadRequest(title="invalid request", - description=f"required field 'rot' or 'ixn' missing from request") + raise falcon.HTTPBadRequest( + title="invalid request", + description=f"required field 'rot' or 'ixn' missing from request", + ) rep.status = falcon.HTTP_200 rep.content_type = "application/json" @@ -774,19 +855,23 @@ def rotate(agent, name, body): rot = body.get("rot") if rot is None: - raise falcon.HTTPBadRequest(title="invalid rotation", - description=f"required field 'rot' missing from request") + raise falcon.HTTPBadRequest( + title="invalid rotation", + description=f"required field 'rot' missing from request", + ) - if 'ba' in rot: - for wit in rot['ba']: + if "ba" in rot: + for wit in rot["ba"]: urls = agent.agentHab.fetchUrls(eid=wit, scheme=kering.Schemes.http) if not urls and wit not in agent.hby.kevers: - raise falcon.HTTPBadRequest(description=f'unknown witness {wit}') + raise falcon.HTTPBadRequest(description=f"unknown witness {wit}") sigs = body.get("sigs") if sigs is None or len(sigs) == 0: - raise falcon.HTTPBadRequest(title="invalid rotation", - description=f"required field 'sigs' missing from request") + raise falcon.HTTPBadRequest( + title="invalid rotation", + description=f"required field 'sigs' missing from request", + ) serder = serdering.SerderKERI(sad=rot) sigers = [core.Siger(qb64=sig) for sig in sigs] @@ -809,7 +894,9 @@ def rotate(agent, name, body): rand = body[Algos.randy] keeper = agent.mgr.get(Algos.randy) - keeper.rotate(pre=serder.pre, verfers=serder.verfers, digers=serder.ndigers, **rand) + keeper.rotate( + pre=serder.pre, verfers=serder.verfers, digers=serder.ndigers, **rand + ) elif Algos.group in body: smids = httping.getRequiredParam(body, "smids") @@ -821,25 +908,40 @@ def rotate(agent, name, body): keeper.rotate(pre=serder.pre, verfers=serder.verfers, digers=serder.ndigers) - agent.groups.append(dict(pre=hab.pre, serder=serder, sigers=sigers, smids=smids, rmids=rmids)) - op = agent.monitor.submit(serder.pre, longrunning.OpTypes.group, metadata=dict(sn=serder.sn)) + agent.groups.append( + dict( + pre=hab.pre, serder=serder, sigers=sigers, smids=smids, rmids=rmids + ) + ) + op = agent.monitor.submit( + serder.pre, longrunning.OpTypes.group, metadata=dict(sn=serder.sn) + ) return op if hab.kever.delpre: agent.anchors.append(dict(alias=name, pre=hab.pre, sn=serder.sn)) - op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.delegation, - metadata=dict(pre=hab.pre, sn=serder.sn)) + op = agent.monitor.submit( + hab.kever.prefixer.qb64, + longrunning.OpTypes.delegation, + metadata=dict(pre=hab.pre, sn=serder.sn), + ) return op if hab.kever.wits: agent.witners.append(dict(serder=serder)) - op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.witness, - metadata=dict(sn=serder.sn)) + op = agent.monitor.submit( + hab.kever.prefixer.qb64, + longrunning.OpTypes.witness, + metadata=dict(sn=serder.sn), + ) return op - op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.done, - metadata=dict(response=serder.ked)) + op = agent.monitor.submit( + hab.kever.prefixer.qb64, + longrunning.OpTypes.done, + metadata=dict(response=serder.ked), + ) return op @staticmethod @@ -850,13 +952,17 @@ def interact(agent, name, body): ixn = body.get("ixn") if ixn is None: - raise falcon.HTTPBadRequest(title="invalid interaction", - description=f"required field 'ixn' missing from request") + raise falcon.HTTPBadRequest( + title="invalid interaction", + description=f"required field 'ixn' missing from request", + ) sigs = body.get("sigs") if sigs is None or len(sigs) == 0: - raise falcon.HTTPBadRequest(title="invalid interaction", - description=f"required field 'sigs' missing from request") + raise falcon.HTTPBadRequest( + title="invalid interaction", + description=f"required field 'sigs' missing from request", + ) serder = serdering.SerderKERI(sad=ixn) sigers = [core.Siger(qb64=sig) for sig in sigs] @@ -865,18 +971,26 @@ def interact(agent, name, body): if "group" in body: agent.groups.append(dict(pre=hab.pre, serder=serder, sigers=sigers)) - op = agent.monitor.submit(serder.pre, longrunning.OpTypes.group, metadata=dict(sn=serder.sn)) + op = agent.monitor.submit( + serder.pre, longrunning.OpTypes.group, metadata=dict(sn=serder.sn) + ) return op if hab.kever.wits: agent.witners.append(dict(serder=serder)) - op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.witness, - metadata=dict(sn=serder.sn)) + op = agent.monitor.submit( + hab.kever.prefixer.qb64, + longrunning.OpTypes.witness, + metadata=dict(sn=serder.sn), + ) return op - op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.done, - metadata=dict(response=serder.ked)) + op = agent.monitor.submit( + hab.kever.prefixer.qb64, + longrunning.OpTypes.done, + metadata=dict(response=serder.ked), + ) return op @@ -886,8 +1000,12 @@ def info(hab, rm, full=False): prefix=hab.pre, ) - if not isinstance(hab, habbing.SignifyHab) and not isinstance(hab, habbing.SignifyGroupHab): - raise kering.ConfigurationError(f"agent only allows SignifyHab instances, {type(hab)}") + if not isinstance(hab, habbing.SignifyHab) and not isinstance( + hab, habbing.SignifyGroupHab + ): + raise kering.ConfigurationError( + f"agent only allows SignifyHab instances, {type(hab)}" + ) keeper = rm.get(pre=hab.pre) data.update(keeper.params(pre=hab.pre)) @@ -907,13 +1025,13 @@ def info(hab, rm, full=False): class IdentifierOOBICollectionEnd: """ - This class represents the OOBI subresource collection endpoint for identifiers + This class represents the OOBI subresource collection endpoint for identifiers """ @staticmethod def on_get(req, rep, name): - """ Identifier GET endpoint + """Identifier GET endpoint Parameters: req: falcon.Request HTTP request @@ -962,33 +1080,48 @@ def on_get(req, rep, name): if role in (kering.Roles.witness,): # Fetch URL OOBIs for all witnesses oobis = [] for wit in hab.kever.wits: - urls = hab.fetchUrls(eid=wit, scheme=kering.Schemes.http) or hab.fetchUrls(eid=wit, - scheme=kering.Schemes.https) + urls = hab.fetchUrls( + eid=wit, scheme=kering.Schemes.http + ) or hab.fetchUrls(eid=wit, scheme=kering.Schemes.https) if not urls: - raise falcon.HTTPNotFound(description=f"unable to query witness {wit}, no http endpoint") - - url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + raise falcon.HTTPNotFound( + description=f"unable to query witness {wit}, no http endpoint" + ) + + url = ( + urls[kering.Schemes.http] + if kering.Schemes.http in urls + else urls[kering.Schemes.https] + ) up = urlparse(url) oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/witness/{wit}")) res["oobis"] = oobis elif role in (kering.Roles.controller,): # Fetch any controller URL OOBIs oobis = [] - urls = hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.http) or hab.fetchUrls(eid=hab.pre, - scheme=kering.Schemes.https) + urls = hab.fetchUrls( + eid=hab.pre, scheme=kering.Schemes.http + ) or hab.fetchUrls(eid=hab.pre, scheme=kering.Schemes.https) if not urls: - raise falcon.HTTPNotFound(description=f"unable to query controller {hab.pre}, no http endpoint") - - url = urls[kering.Schemes.http] if kering.Schemes.http in urls else urls[kering.Schemes.https] + raise falcon.HTTPNotFound( + description=f"unable to query controller {hab.pre}, no http endpoint" + ) + + url = ( + urls[kering.Schemes.http] + if kering.Schemes.http in urls + else urls[kering.Schemes.https] + ) up = urlparse(url) oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/controller")) res["oobis"] = oobis elif role in (kering.Roles.agent,): # Fetch URL OOBIs for all witnesses - roleUrls = hab.fetchRoleUrls(cid=hab.pre, role=kering.Roles.agent, - scheme=kering.Schemes.http) or hab.fetchRoleUrls(cid=hab.pre, - role=kering.Roles.agent, - scheme=kering.Schemes.https) + roleUrls = hab.fetchRoleUrls( + cid=hab.pre, role=kering.Roles.agent, scheme=kering.Schemes.http + ) or hab.fetchRoleUrls( + cid=hab.pre, role=kering.Roles.agent, scheme=kering.Schemes.https + ) if kering.Roles.agent not in roleUrls: - res['oobis'] = [] + res["oobis"] = [] else: aoobis = roleUrls[kering.Roles.agent] @@ -1003,14 +1136,19 @@ def on_get(req, rep, name): urls.extend(murl.naball(kering.Schemes.https)) for url in urls: up = urlparse(url) - oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/agent/{agent}")) + oobis.append( + urljoin(up.geturl(), f"/oobi/{hab.pre}/agent/{agent}") + ) res["oobis"] = oobis elif role in (kering.Roles.mailbox,): # Fetch URL OOBIs for all witnesses - roleUrls = (hab.fetchRoleUrls(cid=hab.pre, role=kering.Roles.mailbox, scheme=kering.Schemes.http) or - hab.fetchRoleUrls(cid=hab.pre, role=kering.Roles.mailbox, scheme=kering.Schemes.https)) + roleUrls = hab.fetchRoleUrls( + cid=hab.pre, role=kering.Roles.mailbox, scheme=kering.Schemes.http + ) or hab.fetchRoleUrls( + cid=hab.pre, role=kering.Roles.mailbox, scheme=kering.Schemes.https + ) if kering.Roles.mailbox not in roleUrls: - res['oobis'] = [] + res["oobis"] = [] else: aoobis = roleUrls[kering.Roles.mailbox] @@ -1025,11 +1163,17 @@ def on_get(req, rep, name): urls.extend(murl.naball(kering.Schemes.https)) for url in urls: up = urlparse(url) - oobis.append(urljoin(up.geturl(), f"/oobi/{hab.pre}/mailbox/{mailbox}")) + oobis.append( + urljoin( + up.geturl(), f"/oobi/{hab.pre}/mailbox/{mailbox}" + ) + ) res["oobis"] = oobis else: - raise falcon.HTTPBadRequest(description=f"unsupport role type {role} for oobi request") + raise falcon.HTTPBadRequest( + description=f"unsupport role type {role} for oobi request" + ) rep.status = falcon.HTTP_200 rep.content_type = "application/json" @@ -1040,7 +1184,7 @@ class EndRoleCollectionEnd: @staticmethod def on_get(req, rep, name=None, aid=None, role=None): - """ GET endpoint for end role collection + """GET endpoint for end role collection Parameters: req (Request): falcon HTTP request object @@ -1092,10 +1236,15 @@ def on_get(req, rep, name=None, aid=None, role=None): elif aid is not None: pre = aid else: - raise falcon.HTTPBadRequest(description="either `aid` or `name` are required in the path") + raise falcon.HTTPBadRequest( + description="either `aid` or `name` are required in the path" + ) if role is not None: - keys = (pre, role,) + keys = ( + pre, + role, + ) else: keys = (pre,) @@ -1109,7 +1258,7 @@ def on_get(req, rep, name=None, aid=None, role=None): @staticmethod def on_post(req, rep, name, aid=None, role=None): - """ POST endpoint for end role collection + """POST endpoint for end role collection Args: req (Request): Falcon HTTP request object @@ -1168,10 +1317,10 @@ def on_post(req, rep, name, aid=None, role=None): rsigs = httping.getRequiredParam(body, "sigs") rserder = serdering.SerderKERI(sad=rpy) - data = rserder.ked['a'] - pre = data['cid'] - role = data['role'] - eid = data['eid'] + data = rserder.ked["a"] + pre = data["cid"] + role = data["role"] + eid = data["eid"] hab = agent.hby.habByName(name) if hab is None: @@ -1179,17 +1328,25 @@ def on_post(req, rep, name, aid=None, role=None): if pre != hab.pre: raise falcon.errors.HTTPBadRequest( - description=f"error trying to create end role for unknown local AID {pre}") + description=f"error trying to create end role for unknown local AID {pre}" + ) 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) + 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 oid = ".".join([pre, role, eid]) - op = agent.monitor.submit(oid, longrunning.OpTypes.endrole, metadata=dict(cid=pre, role=role, eid=eid)) + op = agent.monitor.submit( + oid, longrunning.OpTypes.endrole, metadata=dict(cid=pre, role=role, eid=eid) + ) rep.content_type = "application/json" rep.status = falcon.HTTP_202 @@ -1247,11 +1404,11 @@ def on_get(req, rep): class ChallengeCollectionEnd: - """ Resource for Challenge/Response Endpoints """ + """Resource for Challenge/Response Endpoints""" @staticmethod def on_get(req, rep): - """ Challenge GET endpoint + """Challenge GET endpoint Parameters: req: falcon.Request HTTP request @@ -1285,7 +1442,7 @@ def on_get(req, rep): type: string """ - mnem = mnemonic.Mnemonic(language='english') + mnem = mnemonic.Mnemonic(language="english") s = req.params.get("strength") strength = int(s) if s is not None else 128 @@ -1297,11 +1454,11 @@ def on_get(req, rep): class ChallengeResourceEnd: - """ Resource for Challenge/Response Endpoints """ + """Resource for Challenge/Response Endpoints""" @staticmethod def on_post(req, rep, name): - """ Challenge POST endpoint + """Challenge POST endpoint Parameters: req: falcon.Request HTTP request @@ -1347,7 +1504,9 @@ def on_post(req, rep, name): body = req.get_media() if "exn" not in body or "sig" not in body or "recipient" not in body: - raise falcon.HTTPBadRequest(description="challenge response requires 'words', 'sig' and 'recipient'") + raise falcon.HTTPBadRequest( + description="challenge response requires 'words', 'sig' and 'recipient'" + ) exn = body["exn"] sig = body["sig"] @@ -1359,17 +1518,19 @@ def on_post(req, rep, name): agent.hby.psr.parseOne(ims=bytearray(ims)) - agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=recpt, topic='challenge')) + agent.exchanges.append( + dict(said=serder.said, pre=hab.pre, rec=recpt, topic="challenge") + ) rep.status = falcon.HTTP_202 class ChallengeVerifyResourceEnd: - """ Resource for Challenge/Response Verification Endpoints """ + """Resource for Challenge/Response Verification Endpoints""" @staticmethod def on_post(req, rep, source): - """ Challenge POST endpoint + """Challenge POST endpoint Parameters: req: falcon.Request HTTP request @@ -1413,7 +1574,9 @@ def on_post(req, rep, source): body = req.get_media() words = httping.getRequiredParam(body, "words") if source not in agent.hby.kevers: - raise falcon.HTTPNotFound(description=f"challenge response source={source} not found") + raise falcon.HTTPNotFound( + description=f"challenge response source={source} not found" + ) meta = dict(words=words) op = agent.monitor.submit(source, longrunning.OpTypes.challenge, metadata=meta) @@ -1425,7 +1588,7 @@ def on_post(req, rep, source): @staticmethod def on_put(req, rep, source): - """ Challenge PUT accept endpoint + """Challenge PUT accept endpoint Parameters: req: falcon.Request HTTP request @@ -1466,10 +1629,14 @@ def on_put(req, rep, source): agent = req.context.agent body = req.get_media() if "said" not in body: - raise falcon.HTTPBadRequest(description="challenge response acceptance requires 'aid' and 'said'") + raise falcon.HTTPBadRequest( + description="challenge response acceptance requires 'aid' and 'said'" + ) if source not in agent.hby.kevers: - raise falcon.HTTPNotFound(description=f"challenge response source={source} not found") + raise falcon.HTTPNotFound( + description=f"challenge response source={source} not found" + ) said = body["said"] saider = coring.Saider(qb64=said) @@ -1481,7 +1648,7 @@ def on_put(req, rep, source): class ContactCollectionEnd: def on_get(self, req, rep): - """ Contact plural GET endpoint + """Contact plural GET endpoint Parameters: req: falcon.Request HTTP request @@ -1535,7 +1702,9 @@ def on_get(self, req, rep): elif field is not None: val = req.params.get("filter_value") if val is None: - raise falcon.HTTPBadRequest(description="filter_value if required if field_field is specified") + raise falcon.HTTPBadRequest( + description="filter_value if required if field_field is specified" + ) contacts = agent.org.find(field=field, val=val) self.authn(agent, contacts) @@ -1558,10 +1727,10 @@ def on_get(self, req, rep): @staticmethod def authn(agent, contacts): for contact in contacts: - aid = contact['id'] + aid = contact["id"] ends = agent.agentHab.endsFor(aid) - contact['ends'] = ends + contact["ends"] = ends accepted = [saider.qb64 for saider in agent.hby.db.chas.get(keys=(aid,))] received = [saider.qb64 for saider in agent.hby.db.reps.get(keys=(aid,))] @@ -1569,8 +1738,14 @@ def authn(agent, contacts): challenges = [] for said in received: exn = agent.hby.db.exns.get(keys=(said,)) - challenges.append(dict(dt=exn.ked['dt'], words=exn.ked['a']['words'], said=said, - authenticated=said in accepted)) + challenges.append( + dict( + dt=exn.ked["dt"], + words=exn.ked["a"]["words"], + said=said, + authenticated=said in accepted, + ) + ) contact["challenges"] = challenges @@ -1622,7 +1797,9 @@ def on_post(req, rep, prefix): """ agent = req.context.agent if prefix not in agent.hby.kevers: - raise falcon.HTTPNotFound(description=f"{prefix} is not a known identifier.") + raise falcon.HTTPNotFound( + description=f"{prefix} is not a known identifier." + ) if req.content_length > 1000000: raise falcon.HTTPBadRequest(description="image too big to save") @@ -1632,47 +1809,49 @@ def on_post(req, rep, prefix): @staticmethod def on_get(req, rep, prefix): - """ Contact image GET endpoint + """Contact image GET endpoint - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - prefix: qb64 identifier prefix of contact information to get + Parameters: + req: falcon.Request HTTP request + rep: falcon.Response HTTP response + prefix: qb64 identifier prefix of contact information to get - --- - summary: Get contact image for identifer prefix - description: Get contact image for identifer prefix - tags: - - Contacts - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix of contact image to get - responses: - 200: - description: Contact information successfully retrieved for prefix - content: - image/jpg: - schema: - description: Image - type: binary - 404: - description: No contact information found for prefix + --- + summary: Get contact image for identifer prefix + description: Get contact image for identifer prefix + tags: + - Contacts + parameters: + - in: path + name: prefix + schema: + type: string + required: true + description: qb64 identifier prefix of contact image to get + responses: + 200: + description: Contact information successfully retrieved for prefix + content: + image/jpg: + schema: + description: Image + type: binary + 404: + description: No contact information found for prefix """ agent = req.context.agent if prefix not in agent.hby.kevers: - raise falcon.HTTPNotFound(description=f"{prefix} is not a known identifier.") + raise falcon.HTTPNotFound( + description=f"{prefix} is not a known identifier." + ) data = agent.org.getImgData(pre=prefix) if data is None: raise falcon.HTTPNotFound(description=f"no image available for {prefix}.") rep.status = falcon.HTTP_200 - rep.set_header('Content-Type', data["type"]) - rep.set_header('Content-Length', data["length"]) + rep.set_header("Content-Type", data["type"]) + rep.set_header("Content-Length", data["length"]) rep.stream = agent.org.getImg(pre=prefix) @@ -1680,35 +1859,37 @@ class ContactResourceEnd: @staticmethod def on_get(req, rep, prefix): - """ Contact GET endpoint + """Contact GET endpoint - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - prefix: qb64 identifier prefix of contact information to get + Parameters: + req: falcon.Request HTTP request + rep: falcon.Response HTTP response + prefix: qb64 identifier prefix of contact information to get - --- - summary: Get contact information associated with single remote identifier - description: Get contact information associated with single remote identifier. All - information is meta-data and kept in local storage only - tags: - - Contacts - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix of contact to get - responses: - 200: - description: Contact information successfully retrieved for prefix - 404: - description: No contact information found for prefix + --- + summary: Get contact information associated with single remote identifier + description: Get contact information associated with single remote identifier. All + information is meta-data and kept in local storage only + tags: + - Contacts + parameters: + - in: path + name: prefix + schema: + type: string + required: true + description: qb64 identifier prefix of contact to get + responses: + 200: + description: Contact information successfully retrieved for prefix + 404: + description: No contact information found for prefix """ agent = req.context.agent if prefix not in agent.hby.kevers: - raise falcon.HTTPNotFound(description=f"{prefix} is not a known identifier.") + raise falcon.HTTPNotFound( + description=f"{prefix} is not a known identifier." + ) contact = agent.org.get(prefix) if contact is None: @@ -1719,57 +1900,63 @@ def on_get(req, rep, prefix): @staticmethod def on_post(req, rep, prefix): - """ Contact plural GET endpoint + """Contact plural GET endpoint - Parameters: - req: falcon.Request HTTP request - rep: falcon.Response HTTP response - prefix: human readable name of identifier to replace contact information + Parameters: + req: falcon.Request HTTP request + rep: falcon.Response HTTP response + prefix: human readable name of identifier to replace contact information - --- - summary: Create new contact information for an identifier - description: Creates new information for an identifier, overwriting all existing - information for that identifier - tags: - - Contacts - parameters: - - in: path - name: prefix - schema: - type: string - required: true - description: qb64 identifier prefix to add contact metadata to - requestBody: - required: true - content: - application/json: - schema: - description: Contact information - type: object + --- + summary: Create new contact information for an identifier + description: Creates new information for an identifier, overwriting all existing + information for that identifier + tags: + - Contacts + parameters: + - in: path + name: prefix + schema: + type: string + required: true + description: qb64 identifier prefix to add contact metadata to + requestBody: + required: true + content: + application/json: + schema: + description: Contact information + type: object - responses: - 200: - description: Updated contact information for remote identifier - 400: - description: Invalid identifier used to update contact information - 404: - description: Prefix not found in identifier contact information + responses: + 200: + description: Updated contact information for remote identifier + 400: + description: Invalid identifier used to update contact information + 404: + description: Prefix not found in identifier contact information """ agent = req.context.agent body = req.get_media() if prefix not in agent.hby.kevers: - raise falcon.HTTPNotFound(description="{prefix} is not a known identifier. oobi required before contact " - "information") + raise falcon.HTTPNotFound( + description="{prefix} is not a known identifier. oobi required before contact " + "information" + ) if prefix in agent.hby.prefixes: - raise falcon.HTTPBadRequest(description=f"{prefix} is a local identifier, contact information only for " - f"remote identifiers") + raise falcon.HTTPBadRequest( + description=f"{prefix} is a local identifier, contact information only for " + f"remote identifiers" + ) if "id" in body: del body["id"] if agent.org.get(prefix): - raise falcon.HTTPBadRequest(description=f"contact data for {prefix} already exists") + raise falcon.HTTPBadRequest( + description=f"contact data for {prefix} already exists" + ) agent.org.replace(prefix, body) contact = agent.org.get(prefix) @@ -1779,7 +1966,7 @@ def on_post(req, rep, prefix): @staticmethod def on_put(req, rep, prefix): - """ Contact PUT endpoint + """Contact PUT endpoint Parameters: req: falcon.Request HTTP request @@ -1819,11 +2006,13 @@ def on_put(req, rep, prefix): body = req.get_media() if prefix not in agent.hby.kevers: raise falcon.HTTPNotFound( - description=f"{prefix} is not a known identifier. oobi required before contact information") + description=f"{prefix} is not a known identifier. oobi required before contact information" + ) if prefix in agent.hby.prefixes: raise falcon.HTTPBadRequest( - description=f"{prefix} is a local identifier, contact information only for remote identifiers") + description=f"{prefix} is a local identifier, contact information only for remote identifiers" + ) if "id" in body: del body["id"] @@ -1836,7 +2025,7 @@ def on_put(req, rep, prefix): @staticmethod def on_delete(req, rep, prefix): - """ Contact plural GET endpoint + """Contact plural GET endpoint Parameters: req: falcon.Request HTTP request @@ -1864,7 +2053,9 @@ def on_delete(req, rep, prefix): agent = req.context.agent deleted = agent.org.rem(prefix) if not deleted: - raise falcon.HTTPNotFound(description=f"no contact information to delete for {prefix}") + raise falcon.HTTPNotFound( + description=f"no contact information to delete for {prefix}" + ) rep.status = falcon.HTTP_202 @@ -1907,22 +2098,20 @@ def on_get(req, rep, name): raise falcon.errors.HTTPNotFound(description=f"invalid alias {name}") if not isinstance(hab, habbing.SignifyGroupHab): - raise falcon.HTTPBadRequest(description="members endpoint only available for group AIDs") + raise falcon.HTTPBadRequest( + description="members endpoint only available for group AIDs" + ) smids = hab.db.signingMembers(hab.pre) rmids = hab.db.rotationMembers(hab.pre) signing = [] for smid in smids: - if isinstance(smid,dict): - smid = smid['i'] ends = hab.endsFor(smid) signing.append(dict(aid=smid, ends=ends)) rotation = [] for rmid in rmids: - if isinstance(rmid,dict): - rmid = rmid['i'] ends = hab.endsFor(rmid) rotation.append(dict(aid=rmid, ends=ends)) diff --git a/src/keria/app/grouping.py b/src/keria/app/grouping.py index a2c23877..05583e82 100644 --- a/src/keria/app/grouping.py +++ b/src/keria/app/grouping.py @@ -71,7 +71,7 @@ def on_post(req, rep, name): del ims[:serder.size] slist = hab.db.signingMembers(pre=hab.pre) - smids = [d['i'] for d in slist if 'i' in d] + smids = slist smids.remove(hab.mhab.pre) agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=smids, topic='multisig')) diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index f07e0c95..96d2c4de 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -522,13 +522,14 @@ def test_identifier_collection_end(helpers): sigers = [signer0.sign(ser=serder.raw, index=0).qb64, p1.sign(ser=serder.raw, indices=[1])[0].qb64, p2.sign(ser=serder.raw, indices=[2])[0].qb64] states = nstates = [agent0, asdict(p1.kever.state()), asdict(p2.kever.state())] + smids = rmids = [state['i'] for state in states if 'i' in state] body = { 'name': 'multisig', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "keys": keys, "ndigs": ndigs @@ -546,8 +547,8 @@ def test_identifier_collection_end(helpers): 'name': 'multisig', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": bad, "keys": keys, @@ -564,8 +565,8 @@ def test_identifier_collection_end(helpers): 'name': 'multisig', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": mhab, "ndigs": ndigs @@ -581,8 +582,8 @@ def test_identifier_collection_end(helpers): 'name': 'multisig', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": mhab, "keys": keys, @@ -599,8 +600,8 @@ def test_identifier_collection_end(helpers): 'name': 'multisig', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": mhab, "keys": keys, diff --git a/tests/app/test_grouping.py b/tests/app/test_grouping.py index 3849d500..e2af3468 100644 --- a/tests/app/test_grouping.py +++ b/tests/app/test_grouping.py @@ -83,13 +83,14 @@ def test_multisig_request_ends(helpers): # Send in all signatures as if we are joining the inception event sigers = [signer0.sign(ser=serder.raw, index=0).qb64, signer1.sign(ser=serder.raw, index=1).qb64] states = nstates = [m0['state'], m1['state']] + smids = rmids = [state['i'] for state in states if 'i' in state] body = { 'name': 'multisig', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": m0, "keys": keys, diff --git a/tests/app/test_ipexing.py b/tests/app/test_ipexing.py index d44df504..75b20c25 100644 --- a/tests/app/test_ipexing.py +++ b/tests/app/test_ipexing.py @@ -194,13 +194,14 @@ def test_ipex_grant(helpers, mockHelpingNowIso8601, seeder): # Send in all signatures as if we are joining the inception event sigers = [signer0.sign(ser=serder.raw, index=0).qb64, signer1.sign(ser=serder.raw, index=1).qb64] states = nstates = [m0['state'], m1['state']] + smids = rmids = [state['i'] for state in states if 'i' in state] body = { 'name': 'multisig', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": m0, "keys": keys, @@ -430,13 +431,14 @@ def test_multisig_grant_admit(seeder, helpers): sigers = [issuerSigner0.sign(ser=serder.raw, index=0).qb64, issuerSigner1.sign(ser=serder.raw, index=1).qb64] states = nstates = [ip0['state'], ip1['state']] + smids = rmids = [state['i'] for state in states if 'i' in state] body = { 'name': 'issuer', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": ip0, "keys": ikeys, @@ -451,8 +453,8 @@ def test_multisig_grant_admit(seeder, helpers): 'name': 'issuer', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": ip1, "keys": ikeys, @@ -530,13 +532,14 @@ def test_multisig_grant_admit(seeder, helpers): # Send in all signatures as if we are joining the inception event sigers = [holderSigner0.sign(ser=serder.raw, index=0).qb64, holderSigner1.sign(ser=serder.raw, index=1).qb64] states = nstates = [hp0['state'], hp1['state']] - + smids = rmids = [state['i'] for state in states if 'i' in state] + body = { 'name': 'holder', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": hp0, "keys": keys, @@ -551,8 +554,8 @@ def test_multisig_grant_admit(seeder, helpers): 'name': 'holder', 'icp': serder.ked, 'sigs': sigers, - "smids": states, - "rmids": nstates, + "smids": smids, + "rmids": rmids, 'group': { "mhab": hp1, "keys": keys, From 59e7041afc7adbfcbbcacc59c62902c7fe80b903 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Fri, 21 Jun 2024 14:00:22 -0400 Subject: [PATCH 19/21] Removed unnecessary annotation Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/delegating.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/keria/app/delegating.py b/src/keria/app/delegating.py index 08d25dc8..da624ed4 100644 --- a/src/keria/app/delegating.py +++ b/src/keria/app/delegating.py @@ -227,7 +227,6 @@ def on_post(self, req, rep, name): except (kering.AuthError, ValueError) as e: raise falcon.HTTPBadRequest(description=e.args[0]) -@staticmethod def approveDelegation(hab, anc) -> str: serder = serdering.SerderKERI(sad=anc) From d3b3ee8cdba1df4f10036bed73eed993825c2b97 Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Fri, 21 Jun 2024 14:36:32 -0400 Subject: [PATCH 20/21] fix multisig join after original creation see https://github.com/WebOfTrust/signify-ts/issues/227 Signed-off-by: 2byrds <2byrds@gmail.com> --- src/keria/app/grouping.py | 21 ++-- tests/app/test_grouping.py | 192 ++++++++++++++++++------------------- 2 files changed, 109 insertions(+), 104 deletions(-) diff --git a/src/keria/app/grouping.py b/src/keria/app/grouping.py index 05583e82..489d9794 100644 --- a/src/keria/app/grouping.py +++ b/src/keria/app/grouping.py @@ -10,10 +10,12 @@ from keri import core from keri.app import habbing from keri.core import coring, eventing, serdering +from keri.help import ogler from keri.kering import SerializeError from keria.core import httping, longrunning +logger = ogler.getLogger() def loadEnds(app): msrCol = MultisigRequestCollectionEnd() @@ -72,7 +74,8 @@ def on_post(req, rep, name): slist = hab.db.signingMembers(pre=hab.pre) smids = slist - smids.remove(hab.mhab.pre) + if hab.mhab.pre in smids: + smids.remove(hab.mhab.pre) agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=smids, topic='multisig')) @@ -150,6 +153,12 @@ def on_post(req, rep, name): # Get the rot, sigs and recipients from the request rot = httping.getRequiredParam(body, "rot") + serder = None + try: + serder = serdering.SerderKERI(sad=rot) + except(SerializeError) as e: + raise falcon.HTTPBadRequest(description=f"{e.args[0]}") + sigs = httping.getRequiredParam(body, "sigs") # Get group specific values @@ -179,13 +188,11 @@ def on_post(req, rep, name): hab = agent.hby.joinSignifyGroupHab(gid, name=name, mhab=mhab, smids=smids, rmids=rmids) try: - hab.make(serder=serdering.SerderKERI(sad=rot), sigers=sigers) - agent.inceptGroup(pre=gid, mpre=mhab.pre, verfers=verfers, digers=digers) - except (ValueError, SerializeError) as e: - agent.hby.deleteHab(name=name) - raise falcon.HTTPBadRequest(description=f"{e.args[0]}") + hab.make(serder=serder, sigers=sigers) + except (ValueError) as e: + logger.info("Already incepted group, continuing...") - serder = serdering.SerderKERI(sad=rot) + agent.inceptGroup(pre=gid, mpre=mhab.pre, verfers=verfers, digers=digers) agent.groups.append(dict(pre=hab.pre, serder=serder, sigers=sigers, smids=smids, rmids=rmids)) op = agent.monitor.submit(serder.pre, longrunning.OpTypes.group, metadata=dict(sn=serder.sn)) diff --git a/tests/app/test_grouping.py b/tests/app/test_grouping.py index e2af3468..0d9e0acd 100644 --- a/tests/app/test_grouping.py +++ b/tests/app/test_grouping.py @@ -42,19 +42,19 @@ def test_multisig_request_ends(helpers): app.add_route("/multisig/request/{said}", msrRes) # First create participants (aid0, aid1) in a multisig AID - salt0 = b'0123456789abcdef' + salt0 = b"0123456789abcdef" op = helpers.createAid(client, "aid0", salt0) aid0 = op["response"] - pre0 = aid0['i'] + pre0 = aid0["i"] assert pre0 == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" serder, signers0 = helpers.incept(salt0, "signify:aid", pidx=0) assert serder.pre == pre0 signer0 = signers0[0] - salt1 = b'abcdef0123456789' + salt1 = b"abcdef0123456789" op = helpers.createAid(client, "aid1", salt1) aid1 = op["response"] - pre1 = aid1['i'] + pre1 = aid1["i"] assert pre1 == "EMgdjM1qALk3jlh4P2YyLRSTcjSOjLXD3e_uYpxbdbg6" serder, signers1 = helpers.incept(salt1, "signify:aid", pidx=0) assert serder.pre == pre1 @@ -67,35 +67,35 @@ def test_multisig_request_ends(helpers): assert m0["prefix"] == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" assert m1["prefix"] == "EMgdjM1qALk3jlh4P2YyLRSTcjSOjLXD3e_uYpxbdbg6" - keys = [m0['state']['k'][0], m1['state']['k'][0]] - ndigs = [m0['state']['n'][0], m1['state']['n'][0]] + keys = [m0["state"]["k"][0], m1["state"]["k"][0]] + ndigs = [m0["state"]["n"][0], m1["state"]["n"][0]] # Create the mutlsig inception event - serder = eventing.incept(keys=keys, - isith="2", - nsith="2", - ndigs=ndigs, - code=coring.MtrDex.Blake3_256, - toad=0, - wits=[]) + serder = eventing.incept( + keys=keys, + isith="2", + nsith="2", + ndigs=ndigs, + code=coring.MtrDex.Blake3_256, + toad=0, + wits=[], + ) assert serder.said == "EG8p1Zb4BfyKYkA9SkpyTvCo9xoCsISlOl7YlsB5b1Vt" # Send in all signatures as if we are joining the inception event - sigers = [signer0.sign(ser=serder.raw, index=0).qb64, signer1.sign(ser=serder.raw, index=1).qb64] - states = nstates = [m0['state'], m1['state']] - smids = rmids = [state['i'] for state in states if 'i' in state] + sigers = [ + signer0.sign(ser=serder.raw, index=0).qb64, + signer1.sign(ser=serder.raw, index=1).qb64, + ] + states = nstates = [m0["state"], m1["state"]] body = { - 'name': 'multisig', - 'icp': serder.ked, - 'sigs': sigers, - "smids": smids, - "rmids": rmids, - 'group': { - "mhab": m0, - "keys": keys, - "ndigs": ndigs - } + "name": "multisig", + "icp": serder.ked, + "sigs": sigers, + "smids": states, + "rmids": nstates, + "group": {"mhab": m0, "keys": keys, "ndigs": ndigs}, } res = client.simulate_post(path="/identifiers", body=json.dumps(body)) @@ -103,41 +103,44 @@ def test_multisig_request_ends(helpers): # Get the multisig AID hab dict m2 = client.simulate_get(path="/identifiers/multisig").json - pre2 = m2['prefix'] + pre2 = m2["prefix"] assert pre2 == "EG8p1Zb4BfyKYkA9SkpyTvCo9xoCsISlOl7YlsB5b1Vt" - payload = dict(i=pre2, words="these are the words being signed for this response") - cexn, _ = exchanging.exchange(route="/challenge/response", payload=payload, sender=agent.agentHab.pre) + payload = dict( + i=pre2, words="these are the words being signed for this response" + ) + cexn, _ = exchanging.exchange( + route="/challenge/response", payload=payload, sender=agent.agentHab.pre + ) # Signing this with agentHab because I'm lazing. Nothing will be done with this signature cha = agent.agentHab.endorse(serder=cexn, last=False, pipelined=False) - embeds = dict( - exn=cha + embeds = dict(exn=cha) + exn, end = exchanging.exchange( + route="/multisig/exn", payload=dict(gid=pre2), embeds=embeds, sender=pre0 ) - exn, end = exchanging.exchange(route="/multisig/exn", payload=dict(gid=pre2), embeds=embeds, - sender=pre0) sig = signer0.sign(exn.raw, index=0).qb64 - body = dict( - exn=exn.ked, - sigs=[sig], - atc=end.decode("utf-8") - ) + body = dict(exn=exn.ked, sigs=[sig], atc=end.decode("utf-8")) - res = client.simulate_post(path="/identifiers/badaid/multisig/request", json=body) + res = client.simulate_post( + path="/identifiers/badaid/multisig/request", json=body + ) assert res.status_code == 404 res = client.simulate_post(path="/identifiers/aid1/multisig/request", json=body) assert res.status_code == 400 - res = client.simulate_post(path="/identifiers/multisig/multisig/request", json=body) + res = client.simulate_post( + path="/identifiers/multisig/multisig/request", json=body + ) assert res.status_code == 200 assert res.json == exn.ked said = exn.said # Fudge this because we won't be able to save a message from someone else: - esaid = exn.ked['e']['d'] + esaid = exn.ked["e"]["d"] agent.hby.db.meids.add(keys=(esaid,), val=coring.Saider(qb64=exn.said)) res = client.simulate_get(path=f"/multisig/request/BADSAID") @@ -149,9 +152,9 @@ def test_multisig_request_ends(helpers): req = res.json[0] - assert req['exn'] == exn.ked - path = req['paths']['exn'] - assert '-LA35AACAA-e-exn' + path == end.decode("utf-8") + assert req["exn"] == exn.ked + path = req["paths"]["exn"] + assert "-LA35AACAA-e-exn" + path == end.decode("utf-8") # We've send this one exn to our other participants assert len(agent.exchanges) == 1 @@ -166,7 +169,7 @@ def test_join(helpers, monkeypatch): app.add_route("/identifiers", end) app.add_route("/identifiers/{name}", resend) - salt = b'0123456789abcdef' + salt = b"0123456789abcdef" op = helpers.createAid(client, "recipient", salt) aid = op["response"] @@ -188,8 +191,8 @@ def test_join(helpers, monkeypatch): "EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc", "EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5", "EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh", - "EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs" - ] + "EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs", + ], ), sigs=[], gid="EDWg3-rB5FTpcckaYdBcexGmbLIO6AvAwjaJTBlXUn_I", @@ -199,7 +202,7 @@ def test_join(helpers, monkeypatch): "EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc", "EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5", "EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh", - "EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs" + "EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs", ], rmids=[ "EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4", @@ -207,83 +210,74 @@ def test_join(helpers, monkeypatch): "EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc", "EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5", "EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh", - "EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs" - ] + "EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs", + ], ) res = client.simulate_post("/identifiers/mms/multisig/join", json=body) assert res.status_code == 400 - for smid in body['smids']: + for smid in body["smids"]: agent.hby.kevers[smid] = {} - for rmid in body['rmids']: + for rmid in body["rmids"]: agent.hby.kevers[rmid] = {} res = client.simulate_post("/identifiers/mms/multisig/join", json=body) assert res.status_code == 400 - assert res.json == {'description': 'Invalid multisig group rotation request, signing member list ' - "must contain a local identifier'", - 'title': '400 Bad Request'} - - body['smids'][0] = aid["i"] + assert res.json == { + "description": "Missing version string field in {'k': " + "['DNp1NUbUEgei6KOlIfT5evXueOi3TDFZkUXgJQWNvegf', " + "'DLsXs0-dxqrM4hugX7NkfZUzET13ngfRhWC9GgXvX9my', " + "'DE2W_yGSF-m44vXPuQ5_wHJ9EK59N-OIT3hABgdAcCKs', " + "'DKFKNK7s0xLhazlmL3xH9YEl9sc3fVoqUSsQxK6DZ3oC', " + "'DEyEcy5NzjqA3KQ1DTE0BJs-XMIdWIvPWligyq6y1TxS', " + "'DGhflVckn2wVLJH6wq94gGQxmpvsFdsZvd61Owj3Qhjl'], 'n': " + "['EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4', " + "'EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1', " + "'EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc', " + "'EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5', " + "'EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh', " + "'EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs']}.", + "title": "400 Bad Request", + } res = client.simulate_post("/identifiers/mms/multisig/join", json=body) assert res.status_code == 400 - assert res.json == {'description': "Missing version string field in {'k': " - "['DNp1NUbUEgei6KOlIfT5evXueOi3TDFZkUXgJQWNvegf', " - "'DLsXs0-dxqrM4hugX7NkfZUzET13ngfRhWC9GgXvX9my', " - "'DE2W_yGSF-m44vXPuQ5_wHJ9EK59N-OIT3hABgdAcCKs', " - "'DKFKNK7s0xLhazlmL3xH9YEl9sc3fVoqUSsQxK6DZ3oC', " - "'DEyEcy5NzjqA3KQ1DTE0BJs-XMIdWIvPWligyq6y1TxS', " - "'DGhflVckn2wVLJH6wq94gGQxmpvsFdsZvd61Owj3Qhjl'], 'n': " - "['EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4', " - "'EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1', " - "'EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc', " - "'EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5', " - "'EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh', " - "'EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs']}.", - 'title': '400 Bad Request'} - - body['rot'] = { + assert res.json == { + "title": "400 Bad Request", + "description": "Missing version string field in {'k': ['DNp1NUbUEgei6KOlIfT5evXueOi3TDFZkUXgJQWNvegf', 'DLsXs0-dxqrM4hugX7NkfZUzET13ngfRhWC9GgXvX9my', 'DE2W_yGSF-m44vXPuQ5_wHJ9EK59N-OIT3hABgdAcCKs', 'DKFKNK7s0xLhazlmL3xH9YEl9sc3fVoqUSsQxK6DZ3oC', 'DEyEcy5NzjqA3KQ1DTE0BJs-XMIdWIvPWligyq6y1TxS', 'DGhflVckn2wVLJH6wq94gGQxmpvsFdsZvd61Owj3Qhjl'], 'n': ['EKYLUMmNPZeEs77Zvclf0bSN5IN-mLfLpx2ySb-HDlk4', 'EJccSRTfXYF6wrUVuenAIHzwcx3hJugeiJsEKmndi5q1', 'EBFg-5SGDCv5YfwpkArWRBdTxNRUXU8uVcDKNzizOQZc', 'EBmW2bXbgsP3HITwW3FmITzAb3wVmHlxCusZ46vgGgP5', 'EL4RpdS2Atb2Syu5xLdpz9CcNNYoFUUDlLHxHD09vcgh', 'EAiBVuuhCZrgckeHc9KzROVGJpmGbk2-e1B25GaeRrJs']}.", + } + + body["smids"][0] = aid["i"] + + body["rot"] = { "v": "KERI10JSON00030c_", "t": "rot", "d": "EPKCBT0rSgFKTDRjynYzOTsYWo7fDNElTxFbRZZW9f6R", "i": "EDWg3-rB5FTpcckaYdBcexGmbLIO6AvAwjaJTBlXUn_I", "s": "3", "p": "EM2OaIZuLWyGGyxf4Tzs6yeoENvjP47i1Dn88GGxw3_Z", - "kt": [ - "0", - "0", - "1/2", - "1/2", - "1/2", - "1/2" - ], + "kt": ["0", "0", "1/2", "1/2", "1/2", "1/2"], "k": [ "DNp1NUbUEgei6KOlIfT5evXueOi3TDFZkUXgJQWNvegf", "DLsXs0-dxqrM4hugX7NkfZUzET13ngfRhWC9GgXvX9my", "DE2W_yGSF-m44vXPuQ5_wHJ9EK59N-OIT3hABgdAcCKs", "DKFKNK7s0xLhazlmL3xH9YEl9sc3fVoqUSsQxK6DZ3oC", "DEyEcy5NzjqA3KQ1DTE0BJs-XMIdWIvPWligyq6y1TxS", - "DGhflVckn2wVLJH6wq94gGQxmpvsFdsZvd61Owj3Qhjl" - ], - "nt": [ - "1/2", - "1/2", - "1/2", - "1/2" + "DGhflVckn2wVLJH6wq94gGQxmpvsFdsZvd61Owj3Qhjl", ], + "nt": ["1/2", "1/2", "1/2", "1/2"], "n": [ "EDr0gf60BDB9cZyVoz_Os55Ma49muyCNTZoWG-VWAe6g", "EIM3hKH1VBG_ofS7hD-XMfTG-dP1ziJwloFhrNx34G7o", "EOi609MGQlByLPdaUgqGQn_IOEE4cf6u7zCW-J3E82Qz", - "ECQF1Tdpcqew6dqN6nHNpz4jhYTZtojl7EpqVJhXRBav" + "ECQF1Tdpcqew6dqN6nHNpz4jhYTZtojl7EpqVJhXRBav", ], "bt": "3", "br": [], "ba": [], - "a": [] + "a": [], } def make(self, serder, sigers): @@ -293,13 +287,17 @@ def make(self, serder, sigers): res = client.simulate_post("/identifiers/mms/multisig/join", json=body) assert res.status_code == 202 - assert res.json == {'done': False, - 'error': None, - 'metadata': {'sn': 3}, - 'name': 'group.EDWg3-rB5FTpcckaYdBcexGmbLIO6AvAwjaJTBlXUn_I', - 'response': None} + assert res.json == { + "done": False, + "error": None, + "metadata": {"sn": 3}, + "name": "group.EDWg3-rB5FTpcckaYdBcexGmbLIO6AvAwjaJTBlXUn_I", + "response": None, + } res = client.simulate_post("/identifiers/mms/multisig/join", json=body) assert res.status_code == 400 - assert res.json == {'description': 'attempt to create identifier with an already used alias=mms', - 'title': '400 Bad Request'} + assert res.json == { + "description": "attempt to create identifier with an already used alias=mms", + "title": "400 Bad Request", + } From 76d9a548bc32c0963031a78f9f73f1128bf4e67a Mon Sep 17 00:00:00 2001 From: 2byrds <2byrds@gmail.com> Date: Fri, 21 Jun 2024 20:32:13 -0400 Subject: [PATCH 21/21] cleanup docker image entrypoint and instructions Signed-off-by: 2byrds <2byrds@gmail.com> --- README.md | 6 ++++++ images/keria.dockerfile | 4 +--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 540223ab..44897e86 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,12 @@ All Agent db access is through the associated Agent. make build-keria ``` +#### Run with docker +* Specify an entrypoint with proper configuration, for instance if you want to use the demo-witness-oobis that is under the scripts/keri/cf dir: +``` +ENTRYPOINT ["keria", "start", "--config-file", "demo-witness-oobis", "--config-dir", "./scripts"] +``` +You can see a [working example here](https://github.com/WebOfTrust/signify-ts/blob/main/docker-compose.yaml). ### Running Tests diff --git a/images/keria.dockerfile b/images/keria.dockerfile index 6f70f3b1..fa500481 100644 --- a/images/keria.dockerfile +++ b/images/keria.dockerfile @@ -7,6 +7,4 @@ COPY . /usr/local/var/keria WORKDIR /usr/local/var/keria -RUN pip install -r requirements.txt - -ENTRYPOINT ["keria", "start", "--config-file", "demo-witness-oobis", "--config-dir", "./scripts"] +RUN pip install -r requirements.txt \ No newline at end of file