Skip to content

Commit

Permalink
New IPEX admit endpoint and support for parsing credentials on admit …
Browse files Browse the repository at this point in the history
…message submission. (#119)

* New IPEX admit endpoint and support for parsing credentials on admit message submission.

Signed-off-by: pfeairheller <[email protected]>

* Fix reference to topic

Signed-off-by: pfeairheller <[email protected]>

* Ipexing test coverage

Signed-off-by: pfeairheller <[email protected]>

---------

Signed-off-by: pfeairheller <[email protected]>
  • Loading branch information
pfeairheller authored Nov 4, 2023
1 parent d81d444 commit f80d976
Show file tree
Hide file tree
Showing 4 changed files with 300 additions and 5 deletions.
46 changes: 44 additions & 2 deletions src/keria/app/agenting.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from keri.vdr.eventing import Tevery
from keri.app import challenging

from . import aiding, notifying, indirecting, credentialing, presenting
from . import aiding, notifying, indirecting, credentialing, presenting, ipexing
from . import grouping as keriagrouping
from ..peer import exchanging as keriaexchanging
from .specing import AgentSpecResource
Expand Down Expand Up @@ -87,6 +87,7 @@ def setup(name, bran, adminPort, bootPort, base='', httpPort=None, configFile=No
notifying.loadEnds(app=app)
keriagrouping.loadEnds(app=app)
keriaexchanging.loadEnds(app=app)
ipexing.loadEnds(app=app)

if httpPort:
happ = falcon.App(middleware=falcon.CORSMiddleware(
Expand Down Expand Up @@ -293,15 +294,17 @@ def __init__(self, hby, rgy, agentHab, agency, caid, **opts):
self.witners = decking.Deck()
self.queries = decking.Deck()
self.exchanges = decking.Deck()
self.admits = decking.Deck()

receiptor = agenting.Receiptor(hby=hby)
self.postman = forwarding.Poster(hby=hby)
self.witq = agenting.WitnessInquisitor(hby=self.hby)
self.witPub = agenting.WitnessPublisher(hby=self.hby)
self.witDoer = agenting.WitnessReceiptor(hby=self.hby)

self.rep = storing.Respondant(hby=hby, cues=self.cues, mbx=Mailboxer(name=self.hby.name, temp=self.hby.temp))

doers = [habbing.HaberyDoer(habery=hby), receiptor, self.postman, self.witPub, self.rep, self.swain,
doers = [habbing.HaberyDoer(habery=hby), receiptor, self.postman, self.witq, self.witPub, self.rep, self.swain,
self.counselor, self.witDoer, *oobiery.doers]

signaler = signaling.Signaler()
Expand Down Expand Up @@ -359,6 +362,7 @@ def __init__(self, hby, rgy, agentHab, agency, caid, **opts):
Witnesser(receiptor=receiptor, witners=self.witners),
Delegator(agentHab=agentHab, swain=self.swain, anchors=self.anchors),
ExchangeSender(hby=hby, agentHab=agentHab, exc=self.exc, postman=self.postman, exchanges=self.exchanges),
Admitter(hby=hby, witq=self.witq, psr=self.parser, agentHab=agentHab, exc=self.exc, admits=self.admits),
GroupRequester(hby=hby, agentHab=agentHab, counselor=self.counselor, groups=self.groups),
SeekerDoer(seeker=self.seeker, cues=self.verifier.cues),
ExnSeekerDoer(seeker=self.exnseeker, cues=self.exc.cues)
Expand Down Expand Up @@ -485,6 +489,44 @@ def recur(self, tyme):
attachment=atc)


class Admitter(doing.Doer):

def __init__(self, hby, witq, psr, agentHab, exc, admits):
self.hby = hby
self.agentHab = agentHab
self.witq = witq
self.psr = psr
self.exc = exc
self.admits = admits
super(Admitter, self).__init__()

def recur(self, tyme):
if self.admits:
msg = self.admits.popleft()
said = msg['said']
if not self.exc.complete(said=said):
self.admits.append(msg)
return False

hab = self.hby.habs[msg['pre']]
grant, pathed = exchanging.cloneMessage(self.hby, said)

embeds = grant.ked['e']
acdc = embeds["acdc"]
issr = acdc['i']

# Lets get the latest KEL and Registry if needed
self.witq.query(hab=self.agentHab, pre=issr)
if "ri" in acdc:
self.witq.telquery(hab=self.agentHab, wits=hab.kevers[issr].wits, ri=acdc["ri"], i=acdc["d"])

for label in ("anc", "iss", "acdc"):
ked = embeds[label]
sadder = coring.Sadder(ked=ked)
ims = bytearray(sadder.raw) + pathed[label]
self.psr.parseOne(ims=ims)


class SeekerDoer(doing.Doer):

def __init__(self, seeker, cues):
Expand Down
131 changes: 131 additions & 0 deletions src/keria/app/ipexing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# -*- encoding: utf-8 -*-
"""
KERIA
keria.app.ipexing module
services and endpoint for IPEX message managements
"""
import json

import falcon
from keri.core import coring, eventing

from keria.core import httping


def loadEnds(app):
admitColEnd = IpexAdmitCollectonEnd()
app.add_route("/identifiers/{name}/ipex/admit", admitColEnd)


class IpexAdmitCollectonEnd:

@staticmethod
def on_post(req, rep, name):
""" Registries GET endpoint
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
name (str): human readable name for AID
---
summary: List credential issuance and revocation registies
description: List credential issuance and revocation registies
tags:
- Registries
responses:
200:
description: array of current credential issuance and revocation registies
"""
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")
atc = httping.getRequiredParam(body, "atc")
rec = httping.getRequiredParam(body, "rec")

route = ked['r']

match route:
case "/ipex/admit":
IpexAdmitCollectonEnd.sendAdmit(agent, hab, ked, sigs, atc, rec)
case "/multisig/exn":
IpexAdmitCollectonEnd.sendMultisigExn(agent, hab, ked, sigs, atc, rec)

rep.status = falcon.HTTP_202
rep.data = json.dumps(ked).encode("utf-8")

@staticmethod
def sendAdmit(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(f"attempt to send to unknown AID={recp}")

# use that data to create th Serder and Sigers for the exn
serder = coring.Serder(ked=ked)
sigers = [coring.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=hex(kever.lastEst.s), d=kever.lastEst.d)

ims = eventing.messagize(serder=serder, sigers=sigers, seal=seal)

# Have to add the atc to the end... this will be Pathed signatures for embeds
ims.extend(atc.encode("utf-8")) # add the pathed attachments

# make a copy and parse
agent.hby.psr.parseOne(ims=bytearray(ims))

# now get rid of the event so we can pass it as atc to send
del ims[:serder.size]

agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=rec, topic='credential'))
agent.admits.append(dict(said=ked['p'], pre=hab.pre))

@staticmethod
def sendMultisigExn(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(f"attempt to send to unknown AID={recp}")

embeds = ked['e']
admit = embeds['exn']
if admit['r'] != "/ipex/admit":
raise falcon.HTTPBadRequest(f"invalid route for embedded ipex admit {ked['r']}")

holder = admit['a']['i']
serder = coring.Serder(ked=admit)
ims = bytearray(serder.raw) + atc['exn'].encode("utf-8")
agent.hby.psr.parseOne(ims=ims)
agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=holder, topic="credential"))
agent.admits.append(dict(said=admit['p'], pre=hab.pre))

# use that data to create th Serder and Sigers for the exn
serder = coring.Serder(ked=ked)
sigers = [coring.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=hex(kever.lastEst.s), d=kever.lastEst.d)

ims = eventing.messagize(serder=serder, sigers=sigers, seal=seal)

# Have to add the atc to the end... this will be Pathed signatures for embeds
ims.extend(atc['exn'].encode("utf-8")) # add the pathed attachments

# make a copy and parse
agent.hby.psr.parseOne(ims=bytearray(ims))

# now get rid of the event so we can pass it as atc to send
del ims[:serder.size]

agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=rec, topic='credential'))
3 changes: 0 additions & 3 deletions src/keria/peer/exchanging.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ def on_post(req, rep, name):
# make a copy and parse
agent.hby.psr.parseOne(ims=bytearray(ims))

