diff --git a/.github/workflows/python-app-ci.yml b/.github/workflows/python-app-ci.yml index 0a00cfc0..c4097aeb 100644 --- a/.github/workflows/python-app-ci.yml +++ b/.github/workflows/python-app-ci.yml @@ -21,10 +21,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10.4 + - name: Set up Python 3.12.2 uses: actions/setup-python@v2 with: - python-version: 3.10.4 + python-version: 3.12.2 - name: Install dependencies run: | python -m pip install --upgrade pip @@ -44,10 +44,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10.4 + - name: Set up Python 3.12.2 uses: actions/setup-python@v2 with: - python-version: 3.10.4 + python-version: 3.12.2 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/README.md b/README.md index f281ce28..540223ab 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ All Agent db access is through the associated Agent. ### Setup -* Ensure [Python](https://www.python.org/downloads/) `version 3.10.4+` is installed +* Ensure [Python](https://www.python.org/downloads/) `version 3.12.14+` is installed * Install [Keripy dependency](https://github.com/WebOfTrust/keripy#dependencies) (`libsodium 1.0.18+`) diff --git a/docs/README.rst b/docs/README.rst index b4d24334..daa0f280 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -14,7 +14,7 @@ Setup ~~~~~ - Ensure `Python `__ - ``version 3.10.4+`` is installed + ``version 3.12.1+`` is installed - Install `Keripy dependency `__ (``libsodium 1.0.18+``) diff --git a/images/keria.dockerfile b/images/keria.dockerfile index 34f9e3e4..5d2f0bb5 100644 --- a/images/keria.dockerfile +++ b/images/keria.dockerfile @@ -1,5 +1,5 @@ # Builder stage -FROM python:3.10.13-alpine3.18 as builder +FROM python:3.12-alpine3.19 as builder # Install compilation dependencies RUN apk --no-cache add \ @@ -29,7 +29,7 @@ RUN . "$HOME/.cargo/env" && \ pip install -r requirements.txt # Runtime stage -FROM python:3.10.13-alpine3.18 +FROM python:3.12-alpine3.19 # Install runtime dependencies RUN apk --no-cache add \ diff --git a/setup.py b/setup.py index 62b62a11..91683141 100644 --- a/setup.py +++ b/setup.py @@ -73,13 +73,13 @@ "resolver", # eg: 'keyword1', 'keyword2', 'keyword3', ], - python_requires='>=3.10.4', + python_requires='>=3.12.2', install_requires=[ - 'hio>=0.6.9', - 'keri>=1.1.6', + 'hio>=0.6.12', + 'keri>=1.2.0-dev0', 'mnemonic>=0.20', 'multicommand>=1.0.0', - 'falcon>=3.1.0', + 'falcon>=3.1.3', 'http_sfv>=0.9.8', 'dataclasses_json>=0.5.7', 'apispec>=6.3.0', diff --git a/src/keria/app/agenting.py b/src/keria/app/agenting.py index 963fb716..a548aae7 100644 --- a/src/keria/app/agenting.py +++ b/src/keria/app/agenting.py @@ -9,15 +9,19 @@ from dataclasses import asdict from urllib.parse import urlparse, urljoin -from keri import kering -from keri.app.notifying import Notifier -from keri.app.storing import Mailboxer import falcon from falcon import media from hio.base import doing from hio.core import http, tcp from hio.help import decking + +from keri import kering +from keri import core +from keri.app.notifying import Notifier +from keri.app.storing import Mailboxer + + from keri.app import configing, keeping, habbing, storing, signaling, oobiing, agenting, \ forwarding, querying, connecting, grouping from keri.app.grouping import Counselor @@ -149,7 +153,7 @@ def createHttpServer(port, app, keypath=None, certpath=None, cafilepath=None): class Agency(doing.DoDoer): """ Agency - + """ def __init__(self, name, bran, base="", configFile=None, configDir=None, adb=None, temp=False): @@ -173,7 +177,7 @@ def __init__(self, name, bran, base="", configFile=None, configDir=None, adb=Non self.adb = adb if adb is not None else basing.AgencyBaser(name="TheAgency", base=base, reopen=True, temp=temp) super(Agency, self).__init__(doers=[], always=True) - def create(self, caid): + def create(self, caid, salt=None): ks = keeping.Keeper(name=caid, base=self.base, temp=self.temp, @@ -196,7 +200,7 @@ def create(self, caid): cf.put(data) # Create the Hab for the Agent with only 2 AIDs - agentHby = habbing.Habery(name=caid, base=self.base, bran=self.bran, ks=ks, cf=cf, temp=self.temp) + agentHby = habbing.Habery(name=caid, base=self.base, bran=self.bran, ks=ks, cf=cf, temp=self.temp, salt=salt) agentHab = agentHby.makeHab(f"agent-{caid}", ns="agent", transferable=True, delpre=caid) agentRgy = Regery(hby=agentHby, name=agentHab.name, base=self.base, temp=self.temp) @@ -287,7 +291,7 @@ def __init__(self, hby, rgy, agentHab, agency, caid, **opts): self.agency = agency self.caid = caid - self.swain = delegating.Sealer(hby=hby, proxy=agentHab) + self.swain = delegating.Anchorer(hby=hby, proxy=agentHab) self.counselor = Counselor(hby=hby, swain=self.swain, proxy=agentHab) self.org = connecting.Organizer(hby=hby) @@ -814,7 +818,7 @@ def on_post(self, req, rep): if "sig" not in body: raise falcon.HTTPBadRequest(title="invalid inception", description=f'required field "sig" missing from body') - siger = coring.Siger(qb64=body["sig"]) + siger = core.Siger(qb64=body["sig"]) caid = icp.pre @@ -972,12 +976,12 @@ def on_get(req, rep): preb = pre.encode("utf-8") events = [] for fn, dig in agent.hby.db.getFelItemPreIter(preb, fn=0): - dgkey = dbing.dgKey(preb, dig) # get message - if not (raw := agent.hby.db.getEvt(key=dgkey)): + if not (raw := agent.hby.db.cloneEvtMsg(pre=preb, fn=fn, dig=dig)): raise falcon.HTTPInternalServerError(f"Missing event for dig={dig}.") serder = serdering.SerderKERI(raw=bytes(raw)) - events.append(serder.ked) + atc = raw[serder.size:] + events.append(dict(ked=serder.ked, atc=atc.decode("utf-8"))) rep.status = falcon.HTTP_200 rep.content_type = "application/json" diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index b9968980..581cee8b 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -10,6 +10,7 @@ import falcon from keri import kering +from keri import core from keri.app import habbing from keri.app.keeping import Algos from keri.core import coring, serdering @@ -31,6 +32,7 @@ def loadEnds(app, agency, authn): app.add_route("/identifiers", aidsEnd) aidEnd = IdentifierResourceEnd() app.add_route("/identifiers/{name}", aidEnd) + app.add_route("/identifiers/{name}/events", aidEnd) aidOOBIsEnd = IdentifierOOBICollectionEnd() app.add_route("/identifiers/{name}/oobis", aidOOBIsEnd) @@ -95,7 +97,12 @@ def on_get(self, _, rep, caid): if agent.caid not in agent.hby.kevers: raise falcon.HTTPBadRequest(description=f"invalid controller configuration, {agent.caid} not found") - pidx = agent.hby.db.habs.cntAll() + pidx = 0 + for name, _ in agent.hby.db.names.getItemIter(): + if name[0] != "agent": + pidx += 1 + + # pidx = agent.hby.db.habs.cntAll() state = asdict(agent.hby.kevers[agent.caid].state()) key = dbing.dgKey(state['i'], state['ee']['d']) # digest key @@ -155,11 +162,11 @@ def on_put(self, req, rep, caid): sigs = body["sigs"] ctrlHab = agent.hby.habByName(caid, ns="agent") - ctrlHab.rotate(serder=rot, sigers=[coring.Siger(qb64=sig) for sig in sigs]) + ctrlHab.rotate(serder=rot, sigers=[core.Siger(qb64=sig) for sig in sigs]) if not self.authn.verify(req): raise falcon.HTTPForbidden(description="invalid signature on request") - + sxlt = body["sxlt"] agent.mgr.sxlt = sxlt @@ -210,7 +217,7 @@ def interact(req, rep, agent, caid): ked = body['ixn'] sigs = body['sigs'] ixn = serdering.SerderKERI(sad=ked) - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] ctrlHab = agent.hby.habByName(caid, ns="agent") @@ -269,17 +276,18 @@ def on_get(req, rep): start, end = httping.parseRangeHeader(rng, "aids") count = agent.hby.db.habs.cntAll() - it = agent.hby.db.habs.getItemIter() + it = agent.hby.db.names.getItemIter() for _ in range(start): try: next(it) except StopIteration: break - for name, habord in it: - name = ".".join(name) # detupleize the database key name + for (ns, name), pre in it: + if ns == "agent": + continue - hab = agent.hby.habByName(name) + hab = agent.hby.habs[pre] data = info(hab, agent.mgr) res.append(data) @@ -288,7 +296,7 @@ def on_get(req, rep): end = start + (len(res) - 1) if len(res) > 0 else 0 rep.set_header("Accept-Ranges", "aids") - rep.set_header("Content-Range", f"aids {start}-{end}/{count}") + rep.set_header("Content-Range", f"aids {start}-{end}/{count - 1}") rep.content_type = "application/json" rep.data = json.dumps(res).encode("utf-8") @@ -310,7 +318,7 @@ def on_post(req, rep): serder = serdering.SerderKERI(sad=icp) - sigers = [coring.Siger(qb64=sig) for sig in sigs] + 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") @@ -350,10 +358,12 @@ def on_post(req, rep): ndigs = group["ndigs"] digers = [coring.Diger(qb64=ndig) for ndig in ndigs] - smids = httping.getRequiredParam(body, "smids") - rmids = httping.getRequiredParam(body, "rmids") - - hab = agent.hby.makeSignifyGroupHab(name, mhab=mhab, serder=serder, sigers=sigers) + 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, + sigers=sigers) try: agent.inceptGroup(pre=serder.pre, mpre=mhab.pre, verfers=verfers, digers=digers) except ValueError as e: @@ -361,7 +371,7 @@ def on_post(req, rep): 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=smids, rmids=rmids)) + 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" @@ -405,7 +415,7 @@ def on_post(req, rep): # 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.delegator: + 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)) @@ -456,7 +466,56 @@ def on_get(req, rep, name): rep.data = json.dumps(data).encode("utf-8") def on_put(self, req, rep, name): - """ Identifier UPDATE endpoint + """ Identifier rename endpoint + + 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() + 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.names.pin(keys=("", newName), val=hab.pre) + hab.db.names.rem(keys=("", name)) + hab.name = newName + hab = agent.hby.habByName(newName) + data = info(hab, agent.mgr, full=True) + rep.status = falcon.HTTP_200 + rep.content_type = "application/json" + rep.data = json.dumps(data).encode("utf-8") + + def on_delete(self, req, rep, name): + """ Identifier delete endpoint + + Parameters: + req (Request): falcon.Request HTTP request object + rep (Response): falcon.Response HTTP response object + name (str): human readable name for Hab to delete + + """ + 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") + agent.hby.deleteHab(name) + rep.status = falcon.HTTP_200 + + def on_post(self, req, rep, name): + """ Identifier events endpoint Parameters: req (Request): falcon.Request HTTP request object @@ -464,15 +523,18 @@ def on_put(self, req, rep, name): name (str): human readable name for Hab to rotate or interact """ + if not name: + raise falcon.HTTPBadRequest(description="name is required") agent = req.context.agent try: body = req.get_media() - typ = Ilks.ixn if req.params.get("type") == "ixn" else Ilks.rot - - if typ in (Ilks.rot,): + if body.get("rot") is not None: op = self.rotate(agent, name, body) - else: + 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") rep.status = falcon.HTTP_200 rep.content_type = "application/json" @@ -486,7 +548,7 @@ def rotate(agent, name, body): hab = agent.hby.habByName(name) if hab is None: raise falcon.HTTPNotFound(title=f"No AID with name {name} found") - + rot = body.get("rot") if rot is None: raise falcon.HTTPBadRequest(title="invalid rotation", @@ -504,7 +566,7 @@ def rotate(agent, name, body): description=f"required field 'sigs' missing from request") serder = serdering.SerderKERI(sad=rot) - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] hab.rotate(serder=serder, sigers=sigers) @@ -537,7 +599,7 @@ def rotate(agent, name, body): return op - if hab.kever.delegator: + 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)) @@ -570,7 +632,7 @@ def interact(agent, name, body): description=f"required field 'sigs' missing from request") serder = serdering.SerderKERI(sad=ixn) - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] hab.interact(serder=serder, sigers=sigers) @@ -611,7 +673,7 @@ def info(hab, rm, full=False): data["state"] = asdict(kever.state()) dgkey = dbing.dgKey(kever.prefixer.qb64b, kever.serder.saidb) wigs = hab.db.getWigs(dgkey) - data["windexes"] = [coring.Siger(qb64b=bytes(wig)).index for wig in wigs] + data["windexes"] = [core.Siger(qb64b=bytes(wig)).index for wig in wigs] return data @@ -649,7 +711,8 @@ 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") @@ -659,7 +722,8 @@ def on_get(req, rep, name): 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") @@ -668,7 +732,10 @@ def on_get(req, rep, name): 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'] = [] else: @@ -792,7 +859,7 @@ def on_post(req, rep, name, aid=None, role=None): raise falcon.errors.HTTPBadRequest( description=f"error trying to create end role for unknown local AID {pre}") - rsigers = [coring.Siger(qb64=rsig) for rsig in rsigs] + rsigers = [core.Siger(qb64=rsig) for rsig in rsigs] tsg = (hab.kever.prefixer, coring.Seqner(sn=hab.kever.sn), coring.Saider(qb64=hab.kever.serder.said), rsigers) try: agent.hby.rvy.processReply(rserder, tsgs=[tsg]) @@ -1145,6 +1212,10 @@ def on_get(self, req, rep): def authn(agent, contacts): for contact in contacts: aid = contact['id'] + + ends = agent.agentHab.endsFor(aid) + 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,))] diff --git a/src/keria/app/cli/commands/start.py b/src/keria/app/cli/commands/start.py index 7381e024..9a9dc2dd 100644 --- a/src/keria/app/cli/commands/start.py +++ b/src/keria/app/cli/commands/start.py @@ -60,11 +60,17 @@ help="TLS server signed certificate (public key) file") parser.add_argument("--cafilepath", action="store", required=False, default=None, help="TLS server CA certificate chain") +parser.add_argument("--loglevel", action="store", required=False, default="CRITICAL", + help="Set log level to DEBUG | INFO | WARNING | ERROR | CRITICAL. Default is CRITICAL") +parser.add_argument("--logfile", action="store", required=False, default=None, + help="path of the log file. If not defined, logs will not be written to the file.") def launch(args): - help.ogler.level = logging.CRITICAL - help.ogler.reopen(name=args.name, temp=True, clear=True) + help.ogler.level = logging.getLevelName(args.loglevel) + if(args.logfile != None): + help.ogler.headDirPath = args.logfile + help.ogler.reopen(name=args.name, temp=False, clear=True) logger = help.ogler.getLogger() diff --git a/src/keria/app/credentialing.py b/src/keria/app/credentialing.py index 70185c9a..eb816226 100644 --- a/src/keria/app/credentialing.py +++ b/src/keria/app/credentialing.py @@ -167,6 +167,9 @@ def on_post(self, req, rep, name): if hab is None: raise falcon.HTTPNotFound(description="alias is not a valid reference to an identifier") + if agent.rgy.registryByName(name=rname) is not None: + raise falcon.HTTPBadRequest(description=f"registry name {rname} already in use") + registry = agent.rgy.makeSignifyRegistry(name=rname, prefix=hab.pre, regser=vcp) if hab.kever.estOnly: @@ -283,6 +286,7 @@ def on_put(req, rep, name, registryName): regord = viring.RegistryRecord(registryKey=registry.regk, prefix=hab.pre) agent.rgy.reger.regs.pin(keys=(name,), val=regord) + agent.rgy.reger.regs.rem(keys=(registryName,)) registry.name = name rd = dict( diff --git a/src/keria/app/delegating.py b/src/keria/app/delegating.py index b08dcd7b..45f080ed 100644 --- a/src/keria/app/delegating.py +++ b/src/keria/app/delegating.py @@ -5,7 +5,7 @@ from keri.db import dbing -class Sealer(doing.DoDoer): +class Anchorer(doing.DoDoer): """ Sends messages to Delegator of an identifier and wait for the anchoring event to be processed to ensure the inception or rotation event has been approved by the delegator. @@ -32,7 +32,7 @@ def __init__(self, hby, proxy=None, **kwa): self.witDoer = agenting.Receiptor(hby=self.hby) self.proxy = proxy - super(Sealer, self).__init__(doers=[self.witq, self.witDoer, self.postman, doing.doify(self.escrowDo)], + super(Anchorer, self).__init__(doers=[self.witq, self.witDoer, self.postman, doing.doify(self.escrowDo)], **kwa) def delegation(self, pre, sn=None, proxy=None): @@ -41,7 +41,7 @@ def delegation(self, pre, sn=None, proxy=None): # load the hab of the delegated identifier to anchor hab = self.hby.habs[pre] - delpre = hab.kever.delegator # get the delegator identifier + delpre = hab.kever.delpre # get the delegator identifier if delpre not in hab.kevers: raise kering.ValidationError(f"delegator {delpre} not found, unable to process delegation") @@ -126,7 +126,7 @@ def processUnanchoredEscrow(self): """ for (pre, said), serder in self.hby.db.dune.getItemIter(): # group partial witness escrow kever = self.hby.kevers[pre] - dkever = self.hby.kevers[kever.delegator] + dkever = self.hby.kevers[kever.delpre] seal = dict(i=serder.pre, s=serder.snh, d=serder.said) if dserder := self.hby.db.findAnchoringSealEvent(dkever.prefixer.qb64, seal=seal): diff --git a/src/keria/app/grouping.py b/src/keria/app/grouping.py index 90ebd548..0d61df22 100644 --- a/src/keria/app/grouping.py +++ b/src/keria/app/grouping.py @@ -7,6 +7,7 @@ import json import falcon +from keri import core from keri.app import habbing from keri.core import coring, eventing, serdering from keri.kering import SerializeError @@ -56,7 +57,7 @@ def on_post(req, rep, name): atc = httping.getRequiredParam(body, "atc") # create sigers from the edge signatures so we can messagize the whole thing - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] # create seal for the proper location to find the signatures kever = hab.mhab.kever @@ -116,7 +117,7 @@ def on_post(req, rep, name): agent.hby.deleteHab(name=name) raise falcon.HTTPBadRequest(description=f"attempt to merge with unknown AID={recp}") - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] verfers = [coring.Verfer(qb64=k) for k in rot['k']] digers = [coring.Diger(qb64=n) for n in rot['n']] diff --git a/src/keria/app/indirecting.py b/src/keria/app/indirecting.py index e249f875..7ff53c13 100644 --- a/src/keria/app/indirecting.py +++ b/src/keria/app/indirecting.py @@ -9,7 +9,7 @@ from keri.app import httping from keri.core import eventing from keri.core.coring import Ilks, Sadder -from keri.kering import Protos +from keri.kering import Protocols CESR_DESTINATION_HEADER = "CESR-DESTINATION" @@ -84,7 +84,7 @@ def on_post(self, req, rep): agent.parser.ims.extend(msg) - if serder.proto == Protos.acdc: + if serder.proto == Protocols.acdc: rep.status = falcon.HTTP_204 else: diff --git a/src/keria/app/ipexing.py b/src/keria/app/ipexing.py index 39ec50bd..37289c2f 100644 --- a/src/keria/app/ipexing.py +++ b/src/keria/app/ipexing.py @@ -8,6 +8,7 @@ import json import falcon +from keri import core from keri.app import habbing from keri.core import coring, eventing, serdering from keri.peer import exchanging @@ -20,13 +21,19 @@ def loadEnds(app): app.add_route("/identifiers/{name}/ipex/admit", admitColEnd) grantColEnd = IpexGrantCollectionEnd() app.add_route("/identifiers/{name}/ipex/grant", grantColEnd) + applyColEnd = IpexApplyCollectionEnd() + app.add_route("/identifiers/{name}/ipex/apply", applyColEnd) + offerColEnd = IpexOfferCollectionEnd() + app.add_route("/identifiers/{name}/ipex/offer", offerColEnd) + agreeColEnd = IpexAgreeCollectionEnd() + app.add_route("/identifiers/{name}/ipex/agree", agreeColEnd) class IpexAdmitCollectionEnd: @staticmethod def on_post(req, rep, name): - """ Registries GET endpoint + """ IPEX Admit POST endpoint Parameters: req: falcon.Request HTTP request @@ -34,13 +41,13 @@ def on_post(req, rep, name): name (str): human readable name for AID --- - summary: List credential issuance and revocation registies - description: List credential issuance and revocation registies + summary: Accept a credential being issued or presented in response to an IPEX grant + description: Accept a credential being issued or presented in response to an IPEX grant tags: - Registries responses: 200: - description: array of current credential issuance and revocation registies + description: long running operation of IPEX admit """ agent = req.context.agent @@ -77,7 +84,7 @@ def sendAdmit(agent, hab, ked, sigs, rec): # use that data to create th Serder and Sigers for the exn serder = serdering.SerderKERI(sad=ked) - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] # Now create the stream to send, need the signer seal kever = hab.kever @@ -116,7 +123,7 @@ def sendMultisigExn(agent, hab, ked, sigs, atc, rec): # use that data to create th Serder and Sigers for the exn serder = serdering.SerderKERI(sad=ked) - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] # Now create the stream to send, need the signer seal kever = hab.mhab.kever @@ -154,7 +161,7 @@ class IpexGrantCollectionEnd: @staticmethod def on_post(req, rep, name): - """ Registries GET endpoint + """ IPEX Grant POST endpoint Parameters: req: falcon.Request HTTP request @@ -162,13 +169,13 @@ def on_post(req, rep, name): name (str): human readable name for AID --- - summary: List credential issuance and revocation registies - description: List credential issuance and revocation registies + summary: Reply to IPEX agree message or initiate an IPEX exchange with a credential issuance or presentation + description: Reply to IPEX agree message or initiate an IPEX exchange with a credential issuance or presentation tags: - - Registries + - Credentials responses: 200: - description: array of current credential issuance and revocation registies + description: long running operation of IPEX grant """ agent = req.context.agent @@ -204,7 +211,7 @@ def sendGrant(agent, hab, ked, sigs, atc, rec): # use that data to create th Serder and Sigers for the exn serder = serdering.SerderKERI(sad=ked) - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] # Now create the stream to send, need the signer seal kever = hab.kever @@ -240,7 +247,7 @@ def sendMultisigExn(agent, hab, ked, sigs, atc, rec): # use that data to create th Serder and Sigers for the exn serder = serdering.SerderKERI(sad=ked) - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] # Now create the stream to send, need the signer seal kever = hab.mhab.kever @@ -266,3 +273,203 @@ def sendMultisigExn(agent, hab, ked, sigs, atc, rec): agent.grants.append(dict(said=grant['d'], pre=hab.pre, rec=[holder])) return agent.monitor.submit(serder.pre, longrunning.OpTypes.exchange, metadata=dict(said=serder.said)) + + +class IpexApplyCollectionEnd: + + @staticmethod + def on_post(req, rep, name): + """ IPEX Apply POST endpoint + + Parameters: + req: falcon.Request HTTP request + rep: falcon.Response HTTP response + name (str): human readable name for AID + + --- + summary: Request a credential from another party by initiating an IPEX exchange + description: Request a credential from another party by initiating an IPEX exchange + tags: + - Credentials + responses: + 200: + description: long running operation of IPEX apply + + """ + agent = req.context.agent + # Get the hab + hab = agent.hby.habByName(name) + if hab is None: + raise falcon.HTTPNotFound(description=f"alias={name} is not a valid reference to an identifier") + + body = req.get_media() + + ked = httping.getRequiredParam(body, "exn") + sigs = httping.getRequiredParam(body, "sigs") + rec = httping.getRequiredParam(body, "rec") + + route = ked['r'] + + match route: + case "/ipex/apply": + op = IpexApplyCollectionEnd.sendApply(agent, hab, ked, sigs, rec) + case _: + raise falcon.HTTPBadRequest(description=f"invalid message route {route}") + + rep.status = falcon.HTTP_200 + rep.data = op.to_json().encode("utf-8") + + @staticmethod + def sendApply(agent, hab, ked, sigs, rec): + for recp in rec: # Have to verify we already know all the recipients. + if recp not in agent.hby.kevers: + raise falcon.HTTPBadRequest(description=f"attempt to send to unknown AID={recp}") + + # use that data to create th Serder and Sigers for the exn + serder = serdering.SerderKERI(sad=ked) + sigers = [core.Siger(qb64=sig) for sig in sigs] + + # Now create the stream to send, need the signer seal + kever = hab.kever + seal = eventing.SealEvent(i=hab.pre, s="{:x}".format(kever.lastEst.s), d=kever.lastEst.d) + + # in this case, ims is a message is a sealed and signed message - signed by Signify (KERIA can't sign anything here...) + ims = eventing.messagize(serder=serder, sigers=sigers, seal=seal) + + # make a copy and parse + agent.hby.psr.parseOne(ims=bytearray(ims)) + + agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=rec, topic='credential')) + return agent.monitor.submit(serder.pre, longrunning.OpTypes.exchange, metadata=dict(said=serder.said)) + +class IpexOfferCollectionEnd: + + @staticmethod + def on_post(req, rep, name): + """ IPEX Offer POST endpoint + + Parameters: + req: falcon.Request HTTP request + rep: falcon.Response HTTP response + name (str): human readable name for AID + + --- + summary: Reply to IPEX apply message or initiate an IPEX exchange with an offer for a credential with certain characteristics + description: Reply to IPEX apply message or initiate an IPEX exchange with an offer for a credential with certain characteristics + tags: + - Credentials + responses: + 200: + description: long running operation of IPEX offer + + """ + agent = req.context.agent + hab = agent.hby.habByName(name) + if hab is None: + raise falcon.HTTPNotFound(description=f"alias={name} is not a valid reference to an identifier") + + body = req.get_media() + + ked = httping.getRequiredParam(body, "exn") + sigs = httping.getRequiredParam(body, "sigs") + atc = httping.getRequiredParam(body, "atc") + rec = httping.getRequiredParam(body, "rec") + + route = ked['r'] + + match route: + case "/ipex/offer": + op = IpexOfferCollectionEnd.sendOffer(agent, hab, ked, sigs, atc, rec) + case _: + raise falcon.HTTPBadRequest(description=f"invalid route {route}") + + rep.status = falcon.HTTP_200 + rep.data = op.to_json().encode("utf-8") + + @staticmethod + def sendOffer(agent, hab, ked, sigs, atc, rec): + for recp in rec: # Have to verify we already know all the recipients. + if recp not in agent.hby.kevers: + raise falcon.HTTPBadRequest(description=f"attempt to send to unknown AID={recp}") + + # use that data to create th Serder and Sigers for the exn + serder = serdering.SerderKERI(sad=ked) + sigers = [core.Siger(qb64=sig) for sig in sigs] + + # Now create the stream to send, need the signer seal + kever = hab.kever + seal = eventing.SealEvent(i=hab.pre, s="{:x}".format(kever.lastEst.s), d=kever.lastEst.d) + + ims = eventing.messagize(serder=serder, sigers=sigers, seal=seal) + ims = ims + atc.encode("utf-8") + + # make a copy and parse + agent.hby.psr.parseOne(ims=bytearray(ims)) + + agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=rec, topic='credential')) + return agent.monitor.submit(serder.pre, longrunning.OpTypes.exchange, metadata=dict(said=serder.said)) + +class IpexAgreeCollectionEnd: + + @staticmethod + def on_post(req, rep, name): + """ IPEX Agree POST endpoint + + Parameters: + req: falcon.Request HTTP request + rep: falcon.Response HTTP response + name (str): human readable name for AID + + --- + summary: Reply to IPEX offer message acknowledged willingness to accept offered credential + description: Reply to IPEX offer message acknowledged willingness to accept offered credential + tags: + - Credentials + responses: + 200: + description: long running operation of IPEX agree + + """ + agent = req.context.agent + hab = agent.hby.habByName(name) + if hab is None: + raise falcon.HTTPNotFound(description=f"alias={name} is not a valid reference to an identifier") + + body = req.get_media() + + ked = httping.getRequiredParam(body, "exn") + sigs = httping.getRequiredParam(body, "sigs") + rec = httping.getRequiredParam(body, "rec") + + route = ked['r'] + + match route: + case "/ipex/agree": + op = IpexAgreeCollectionEnd.sendAgree(agent, hab, ked, sigs, rec) + case _: + raise falcon.HTTPBadRequest(description=f"invalid route {route}") + + rep.status = falcon.HTTP_200 + rep.data = op.to_json().encode("utf-8") + + @staticmethod + def sendAgree(agent, hab, ked, sigs, rec): + for recp in rec: # Have to verify we already know all the recipients. + if recp not in agent.hby.kevers: + raise falcon.HTTPBadRequest(description=f"attempt to send to unknown AID={recp}") + + # use that data to create th Serder and Sigers for the exn + serder = serdering.SerderKERI(sad=ked) + sigers = [core.Siger(qb64=sig) for sig in sigs] + + # Now create the stream to send, need the signer seal + kever = hab.kever + seal = eventing.SealEvent(i=hab.pre, s="{:x}".format(kever.lastEst.s), d=kever.lastEst.d) + + ims = eventing.messagize(serder=serder, sigers=sigers, seal=seal) + + # make a copy and parse + agent.hby.psr.parseOne(ims=bytearray(ims)) + + agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=rec, topic='credential')) + return agent.monitor.submit(serder.pre, longrunning.OpTypes.exchange, metadata=dict(said=serder.said)) diff --git a/src/keria/core/longrunning.py b/src/keria/core/longrunning.py index c0d41122..06016184 100644 --- a/src/keria/core/longrunning.py +++ b/src/keria/core/longrunning.py @@ -88,7 +88,7 @@ class Monitor: Attributes: hby (Habery): identifier database environment opr(Operator): long running operations database - swain(Sealer): Delegation processes tracker + swain(Anchorer): Delegation processes tracker """ @@ -98,7 +98,7 @@ def __init__(self, hby, swain, counselor=None, registrar=None, exchanger=None, c Parameters: hby (Habery): identifier database environment - swain(Sealer): Delegation processes tracker + swain(Anchorer): Delegation processes tracker opr (Operator): long running operations database """ @@ -410,8 +410,8 @@ def status(self, op): class OperationCollectionEnd: @staticmethod def on_get(req, rep): - """ Get list of long running operations - + """ Get list of long running operations + Parameters: req (Request): Falcon HTTP Request object rep (Response): Falcon HTTP Response object diff --git a/src/keria/peer/exchanging.py b/src/keria/peer/exchanging.py index fb27ee3c..3a98cf7e 100644 --- a/src/keria/peer/exchanging.py +++ b/src/keria/peer/exchanging.py @@ -7,6 +7,7 @@ import json import falcon +from keri import core from keri.core import coring, eventing, serdering from keri.peer import exchanging @@ -58,7 +59,7 @@ def on_post(req, rep, name): # use that data to create th Serder and Sigers for the exn serder = serdering.SerderKERI(sad=ked) - sigers = [coring.Siger(qb64=sig) for sig in sigs] + sigers = [core.Siger(qb64=sig) for sig in sigs] # Now create the stream to send, need the signer seal kever = hab.kever diff --git a/tests/app/test_agenting.py b/tests/app/test_agenting.py index d0d6d8df..42a05a56 100644 --- a/tests/app/test_agenting.py +++ b/tests/app/test_agenting.py @@ -79,7 +79,7 @@ def test_agency(): doist.enter(doers=[agency]) caid = "ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose" - agent = agency.create(caid) + agent = agency.create(caid, salt=salter.qb64) assert agent.pre == "EIAEKYpTygdBtFHBrHKWeh0aYCdx0ZJqZtzQLFnaDB2b" badcaid = "E987eerAdhmvrjDeam2eAO2SR5niCgnjAJXJHtJoe" @@ -121,7 +121,7 @@ def test_agency(): doist.enter(doers=[agency]) caid = "ELI7pg979AdhmvrjDeam2eAO2SR5niCgnjAJXJHtJose" - agent = agency.create(caid) + agent = agency.create(caid, salt=salter.qb64) assert agent.pre == "EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei" # Rcreate the agency to see if agent is reloaded from disk diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index 7b6efe29..0c5bde34 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -242,13 +242,17 @@ def test_agent_resource(helpers, mockHelpingNowUTC): def test_identifier_collection_end(helpers): + salt = b'0123456789abcdef' + salter = coring.Salter(raw=salt) + with helpers.openKeria() as (agency, agent, app, client), \ - habbing.openHby(name="p1", temp=True) as p1hby, \ - habbing.openHby(name="p2", temp=True) as p2hby: + 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) groupEnd = aiding.GroupMemberCollectionEnd() app.add_route("/identifiers/{name}/members", groupEnd) @@ -362,7 +366,7 @@ def test_identifier_collection_end(helpers): 'salty': {'stem': 'signify:aid', 'pidx': 0, 'tier': 'low', 'sxlt': sxlt, 'transferable': True, 'kidx': 1, 'icodes': [MtrDex.Ed25519_Seed], 'ncodes': [MtrDex.Ed25519_Seed]} } - res = client.simulate_put(path="/identifiers/aid1", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/aid1/events", body=json.dumps(body)) assert res.status_code == 200 # Try with missing arguments @@ -372,7 +376,7 @@ def test_identifier_collection_end(helpers): 'salty': {'stem': 'signify:aid', 'pidx': 0, 'tier': 'low', 'sxlt': sxlt, 'icodes': [MtrDex.Ed25519_Seed], 'ncodes': [MtrDex.Ed25519_Seed]} } - res = client.simulate_put(path="/identifiers/aid1", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/aid1/events", body=json.dumps(body)) assert res.status_code == 500 # Test with witnesses @@ -415,7 +419,6 @@ def test_identifier_collection_end(helpers): 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 @@ -467,8 +470,21 @@ def test_identifier_collection_end(helpers): 'salty': {'stem': 'signify:aid', 'pidx': 0, 'tier': 'low', 'sxlt': sxlt, 'transferable': True, 'kidx': 3, 'icodes': [MtrDex.Ed25519_Seed], 'ncodes': [MtrDex.Ed25519_Seed]} } - res = client.simulate_put(path="/identifiers/aid3", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/aid3/events", body=json.dumps(body)) + assert res.status_code == 200 + + # rename aid3 + res = client.simulate_put(path="/identifiers/aid3", body=json.dumps({"name": "aid3Renamed"})) assert res.status_code == 200 + aid = res.json + assert aid["name"] == "aid3Renamed" + + # delete aid3renamed + res = client.simulate_delete(path="/identifiers/aid3Renamed") + assert res.status_code == 200 + res = client.simulate_get(path="/identifiers") + assert res.status_code == 200 + assert len(res.json) == 2 # create member habs for group AID p1 = p1hby.makeHab(name="p1") @@ -596,8 +612,8 @@ def test_identifier_collection_end(helpers): res = client.simulate_get(path="/identifiers") assert res.status_code == 200 - assert len(res.json) == 4 - aid = res.json[3] + assert len(res.json) == 3 + aid = res.json[2] assert aid["name"] == "multisig" assert aid["prefix"] == serder.pre group = aid["group"] @@ -623,10 +639,10 @@ def test_identifier_collection_end(helpers): assert res.status_code == 200 assert "signing" in res.json signing = res.json["signing"] - assert len(signing) == 5 # this number is a little janky because we reuse public keys above, leaving for now + assert len(signing) == 3 assert "rotation" in res.json rotation = res.json["rotation"] - assert len(rotation) == 5 # this number is a little janky because we reuse rotation keys above, leaving for now + assert len(rotation) == 3 # Try unknown witness serder, signers = helpers.incept(salt, "signify:aid", pidx=3, @@ -651,6 +667,7 @@ def test_identifier_collection_end(helpers): resend = aiding.IdentifierResourceEnd() app.add_route("/identifiers", end) app.add_route("/identifiers/{name}", resend) + app.add_route("/identifiers/{name}/events", resend) eventsEnd = agenting.KeyEventCollectionEnd() app.add_route("/events", eventsEnd) @@ -719,7 +736,7 @@ def test_identifier_collection_end(helpers): "transferable": True, } } - res = client.simulate_put(path="/identifiers/randy1", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/randy1/events", body=json.dumps(body)) assert res.status_code == 200 assert res.json["response"] == serder.ked res = client.simulate_get(path="/identifiers") @@ -730,14 +747,14 @@ def test_identifier_collection_end(helpers): assert res.status_code == 200 events = res.json assert len(events) == 2 - assert events[1] == serder.ked + assert events[1]['ked'] == serder.ked serder = eventing.interact(pre=pre, dig=serder.said, sn=len(events), data=[pre]) sigers = [signer.sign(ser=serder.raw, index=0).qb64 for signer in signers] body = {'ixn': serder.ked, 'sigs': sigers } - res = client.simulate_put(path="/identifiers/randy1?type=ixn", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/randy1/events", body=json.dumps(body)) assert res.status_code == 200 assert res.json["response"] == serder.ked @@ -745,20 +762,20 @@ def test_identifier_collection_end(helpers): assert res.status_code == 200 events = res.json assert len(events) == 3 - assert events[2] == serder.ked + assert events[2]['ked'] == serder.ked # Bad interactions - res = client.simulate_put(path="/identifiers/badrandy?type=ixn", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/badrandy/events", body=json.dumps(body)) assert res.status_code == 404 assert res.json == {'title': 'No AID badrandy found'} body = {'sigs': sigers} - res = client.simulate_put(path="/identifiers/randy1?type=ixn", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/randy1/events", body=json.dumps(body)) assert res.status_code == 400 - assert res.json == {'description': "required field 'ixn' missing from request", 'title': 'invalid interaction'} + assert res.json == {'description': "required field 'rot' or 'ixn' missing from request", 'title': 'invalid request'} body = {'ixn': serder.ked} - res = client.simulate_put(path="/identifiers/randy1?type=ixn", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/randy1/events", body=json.dumps(body)) assert res.status_code == 400 assert res.json == {'description': "required field 'sigs' missing from request", 'title': 'invalid interaction'} @@ -771,7 +788,7 @@ def test_identifier_collection_end(helpers): "transferable": True, } } - res = client.simulate_put(path="/identifiers/randybad?type=rot", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/randybad/events", body=json.dumps(body)) assert res.status_code == 404 assert res.json == {'title': 'No AID with name randybad found'} @@ -783,9 +800,9 @@ def test_identifier_collection_end(helpers): "transferable": True, } } - res = client.simulate_put(path="/identifiers/randy1", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/randy1/events", body=json.dumps(body)) assert res.status_code == 400 - assert res.json == {'description': "required field 'rot' missing from request", 'title': 'invalid rotation'} + assert res.json == {'description': "required field 'rot' or 'ixn' missing from request", 'title': 'invalid request'} # rotate to unknown witness serder = eventing.rotate(keys=keys, @@ -809,7 +826,7 @@ def test_identifier_collection_end(helpers): "transferable": True, } } - res = client.simulate_put(path="/identifiers/randy1", body=json.dumps(body)) + res = client.simulate_post(path="/identifiers/randy1/events", body=json.dumps(body)) assert res.status_code == 400 assert res.json == {'description': "unknown witness EJJR2nmwyYAZAoTNZH3ULvaU6Z-i0d8fSVPzhzS6b5CM", 'title': '400 Bad Request'} @@ -820,6 +837,7 @@ def test_identifier_collection_end(helpers): resend = aiding.IdentifierResourceEnd() app.add_route("/identifiers", end) app.add_route("/identifiers/{name}", resend) + app.add_route("/identifiers/{name}/events", resend) eventsEnd = agenting.KeyEventCollectionEnd() app.add_route("/events", eventsEnd) @@ -1132,6 +1150,7 @@ def test_contact_ends(helpers): assert response.status == falcon.HTTP_200 assert response.json == [{'challenges': [], 'company': 'GLEIF', + 'ends': {}, 'first': 'Ken3', 'id': 'EAjKmvW6flpWJfdYYZ2Lu4pllPWKFjCBz0dcX-S86Nvg', 'last': 'Burns3', @@ -1143,30 +1162,35 @@ def test_contact_ends(helpers): assert response.status == falcon.HTTP_200 assert response.json == [{'challenges': [], 'company': 'GLEIF', + 'ends': {}, 'first': 'Ken3', 'id': 'EAjKmvW6flpWJfdYYZ2Lu4pllPWKFjCBz0dcX-S86Nvg', 'last': 'Burns3', 'wellKnowns': []}, {'challenges': [], 'company': 'GLEIF', + 'ends': {}, 'first': 'Ken1', 'id': 'EER-n23rDM2RQB8Kw4KRrm8SFpoid4Jnelhauo6KxQpz', 'last': 'Burns1', 'wellKnowns': []}, {'challenges': [], 'company': 'ProSapien', + 'ends': {}, 'first': 'Ken4', 'id': 'EGwcSt3uvK5-oHI7hVU7dKMvWt0vRfMW2demzBBMDnBG', 'last': 'Burns4', 'wellKnowns': []}, {'challenges': [], 'company': 'ProSapien', + 'ends': {}, 'first': 'Ken2', 'id': 'ELTQ3tF3n7QS8LDpKMdJyCMhVyMdvNPTiisnqW5ZQP3C', 'last': 'Burns2', 'wellKnowns': []}, {'challenges': [], 'company': 'GLEIF', + 'ends': {}, 'first': 'Ken0', 'id': 'EPo8Wy1xpTa6ri25M4IlmWBBzs5y8v4Qn3Z8xP4kEjcK', 'last': 'Burns0', @@ -1449,7 +1473,6 @@ def test_approve_delegation(helpers): assert res.status_code == 200 agt = res.json["agent"] ctrl = res.json["controller"] - assert agt["i"] == "EHyaw-1bCenigGQCZRs_hXNdndHw0fSf-Q5-LpUwOR8r" assert ctrl["state"]["i"] == controllerAID anchor = dict(i=agt["i"], s="0", d=agt["d"]) @@ -1533,9 +1556,7 @@ def test_rotation(helpers): res = client.simulate_get(path=f"/agent/{serder.pre}") assert res.status_code == 200 - agt = res.json["agent"] ctrl = res.json["controller"] - assert agt["i"] == "EHyaw-1bCenigGQCZRs_hXNdndHw0fSf-Q5-LpUwOR8r" assert ctrl["state"]["i"] == controllerAID op = helpers.createAid(client, name="salty_aid", salt=bran) diff --git a/tests/app/test_credentialing.py b/tests/app/test_credentialing.py index 69c91ee1..e40ee7e7 100644 --- a/tests/app/test_credentialing.py +++ b/tests/app/test_credentialing.py @@ -94,6 +94,8 @@ def test_registry_end(helpers, seeder): idResEnd = aiding.IdentifierResourceEnd() registryEnd = credentialing.RegistryCollectionEnd(idResEnd) app.add_route("/identifiers/{name}/registries", registryEnd) + registryResEnd = credentialing.RegistryResourceEnd() + app.add_route("/identifiers/{name}/registries/{registryName}", registryResEnd) opEnd = longrunning.OperationResourceEnd() app.add_route("/operations/{name}", opEnd) @@ -150,13 +152,48 @@ def test_registry_end(helpers, seeder): result = client.simulate_get(path="/identifiers/test/registries") assert result.status == falcon.HTTP_200 assert len(result.json) == 1 + result = client.simulate_post(path="/identifiers/test/registries", body=json.dumps(body).encode("utf-8")) + assert result.status == falcon.HTTP_400 + assert result.json == {'description': 'registry name test already in use', 'title': '400 Bad Request'} body = dict(name="test", alias="test", vcp=regser.ked, ixn=serder.ked, sigs=sigers) result = client.simulate_post(path="/identifiers/bad_test/registries", body=json.dumps(body).encode("utf-8")) assert result.status == falcon.HTTP_404 assert result.json == {'description': 'alias is not a valid reference to an identifier', + 'title': '404 Not Found'} + + # Try with bad identifier name + body = b'{"name": "new-name"}' + result = client.simulate_put(path="/identifiers/test-bad/registries/test", body=body) + assert result.status == falcon.HTTP_404 + assert result.json == {'description': 'test-bad is not a valid reference to an identifier', 'title': '404 Not Found'} + result = client.simulate_put(path="/identifiers/test/registries/test", body=body) + assert result.status == falcon.HTTP_200 + regk = result.json['regk'] + + # Try to rename a the now used name + result = client.simulate_put(path="/identifiers/test/registries/new-name", body=b'{}') + assert result.status == falcon.HTTP_400 + assert result.json == {'description': "'name' is required in body", 'title': '400 Bad Request'} + + # Try to rename a the now used name + result = client.simulate_put(path="/identifiers/test/registries/test", body=body) + assert result.status == falcon.HTTP_400 + assert result.json == {'description': 'new-name is already in use for a registry', + 'title': '400 Bad Request'} + + # Try to rename a now non-existant registry + body = b'{"name": "newnew-name"}' + result = client.simulate_put(path="/identifiers/test/registries/test", body=body) + assert result.status == falcon.HTTP_404 + assert result.json == {'description': 'test is not a valid reference to a credential registry', + 'title': '404 Not Found'} + # Rename registry by SAID + body = b'{"name": "newnew-name"}' + result = client.simulate_put(path=f"/identifiers/test/registries/{regk}", body=body) + assert result.status == falcon.HTTP_200 result = client.simulate_get(path="/identifiers/not_test/registries") assert result.status == falcon.HTTP_404 diff --git a/tests/app/test_delegating.py b/tests/app/test_delegating.py index 2c569357..01d4fb54 100644 --- a/tests/app/test_delegating.py +++ b/tests/app/test_delegating.py @@ -3,7 +3,7 @@ KERIA keria.app.delegating module -Testing the Mark II Agent Sealer +Testing the Mark II Agent Anchorer """ import pytest from hio.base import doing @@ -16,12 +16,12 @@ def test_sealer(): with habbing.openHby(name="p1", temp=True) as hby: - # Create Sealer to test - sealer = delegating.Sealer(hby=hby) + # Create Anchorer to test + anchorer = delegating.Anchorer(hby=hby) # Doer hierarchy doist = doing.Doist(tock=0.03125, real=True) - deeds = doist.enter(doers=[sealer]) + deeds = doist.enter(doers=[anchorer]) # Create delegator and delegate Habs delegator = hby.makeHab("delegator") @@ -30,34 +30,34 @@ def test_sealer(): # Try with a bad AID with pytest.raises(kering.ValidationError): - sealer.delegation(pre="EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY") + anchorer.delegation(pre="EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY") # Needs a proxy with pytest.raises(kering.ValidationError): - sealer.delegation(pre=delegate.pre) + anchorer.delegation(pre=delegate.pre) # Run delegation to escrow inception event - sealer.delegation(pre=delegate.pre, proxy=proxy) + anchorer.delegation(pre=delegate.pre, proxy=proxy) doist.recur(deeds=deeds) prefixer = coring.Prefixer(qb64=delegate.pre) seqner = coring.Seqner(sn=0) - assert sealer.complete(prefixer=prefixer, seqner=seqner) is False + assert anchorer.complete(prefixer=prefixer, seqner=seqner) is False # Anchor the seal in delegator's KEL, approving the delegation seal = eventing.SealEvent(prefixer.qb64, "0", prefixer.qb64) delegator.interact(data=[seal._asdict()]) - while sealer.complete(prefixer=prefixer, seqner=seqner) is False: + while anchorer.complete(prefixer=prefixer, seqner=seqner) is False: doist.recur(deeds=deeds) # Will raise with a bad digest with pytest.raises(kering.ValidationError): # Create saider for the wrong event saider = coring.Saider(qb64=delegator.kever.serder.said) - sealer.complete(prefixer=prefixer, seqner=seqner, saider=saider) + anchorer.complete(prefixer=prefixer, seqner=seqner, saider=saider) - assert sealer.complete(prefixer=prefixer, seqner=seqner) is True + assert anchorer.complete(prefixer=prefixer, seqner=seqner) is True diff --git a/tests/app/test_ipexing.py b/tests/app/test_ipexing.py index 2c363248..4182b491 100644 --- a/tests/app/test_ipexing.py +++ b/tests/app/test_ipexing.py @@ -11,8 +11,9 @@ from falcon import testing from hio.base import doing from hio.help import decking +from keri import core from keri.app import habbing, signing -from keri.core import eventing, coring, parsing, serdering +from keri.core import eventing, coring, serdering from keri.help import helping from keri.kering import Roles from keri.peer import exchanging @@ -36,6 +37,12 @@ def test_load_ends(helpers): assert isinstance(end, ipexing.IpexAdmitCollectionEnd) (end, *_) = app._router.find("/identifiers/NAME/ipex/grant") assert isinstance(end, ipexing.IpexGrantCollectionEnd) + (end, *_) = app._router.find("/identifiers/NAME/ipex/apply") + assert isinstance(end, ipexing.IpexApplyCollectionEnd) + (end, *_) = app._router.find("/identifiers/NAME/ipex/offer") + assert isinstance(end, ipexing.IpexOfferCollectionEnd) + (end, *_) = app._router.find("/identifiers/NAME/ipex/agree") + assert isinstance(end, ipexing.IpexAgreeCollectionEnd) def test_ipex_admit(helpers, mockHelpingNowIso8601): @@ -306,7 +313,7 @@ def test_ipex_grant(helpers, mockHelpingNowIso8601, seeder): assert len(agent.exchanges) == 1 assert len(agent.grants) == 1 - ims = eventing.messagize(serder=exn, sigers=[coring.Siger(qb64=sigs[0])]) + ims = eventing.messagize(serder=exn, sigers=[core.Siger(qb64=sigs[0])]) # Test sending embedded admit in multisig/exn message exn, end = exchanging.exchange(route="/multisig/exn", payload=dict(), @@ -919,3 +926,314 @@ def test_granter(helpers): doist.recur(deeds=deeds) assert len(grants) == 1 + + +def test_ipex_apply(helpers, mockHelpingNowIso8601): + with helpers.openKeria() as (_, agent, app, client): + applyEnd = ipexing.IpexApplyCollectionEnd() + app.add_route("/identifiers/{name}/ipex/apply", applyEnd) + + end = aiding.IdentifierCollectionEnd() + app.add_route("/identifiers", end) + aidEnd = aiding.IdentifierResourceEnd() + app.add_route("/identifiers/{name}", aidEnd) + + salt = b'0123456789abcdef' + op = helpers.createAid(client, "test", salt) + aid = op["response"] + pre = aid['i'] + assert pre == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" + + salt2 = b'0123456789abcdeg' + op = helpers.createAid(client, "recp", salt2) + aid1 = op["response"] + pre1 = aid1['i'] + assert pre1 == "EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm" + + applySerder, end = exchanging.exchange(route="/ipex/apply", + payload={'m': 'Applying for a credential', 's': 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao', 'a': {'LEI': '78I9GKEFM361IFY3PIN0'}}, + sender=pre, + embeds=dict(), + recipient=pre1, + date=helping.nowIso8601()) + assert applySerder.ked == {'a': {'i': 'EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm', 'm': 'Applying for a credential', 's': 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao', 'a': {'LEI': '78I9GKEFM361IFY3PIN0'}}, + 'd': 'EJq6zSDUWw6iaBz8n1cY5cAW3Rrgp4E3sUsoz5JkoMZc', + 'dt': '2021-06-27T21:26:21.233257+00:00', + 'e': {}, + 'i': 'EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY', + 'p': '', + 'q': {}, + 'r': '/ipex/apply', + 't': 'exn', + 'v': 'KERI10JSON000187_'} + assert end == b'' + sigs = ["AAAa70b4QnTOtGOsMqcezMtVzCFuRJHGeIMkWYHZ5ZxGIXM0XDVAzkYdCeadfPfzlKC6dkfiwuJ0IzLOElaanUgH"] + + body = dict( + exn=applySerder.ked, + sigs=sigs, + atc="", + rec=["EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM"] + ) + + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/test/ipex/apply", body=data) + + assert res.status_code == 400 + assert res.json == {'description': 'attempt to send to unknown ' + 'AID=EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM', + 'title': '400 Bad Request'} + + body = dict( + exn=applySerder.ked, + sigs=sigs, + atc="", + rec=[pre1] + ) + + # Bad Sender + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/BAD/ipex/apply", body=data) + assert res.status_code == 404 + assert res.json == {'description': 'alias=BAD is not a valid reference to an identifier', + 'title': '404 Not Found'} + + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/test/ipex/apply", body=data) + assert res.json == {'done': False, + 'error': None, + 'metadata': {'said': 'EJq6zSDUWw6iaBz8n1cY5cAW3Rrgp4E3sUsoz5JkoMZc'}, + 'name': 'exchange.EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY', + 'response': None} + + assert res.status_code == 200 + assert len(agent.exchanges) == 1 + + +def test_ipex_offer(helpers, mockHelpingNowIso8601): + with helpers.openKeria() as (_, agent, app, client): + offerEnd = ipexing.IpexOfferCollectionEnd() + app.add_route("/identifiers/{name}/ipex/offer", offerEnd) + + end0 = aiding.IdentifierCollectionEnd() + app.add_route("/identifiers", end0) + aidEnd = aiding.IdentifierResourceEnd() + app.add_route("/identifiers/{name}", aidEnd) + + salt = b'0123456789abcdef' + op = helpers.createAid(client, "test", salt) + aid = op["response"] + pre = aid['i'] + assert pre == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" + + salt2 = b'0123456789abcdeg' + op = helpers.createAid(client, "recp", salt2) + aid1 = op["response"] + pre1 = aid1['i'] + assert pre1 == "EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm" + + # This should be a metadata ACDC in reality + acdc = b'{"v":"ACDC10JSON000197_","d":"EBg1YzKmwZIDzZsMslTFwQARB6nUN85sRJF5oywlJr3N","i":"EIqTaQiZw73plMOq8pqHTi9BDgDrrE7iE9v2XfN2Izze","ri":"EACehJRd0wfteUAJgaTTJjMSaQqWvzeeHqAMMqxuqxU4","s":"EFgnk_c08WmZGgv9_mpldibRuqFMTQN-rAgtD-TCOwbs","a":{"d":"ELJ7Emhi0Bhxz3s7HyhZ45qcsgpvsT8p8pxwWkG362n3","dt":"2021-06-27T21:26:21.233257+00:00","i":"EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm","LEI":"78I9GKEFM361IFY3PIN0"}}' + embeds = dict(acdc = acdc) + + # First an offer initiated by discloser (no apply) + offer0Serder, end0 = exchanging.exchange(route="/ipex/offer", + payload={'m': 'Offering this'}, + sender=pre, + embeds=embeds, + recipient=pre1, + date=helping.nowIso8601()) + assert offer0Serder.ked == {'a': {'i': 'EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm', 'm': 'Offering this'}, + 'd': 'EDY-IFIMBR4umlYATxAqEAcT5jiHEMn5EyL6i1sUwxDO', + 'dt': '2021-06-27T21:26:21.233257+00:00', + 'e': {'acdc': {'a': {'d': 'ELJ7Emhi0Bhxz3s7HyhZ45qcsgpvsT8p8pxwWkG362n3', + 'dt': '2021-06-27T21:26:21.233257+00:00', + 'i': 'EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm', + 'LEI': '78I9GKEFM361IFY3PIN0'}, + 'd': 'EBg1YzKmwZIDzZsMslTFwQARB6nUN85sRJF5oywlJr3N', + 'i': 'EIqTaQiZw73plMOq8pqHTi9BDgDrrE7iE9v2XfN2Izze', + 'ri': 'EACehJRd0wfteUAJgaTTJjMSaQqWvzeeHqAMMqxuqxU4', + 's': 'EFgnk_c08WmZGgv9_mpldibRuqFMTQN-rAgtD-TCOwbs', + 'v': 'ACDC10JSON000197_'}, + 'd': 'EEcYZMP-zilz2w1w2hEFm6tF0eaX_1KaPEWhNfY3kf8i'}, + 'i': 'EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY', + 'p': '', + 'q': {}, + 'r': '/ipex/offer', + 't': 'exn', + 'v': 'KERI10JSON0002f6_'} + assert end0 == b'' + sigs = ["AAAa70b4QnTOtGOsMqcezMtVzCFuRJHGeIMkWYHZ5ZxGIXM0XDVAzkYdCeadfPfzlKC6dkfiwuJ0IzLOElaanUgH"] + + body = dict( + exn=offer0Serder.ked, + sigs=sigs, + atc="", + rec=["EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM"] + ) + + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/test/ipex/offer", body=data) + + assert res.status_code == 400 + assert res.json == {'description': 'attempt to send to unknown ' + 'AID=EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM', + 'title': '400 Bad Request'} + + body = dict( + exn=offer0Serder.ked, + sigs=sigs, + atc="", + rec=[pre1] + ) + + # Bad Sender + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/BAD/ipex/offer", body=data) + assert res.status_code == 404 + assert res.json == {'description': 'alias=BAD is not a valid reference to an identifier', + 'title': '404 Not Found'} + + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/test/ipex/offer", body=data) + assert res.json == {'done': False, + 'error': None, + 'metadata': {'said': 'EDY-IFIMBR4umlYATxAqEAcT5jiHEMn5EyL6i1sUwxDO'}, + 'name': 'exchange.EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY', + 'response': None} + + assert res.status_code == 200 + assert len(agent.exchanges) == 1 + + # Now an offer in response to an agree + dig = "EB_Lr3fHezn1ygn-wbBT5JjzaCMxTmhUoegXeZzWC2eT" + offer1Serder, end1 = exchanging.exchange(route="/ipex/offer", + payload={'m': 'How about this'}, + sender=pre, + embeds=embeds, + dig=dig, + recipient=pre1, + date=helping.nowIso8601()) + assert offer1Serder.ked == {'a': {'i': 'EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm', 'm': 'How about this'}, + 'd': 'EDT7go7TfCTzeFnhNBl19JJqdabBfBx8tjBvi_asFCwT', + 'dt': '2021-06-27T21:26:21.233257+00:00', + 'e': {'acdc': {'a': {'d': 'ELJ7Emhi0Bhxz3s7HyhZ45qcsgpvsT8p8pxwWkG362n3', + 'dt': '2021-06-27T21:26:21.233257+00:00', + 'i': 'EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm', + 'LEI': '78I9GKEFM361IFY3PIN0'}, + 'd': 'EBg1YzKmwZIDzZsMslTFwQARB6nUN85sRJF5oywlJr3N', + 'i': 'EIqTaQiZw73plMOq8pqHTi9BDgDrrE7iE9v2XfN2Izze', + 'ri': 'EACehJRd0wfteUAJgaTTJjMSaQqWvzeeHqAMMqxuqxU4', + 's': 'EFgnk_c08WmZGgv9_mpldibRuqFMTQN-rAgtD-TCOwbs', + 'v': 'ACDC10JSON000197_'}, + 'd': 'EEcYZMP-zilz2w1w2hEFm6tF0eaX_1KaPEWhNfY3kf8i'}, + 'i': 'EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY', + 'p': 'EB_Lr3fHezn1ygn-wbBT5JjzaCMxTmhUoegXeZzWC2eT', + 'q': {}, + 'r': '/ipex/offer', + 't': 'exn', + 'v': 'KERI10JSON000323_'} + assert end1 == b'' + sigs = ["AAAa70b4QnTOtGOsMqcezMtVzCFuRJHGeIMkWYHZ5ZxGIXM0XDVAzkYdCeadfPfzlKC6dkfiwuJ0IzLOElaanUgH"] + + body = dict( + exn=offer1Serder.ked, + sigs=sigs, + atc="", + rec=[pre1] + ) + + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/test/ipex/offer", body=data) + assert res.json == {'done': False, + 'error': None, + 'metadata': {'said': 'EDT7go7TfCTzeFnhNBl19JJqdabBfBx8tjBvi_asFCwT'}, + 'name': 'exchange.EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY', + 'response': None} + + assert res.status_code == 200 + assert len(agent.exchanges) == 2 + + +def test_ipex_agree(helpers, mockHelpingNowIso8601): + with helpers.openKeria() as (_, agent, app, client): + agreeEnd = ipexing.IpexAgreeCollectionEnd() + app.add_route("/identifiers/{name}/ipex/agree", agreeEnd) + + end = aiding.IdentifierCollectionEnd() + app.add_route("/identifiers", end) + aidEnd = aiding.IdentifierResourceEnd() + app.add_route("/identifiers/{name}", aidEnd) + + salt = b'0123456789abcdef' + op = helpers.createAid(client, "test", salt) + aid = op["response"] + pre = aid['i'] + assert pre == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" + + salt2 = b'0123456789abcdeg' + op = helpers.createAid(client, "recp", salt2) + aid1 = op["response"] + pre1 = aid1['i'] + assert pre1 == "EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm" + dig = "EB_Lr3fHezn1ygn-wbBT5JjzaCMxTmhUoegXeZzWC2eT" + + offerSerder, end = exchanging.exchange(route="/ipex/agree", + payload={'m': 'Agreed'}, + sender=pre, + embeds=dict(), + dig=dig, + recipient=pre1, + date=helping.nowIso8601()) + assert offerSerder.ked == {'a': {'i': 'EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm', 'm': 'Agreed'}, + 'd': 'ECxQe2TgUCRjbbxyCaXMEp6EtSMaqPmDstetoi4bEUrG', + 'dt': '2021-06-27T21:26:21.233257+00:00', + 'e': {}, + 'i': 'EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY', + 'p': 'EB_Lr3fHezn1ygn-wbBT5JjzaCMxTmhUoegXeZzWC2eT', + 'q': {}, + 'r': '/ipex/agree', + 't': 'exn', + 'v': 'KERI10JSON00014a_'} + assert end == b'' + sigs = ["AAAa70b4QnTOtGOsMqcezMtVzCFuRJHGeIMkWYHZ5ZxGIXM0XDVAzkYdCeadfPfzlKC6dkfiwuJ0IzLOElaanUgH"] + + body = dict( + exn=offerSerder.ked, + sigs=sigs, + atc="", + rec=["EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM"] + ) + + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/test/ipex/agree", body=data) + + assert res.status_code == 400 + assert res.json == {'description': 'attempt to send to unknown ' + 'AID=EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM', + 'title': '400 Bad Request'} + + body = dict( + exn=offerSerder.ked, + sigs=sigs, + atc="", + rec=[pre1] + ) + + # Bad Sender + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/BAD/ipex/agree", body=data) + assert res.status_code == 404 + assert res.json == {'description': 'alias=BAD is not a valid reference to an identifier', + 'title': '404 Not Found'} + + data = json.dumps(body).encode("utf-8") + res = client.simulate_post(path="/identifiers/test/ipex/agree", body=data) + assert res.json == {'done': False, + 'error': None, + 'metadata': {'said': 'ECxQe2TgUCRjbbxyCaXMEp6EtSMaqPmDstetoi4bEUrG'}, + 'name': 'exchange.EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY', + 'response': None} + + assert res.status_code == 200 + assert len(agent.exchanges) == 1 diff --git a/tests/app/test_specing.py b/tests/app/test_specing.py index 134cb337..77115bd1 100644 --- a/tests/app/test_specing.py +++ b/tests/app/test_specing.py @@ -46,7 +46,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"}}}}, "/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}": {"get": {}, "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}/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}/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 diff --git a/tests/core/test_authing.py b/tests/core/test_authing.py index c425c104..5aa24692 100644 --- a/tests/core/test_authing.py +++ b/tests/core/test_authing.py @@ -12,7 +12,7 @@ from hio.help import Hict from keri import kering from keri.app import habbing -from keri.core import parsing, eventing +from keri.core import parsing, eventing, coring from keri.end import ending from keria.app import agenting @@ -21,6 +21,8 @@ def test_authenticater(mockHelpingNowUTC): salt = b'0123456789abcdef' + salter = coring.Salter(raw=salt) + with habbing.openHab(name="caid", salt=salt, temp=True) as (controllerHby, controller): agency = agenting.Agency(name="agency", base='', bran=None, temp=True) @@ -30,7 +32,7 @@ def test_authenticater(mockHelpingNowUTC): doist = doing.Doist(limit=1.0, tock=0.03125, real=True) doist.enter(doers=[agency]) - agent = agency.create(caid=controller.pre) + agent = agency.create(caid=controller.pre, salt=salter.qb64) # Create authenticater with Agent and controllers AID headers = Hict([ @@ -203,6 +205,7 @@ def test_signature_validation(mockHelpingNowUTC): assert rep.status == falcon.HTTP_401 salt = b'0123456789abcdef' + salter = coring.Salter(raw=salt) with habbing.openHab(name="caid", salt=salt, temp=True) as (controllerHby, controller): agency = agenting.Agency(name="agency", base='', bran=None, temp=True) @@ -212,7 +215,7 @@ def test_signature_validation(mockHelpingNowUTC): doist = doing.Doist(limit=1.0, tock=0.03125, real=True) doist.enter(doers=[agency]) - agent = agency.create(caid=controller.pre) + agent = agency.create(caid=controller.pre, salt=salter.qb64) req = testing.create_req(method="POST", path="/reward") req.context.agent = agent rep = falcon.Response()