Skip to content

Commit da8e53f

Browse files
authored
feat: IPEX apply, offer, agree endpoints (WebOfTrust#198)
1 parent c8648e8 commit da8e53f

File tree

2 files changed

+532
-9
lines changed

2 files changed

+532
-9
lines changed

src/keria/app/ipexing.py

Lines changed: 215 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,33 @@ def loadEnds(app):
2020
app.add_route("/identifiers/{name}/ipex/admit", admitColEnd)
2121
grantColEnd = IpexGrantCollectionEnd()
2222
app.add_route("/identifiers/{name}/ipex/grant", grantColEnd)
23+
applyColEnd = IpexApplyCollectionEnd()
24+
app.add_route("/identifiers/{name}/ipex/apply", applyColEnd)
25+
offerColEnd = IpexOfferCollectionEnd()
26+
app.add_route("/identifiers/{name}/ipex/offer", offerColEnd)
27+
agreeColEnd = IpexAgreeCollectionEnd()
28+
app.add_route("/identifiers/{name}/ipex/agree", agreeColEnd)
2329

2430

2531
class IpexAdmitCollectionEnd:
2632

2733
@staticmethod
2834
def on_post(req, rep, name):
29-
""" Registries GET endpoint
35+
""" IPEX Admit POST endpoint
3036
3137
Parameters:
3238
req: falcon.Request HTTP request
3339
rep: falcon.Response HTTP response
3440
name (str): human readable name for AID
3541
3642
---
37-
summary: List credential issuance and revocation registies
38-
description: List credential issuance and revocation registies
43+
summary: Accept a credential being issued or presented in response to an IPEX grant
44+
description: Accept a credential being issued or presented in response to an IPEX grant
3945
tags:
4046
- Registries
4147
responses:
4248
200:
43-
description: array of current credential issuance and revocation registies
49+
description: long running operation of IPEX admit
4450
4551
"""
4652
agent = req.context.agent
@@ -154,21 +160,21 @@ class IpexGrantCollectionEnd:
154160

155161
@staticmethod
156162
def on_post(req, rep, name):
157-
""" Registries GET endpoint
163+
""" IPEX Grant POST endpoint
158164
159165
Parameters:
160166
req: falcon.Request HTTP request
161167
rep: falcon.Response HTTP response
162168
name (str): human readable name for AID
163169
164170
---
165-
summary: List credential issuance and revocation registies
166-
description: List credential issuance and revocation registies
171+
summary: Reply to IPEX agree message or initiate an IPEX exchange with a credential issuance or presentation
172+
description: Reply to IPEX agree message or initiate an IPEX exchange with a credential issuance or presentation
167173
tags:
168-
- Registries
174+
- Credentials
169175
responses:
170176
200:
171-
description: array of current credential issuance and revocation registies
177+
description: long running operation of IPEX grant
172178
173179
"""
174180
agent = req.context.agent
@@ -266,3 +272,203 @@ def sendMultisigExn(agent, hab, ked, sigs, atc, rec):
266272
agent.grants.append(dict(said=grant['d'], pre=hab.pre, rec=[holder]))
267273

268274
return agent.monitor.submit(serder.pre, longrunning.OpTypes.exchange, metadata=dict(said=serder.said))
275+
276+
277+
class IpexApplyCollectionEnd:
278+
279+
@staticmethod
280+
def on_post(req, rep, name):
281+
""" IPEX Apply POST endpoint
282+
283+
Parameters:
284+
req: falcon.Request HTTP request
285+
rep: falcon.Response HTTP response
286+
name (str): human readable name for AID
287+
288+
---
289+
summary: Request a credential from another party by initiating an IPEX exchange
290+
description: Request a credential from another party by initiating an IPEX exchange
291+
tags:
292+
- Credentials
293+
responses:
294+
200:
295+
description: long running operation of IPEX apply
296+
297+
"""
298+
agent = req.context.agent
299+
# Get the hab
300+
hab = agent.hby.habByName(name)
301+
if hab is None:
302+
raise falcon.HTTPNotFound(description=f"alias={name} is not a valid reference to an identifier")
303+
304+
body = req.get_media()
305+
306+
ked = httping.getRequiredParam(body, "exn")
307+
sigs = httping.getRequiredParam(body, "sigs")
308+
rec = httping.getRequiredParam(body, "rec")
309+
310+
route = ked['r']
311+
312+
match route:
313+
case "/ipex/apply":
314+
op = IpexApplyCollectionEnd.sendApply(agent, hab, ked, sigs, rec)
315+
case _:
316+
raise falcon.HTTPBadRequest(description=f"invalid message route {route}")
317+
318+
rep.status = falcon.HTTP_200
319+
rep.data = op.to_json().encode("utf-8")
320+
321+
@staticmethod
322+
def sendApply(agent, hab, ked, sigs, rec):
323+
for recp in rec: # Have to verify we already know all the recipients.
324+
if recp not in agent.hby.kevers:
325+
raise falcon.HTTPBadRequest(description=f"attempt to send to unknown AID={recp}")
326+
327+
# use that data to create th Serder and Sigers for the exn
328+
serder = serdering.SerderKERI(sad=ked)
329+
sigers = [coring.Siger(qb64=sig) for sig in sigs]
330+
331+
# Now create the stream to send, need the signer seal
332+
kever = hab.kever
333+
seal = eventing.SealEvent(i=hab.pre, s="{:x}".format(kever.lastEst.s), d=kever.lastEst.d)
334+
335+
# in this case, ims is a message is a sealed and signed message - signed by Signify (KERIA can't sign anything here...)
336+
ims = eventing.messagize(serder=serder, sigers=sigers, seal=seal)
337+
338+
# make a copy and parse
339+
agent.hby.psr.parseOne(ims=bytearray(ims))
340+
341+
agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=rec, topic='credential'))
342+
return agent.monitor.submit(serder.pre, longrunning.OpTypes.exchange, metadata=dict(said=serder.said))
343+
344+
class IpexOfferCollectionEnd:
345+
346+
@staticmethod
347+
def on_post(req, rep, name):
348+
""" IPEX Offer POST endpoint
349+
350+
Parameters:
351+
req: falcon.Request HTTP request
352+
rep: falcon.Response HTTP response
353+
name (str): human readable name for AID
354+
355+
---
356+
summary: Reply to IPEX apply message or initiate an IPEX exchange with an offer for a credential with certain characteristics
357+
description: Reply to IPEX apply message or initiate an IPEX exchange with an offer for a credential with certain characteristics
358+
tags:
359+
- Credentials
360+
responses:
361+
200:
362+
description: long running operation of IPEX offer
363+
364+
"""
365+
agent = req.context.agent
366+
hab = agent.hby.habByName(name)
367+
if hab is None:
368+
raise falcon.HTTPNotFound(description=f"alias={name} is not a valid reference to an identifier")
369+
370+
body = req.get_media()
371+
372+
ked = httping.getRequiredParam(body, "exn")
373+
sigs = httping.getRequiredParam(body, "sigs")
374+
atc = httping.getRequiredParam(body, "atc")
375+
rec = httping.getRequiredParam(body, "rec")
376+
377+
route = ked['r']
378+
379+
match route:
380+
case "/ipex/offer":
381+
op = IpexOfferCollectionEnd.sendOffer(agent, hab, ked, sigs, atc, rec)
382+
case _:
383+
raise falcon.HTTPBadRequest(description=f"invalid route {route}")
384+
385+
rep.status = falcon.HTTP_200
386+
rep.data = op.to_json().encode("utf-8")
387+
388+
@staticmethod
389+
def sendOffer(agent, hab, ked, sigs, atc, rec):
390+
for recp in rec: # Have to verify we already know all the recipients.
391+
if recp not in agent.hby.kevers:
392+
raise falcon.HTTPBadRequest(description=f"attempt to send to unknown AID={recp}")
393+
394+
# use that data to create th Serder and Sigers for the exn
395+
serder = serdering.SerderKERI(sad=ked)
396+
sigers = [coring.Siger(qb64=sig) for sig in sigs]
397+
398+
# Now create the stream to send, need the signer seal
399+
kever = hab.kever
400+
seal = eventing.SealEvent(i=hab.pre, s="{:x}".format(kever.lastEst.s), d=kever.lastEst.d)
401+
402+
ims = eventing.messagize(serder=serder, sigers=sigers, seal=seal)
403+
ims = ims + atc.encode("utf-8")
404+
405+
# make a copy and parse
406+
agent.hby.psr.parseOne(ims=bytearray(ims))
407+
408+
agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=rec, topic='credential'))
409+
return agent.monitor.submit(serder.pre, longrunning.OpTypes.exchange, metadata=dict(said=serder.said))
410+
411+
class IpexAgreeCollectionEnd:
412+
413+
@staticmethod
414+
def on_post(req, rep, name):
415+
""" IPEX Agree POST endpoint
416+
417+
Parameters:
418+
req: falcon.Request HTTP request
419+
rep: falcon.Response HTTP response
420+
name (str): human readable name for AID
421+
422+
---
423+
summary: Reply to IPEX offer message acknowledged willingness to accept offered credential
424+
description: Reply to IPEX offer message acknowledged willingness to accept offered credential
425+
tags:
426+
- Credentials
427+
responses:
428+
200:
429+
description: long running operation of IPEX agree
430+
431+
"""
432+
agent = req.context.agent
433+
hab = agent.hby.habByName(name)
434+
if hab is None:
435+
raise falcon.HTTPNotFound(description=f"alias={name} is not a valid reference to an identifier")
436+
437+
body = req.get_media()
438+
439+
ked = httping.getRequiredParam(body, "exn")
440+
sigs = httping.getRequiredParam(body, "sigs")
441+
rec = httping.getRequiredParam(body, "rec")
442+
443+
route = ked['r']
444+
445+
match route:
446+
case "/ipex/agree":
447+
op = IpexAgreeCollectionEnd.sendAgree(agent, hab, ked, sigs, rec)
448+
case _:
449+
raise falcon.HTTPBadRequest(description=f"invalid route {route}")
450+
451+
rep.status = falcon.HTTP_200
452+
rep.data = op.to_json().encode("utf-8")
453+
454+
@staticmethod
455+
def sendAgree(agent, hab, ked, sigs, rec):
456+
for recp in rec: # Have to verify we already know all the recipients.
457+
if recp not in agent.hby.kevers:
458+
raise falcon.HTTPBadRequest(description=f"attempt to send to unknown AID={recp}")
459+
460+
# use that data to create th Serder and Sigers for the exn
461+
serder = serdering.SerderKERI(sad=ked)
462+
sigers = [coring.Siger(qb64=sig) for sig in sigs]
463+
464+
# Now create the stream to send, need the signer seal
465+
kever = hab.kever
466+
seal = eventing.SealEvent(i=hab.pre, s="{:x}".format(kever.lastEst.s), d=kever.lastEst.d)
467+
468+
ims = eventing.messagize(serder=serder, sigers=sigers, seal=seal)
469+
470+
# make a copy and parse
471+
agent.hby.psr.parseOne(ims=bytearray(ims))
472+
473+
agent.exchanges.append(dict(said=serder.said, pre=hab.pre, rec=rec, topic='credential'))
474+
return agent.monitor.submit(serder.pre, longrunning.OpTypes.exchange, metadata=dict(said=serder.said))

0 commit comments

Comments
 (0)