# now get rid of the event so we can pass it as atc to send
del ims[:serder.size]

msg = dict(said=serder.said, pre=hab.pre, rec=rec, topic=topic)

agent.exchanges.append(msg)
Expand Down
125 changes: 125 additions & 0 deletions tests/app/test_ipexing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# -*- encoding: utf-8 -*-
"""
KERIA
keria.app.ipe module
Testing credentialing endpoint in the Mark II Agent
"""
import json

from falcon import testing
from keri.core import eventing, coring
from keri.help import helping
from keri.peer import exchanging

from keria.app import ipexing, aiding


def test_load_ends(helpers):
with helpers.openKeria() as (agency, agent, app, client):
ipexing.loadEnds(app=app)
assert app._router is not None

res = app._router.find("/test")
assert res is None

(end, *_) = app._router.find("/identifiers/NAME/ipex/admit")
assert isinstance(end, ipexing.IpexAdmitCollectonEnd)


def test_ipex_admit(helpers, mockHelpingNowIso8601):
with helpers.openKeria() as (agency, agent, app, client):
client = testing.TestClient(app)

admitEnd = ipexing.IpexAdmitCollectonEnd()
app.add_route("/identifiers/{name}/ipex/admit", admitEnd)

end = aiding.IdentifierCollectionEnd()
app.add_route("/identifiers", end)
salt = b'0123456789abcdef'
op = helpers.createAid(client, "test", salt)
aid = op["response"]
pre = aid['i']
assert pre == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY"
dig = "EB_Lr3fHezn1ygn-wbBT5JjzaCMxTmhUoegXeZzWC2eT"

salt2 = b'0123456789abcdeg'
op = helpers.createAid(client, "recp", salt2)
aid1 = op["response"]
pre1 = aid1['i']
assert pre1 == "EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm"

exn, end = exchanging.exchange(route="/ipex/admit",
payload=dict(),
sender=pre,
embeds=dict(),
dig=dig,
recipient=pre1,
date=helping.nowIso8601())
assert exn.ked == {'a': {'i': 'EFnYGvF_ENKJ_4PGsWsvfd_R6m5cN-3KYsz_0mAuNpCm'},
'd': 'EBrMlfQbJRS9RYuP90t2PPPV24Qynmtu7BefWAqWzb0Q',
'dt': '2021-06-27T21:26:21.233257+00:00',
'e': {},
'i': 'EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY',
'p': 'EB_Lr3fHezn1ygn-wbBT5JjzaCMxTmhUoegXeZzWC2eT',
'q': {},
'r': '/ipex/admit',
't': 'exn',
'v': 'KERI10JSON00013d_'}
assert end == b''
sigs = ["AAAa70b4QnTOtGOsMqcezMtVzCFuRJHGeIMkWYHZ5ZxGIXM0XDVAzkYdCeadfPfzlKC6dkfiwuJ0IzLOElaanUgH"]

body = dict(
exn=exn.ked,
sigs=sigs,
atc="",
rec=["EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM"]
)

data = json.dumps(body).encode("utf-8")
res = client.simulate_post(path="/identifiers/test/ipex/admit", body=data)

assert res.status_code == 400
assert res.json == {'title': 'attempt to send to unknown '
'AID=EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM'}

body = dict(
exn=exn.ked,
sigs=sigs,
atc="",
rec=[pre1]
)

data = json.dumps(body).encode("utf-8")
res = client.simulate_post(path="/identifiers/test/ipex/admit", body=data)

assert res.status_code == 202
assert len(agent.exchanges) == 1
assert len(agent.admits) == 1

agent.exchanges.clear()
agent.admits.clear()

ims = eventing.messagize(serder=exn, sigers=[coring.Siger(qb64=sigs[0])])
# Test sending embedded admit in multisig/exn message
exn, end = exchanging.exchange(route="/multisig/exn",
payload=dict(),
sender=pre,
embeds=dict(exn=ims),
dig=dig,
date=helping.nowIso8601())

body = dict(
exn=exn.ked,
sigs=sigs,
atc=dict(exn=end.decode("utf-8")),
rec=[pre1]
)

data = json.dumps(body).encode("utf-8")
res = client.simulate_post(path="/identifiers/test/ipex/admit", body=data)

print(res.json)
assert res.status_code == 202
assert len(agent.exchanges) == 2
assert len(agent.admits) == 1

0 comments on commit f80d976

Please sign in to comment.