Skip to content

Commit

Permalink
New introduce rpy handler and updates to create HTTP server (WebOfTru…
Browse files Browse the repository at this point in the history
…st#772)

* Fix OOBI resolution (WebOfTrust#766)

* Fix OOBI resolution to account for knowing about a delegated AID without having the delegation approval.  Added test.

Closes WebOfTrust#762

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

* Testing older mac test running on GitHub

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

---------

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

* Merging from conflict

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

* New /introduce rpy message handler to process OOBIs presented as introductions.

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

---------

Signed-off-by: pfeairheller <[email protected]>
  • Loading branch information
pfeairheller authored May 2, 2024
1 parent ad3e759 commit 94b6744
Show file tree
Hide file tree
Showing 15 changed files with 375 additions and 46 deletions.
6 changes: 6 additions & 0 deletions src/keri/app/cli/commands/watcher/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- encoding: utf-8 -*-
"""
KERI
keri.app.cli.commands.watcher Package
"""

132 changes: 132 additions & 0 deletions src/keri/app/cli/commands/watcher/add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# -*- encoding: utf-8 -*-
"""
KERI
keri.kli.commands module
"""
import argparse

from hio import help
from hio.base import doing

from keri.app import connecting, habbing, forwarding
from keri.app.cli.common import existing
from keri.core import eventing, serdering

logger = help.ogler.getLogger()

parser = argparse.ArgumentParser(description='Add AID or Alias to list of AIDs for a watcher to watch')
parser.set_defaults(handler=lambda args: add(args),
transferable=True)
parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', required=True)
parser.add_argument('--alias', '-a', help='human readable alias for the identifier to whom the credential was issued',
required=True)
parser.add_argument('--base', '-b', help='additional optional prefix to file location of KERI keystore',
required=False, default="")
parser.add_argument('--passcode', '-p', help='22 character encryption passcode for keystore (is not saved)',
dest="bran", default=None) # passcode => bran
parser.add_argument("--watcher", '-w', help="the watcher AID or alias to add to", required=True)
parser.add_argument("--watched", '-W', help="the watched AID or alias to add")


def add(args):
""" Command line handler for adding an aid to a watcher's list of AIds to watch
Parameters:
args(Namespace): parsed command line arguments
"""

ed = AddDoer(name=args.name,
alias=args.alias,
base=args.base,
bran=args.bran,
watcher=args.watcher,
watched=args.watched)
return [ed]


class AddDoer(doing.DoDoer):

def __init__(self, name, alias, base, bran, watcher, watched):
self.hby = existing.setupHby(name=name, base=base, bran=bran)
self.hab = self.hby.habByName(alias)
self.org = connecting.Organizer(hby=self.hby)

if watcher in self.hby.kevers:
wat = watcher
else:
wat = self.org.find("alias", watcher)
if len(wat) != 1:
raise ValueError(f"invalid recipient {watcher}")
wat = wat[0]['id']

if not wat:
raise ValueError(f"unknown watcher {watcher}")

if watched in self.hby.kevers:
watd = watched
else:
watd = self.org.find("alias", watched)
if len(watd) != 1:
raise ValueError(f"invalid recipient {watched}")
watd = watd[0]['id']

if not watd:
raise ValueError(f"unknown watched {watched}")

self.watcher = wat
self.watched = watd

self.oobi = None
for (key,), obr in self.hby.db.roobi.getItemIter():
if obr.cid == watd:
self.oobi = key

if not self.oobi:
raise ValueError(f"no valid oobi for watched {self.watched}")

doers = [doing.doify(self.addDo)]

super(AddDoer, self).__init__(doers=doers)

def addDo(self, tymth, tock=0.0):
""" Grant credential by creating /ipex/grant exn message
Parameters:
tymth (function): injected function wrapper closure returned by .tymen() of
Tymist instance. Calling tymth() returns associated Tymist .tyme.
tock (float): injected initial tock value
Returns: doifiable Doist compatible generator method
"""
# enter context
self.wind(tymth)
self.tock = tock
_ = (yield self.tock)

if isinstance(self.hab, habbing.GroupHab):
raise ValueError("watchers for multisig AIDs not currently supported")

postman = forwarding.StreamPoster(hby=self.hby, hab=self.hab, recp=self.watcher, topic="reply")
for msg in self.hab.db.clonePreIter(pre=self.hab.pre):
serder = serdering.SerderKERI(raw=msg)
postman.send(serder=serder, attachment=msg[serder.size:])

data = dict(cid=self.hab.pre,
wid=self.watched,
oobi=self.oobi)

route = "/watcher/aid/add"
msg = self.hab.reply(route=route, data=data)
rpy = serdering.SerderKERI(raw=msg)
postman.send(serder=rpy, attachment=msg[rpy.size:])

doer = doing.DoDoer(doers=postman.deliver())
self.extend([doer])

while not doer.done:
yield self.tock

print(f"Request to add {self.watched} to watcher {self.watcher} submitted.")
7 changes: 5 additions & 2 deletions src/keri/app/cli/commands/witness/authenticate.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@


def auth(args):
""" Command line list credential registries handler
""" Command line handler for authenticating against a witness by retrieving the secret or a TOTP
Parameters:
args(Namespace): parsed command line arguments
"""

Expand Down Expand Up @@ -65,7 +68,7 @@ def __init__(self, name, alias, base, bran, witness):
if not wit:
raise ValueError(f"unknown witness {witness}")

self.witness = witness
self.witness = wit
self.clienter = httping.Clienter()
doers = [doing.doify(self.authDo), self.clienter]

Expand Down
2 changes: 1 addition & 1 deletion src/keri/app/habbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2077,7 +2077,7 @@ def processCuesIter(self, cues):
"""
while cues: # iteratively process each cue in cues
msgs = bytearray()
cue = cues.pull() #cues.popleft()
cue = cues.pull() # cues.popleft()
cueKin = cue["kin"] # type or kind of cue

if cueKin in ("receipt",): # cue to receipt a received event from other pre
Expand Down
12 changes: 7 additions & 5 deletions src/keri/app/indirecting.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def setupWitness(hby, alias="witness", mbx=None, aids=None, tcpPort=5631, httpPo
Setup witness controller and doers
"""
host = "0.0.0.0"
cues = decking.Deck()
doers = []

Expand Down Expand Up @@ -87,7 +88,7 @@ def setupWitness(hby, alias="witness", mbx=None, aids=None, tcpPort=5631, httpPo
receiptEnd = ReceiptEnd(hab=hab, inbound=cues, aids=aids)
app.add_route("/receipts", receiptEnd)

server = createHttpServer(httpPort, app, keypath, certpath, cafilepath)
server = createHttpServer(host, httpPort, app, keypath, certpath, cafilepath)
if not server.reopen():
raise RuntimeError(f"cannot create http server on port {httpPort}")
httpServerDoer = http.ServerDoer(server=server)
Expand All @@ -112,10 +113,11 @@ def setupWitness(hby, alias="witness", mbx=None, aids=None, tcpPort=5631, httpPo
return doers


def createHttpServer(port, app, keypath=None, certpath=None, cafilepath=None):
def createHttpServer(host, port, app, keypath=None, certpath=None, cafilepath=None):
"""
Create an HTTP or HTTPS server depending on whether TLS key material is present
Parameters:
host(str) : host to bind to for this server, or None for default of '0.0.0.0', all ifaces
port (int) : port to listen on for all HTTP(s) server instances
app (falcon.App) : application instance to pass to the http.Server instance
keypath (string) : the file path to the TLS private key
Expand All @@ -130,9 +132,9 @@ def createHttpServer(port, app, keypath=None, certpath=None, cafilepath=None):
certpath=certpath,
cafilepath=cafilepath,
port=port)
server = http.Server(port=port, app=app, servant=servant)
server = http.Server(host=host, port=port, app=app, servant=servant)
else:
server = http.Server(port=port, app=app)
server = http.Server(host=host, port=port, app=app)
return server


Expand Down Expand Up @@ -246,7 +248,7 @@ def cueDo(self, tymth=None, tock=0.0):

while True:
while self.cues:
cue = self.cues.pull() # self.cues.popleft()
cue = self.cues.pull() # self.cues.popleft()
cueKin = cue["kin"]
if cueKin == "stream":
self.queries.append(cue)
Expand Down
97 changes: 93 additions & 4 deletions src/keri/app/oobiing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@
import falcon
from hio.base import doing
from hio.help import decking
from keri.core import coring

from keri.core import coring
from . import httping
from .habbing import GroupHab
from .. import help
from .. import kering
from ..app import forwarding, connecting
from ..app import connecting
from ..core import routing, eventing, parsing, scheming, serdering
from ..db import basing
from ..end import ending
from ..end.ending import OOBI_RE, DOOBI_RE
from ..help import helping
from ..kering import Ilks, ValidationError, UnverifiedReplyError, ConfigurationError
from ..peer import exchanging

logger = help.ogler.getLogger()
Expand Down Expand Up @@ -276,7 +276,7 @@ class Oobiery:

RetryDelay = 30

def __init__(self, hby, clienter=None, cues=None):
def __init__(self, hby, rvy=None, clienter=None, cues=None):
""" DoDoer to handle the request and parsing of OOBIs
Parameters:
Expand All @@ -286,8 +286,14 @@ def __init__(self, hby, clienter=None, cues=None):
"""

self.hby = hby
self.rvy = rvy
if self.rvy is not None:
self.registerReplyRoutes(self.rvy.rtr)

self.clienter = clienter or httping.Clienter()
self.org = connecting.Organizer(hby=self.hby)

# Set up a local parser for returned events from OOBI queries.
rtr = routing.Router()
rvy = routing.Revery(db=self.hby.db, rtr=rtr)
kvy = eventing.Kevery(db=self.hby.db, lax=True, local=False, rvy=rvy)
Expand All @@ -298,6 +304,89 @@ def __init__(self, hby, clienter=None, cues=None):
self.clients = dict()
self.doers = [self.clienter, doing.doify(self.scoobiDo)]

def registerReplyRoutes(self, router):
""" Register the routes for processing messages embedded in `rpy` event messages
The Oobiery handles rpy messages with the /introduce route by processing the contained oobi
Parameters:
router(Router): reply message router
"""
router.addRoute("/introduce", self)

def processReply(self, *, serder, saider, route, cigars=None, tsgs=None, **kwargs):
"""
Process one reply message for route = /introduce
with either attached nontrans receipt couples in cigars or attached trans
indexed sig groups in tsgs.
Assumes already validated saider, dater, and route from serder.ked
Parameters:
serder (SerderKERI): instance of reply msg (SAD)
saider (Saider): instance from said in serder (SAD)
route (str): reply route
cigars (list): of Cigar instances that contain nontrans signing couple
signature in .raw and public key in .verfer
tsgs (list): tuples (quadruples) of form
(prefixer, seqner, diger, [sigers]) where:
prefixer is pre of trans endorser
seqner is sequence number of trans endorser's est evt for keys for sigs
diger is digest of trans endorser's est evt for keys for sigs
[sigers] is list of indexed sigs from trans endorser's keys from est evt
OobiRecord:
date: str = date time of reply message of the introduction
Reply Message:
{
"v" : "KERI10JSON00011c_",
"t" : "rpy",
"d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM",
"dt": "2020-08-22T17:50:12.988921+00:00",
"r" : "/introduce",
"a" :
{
"cid": "ENcOes8_t2C7tck4X4j61fSm0sWkLbZrEZffq7mSn8On",
"oobi": "http://localhost:5632/oobi/ENcOes8_t2C7tck4X4j61fSm0sWkLbZrEZffq7mSn8On/witness",
}
}
"""
if route != "/introduce":
raise ValidationError(f"Usupported route={route} in {Ilks.rpy} "
f"msg={serder.ked}.")

data = serder.ked['a']
dt = serder.ked["dt"]

for k in ("cid", "oobi"):
if k not in data:
raise ValidationError(f"Missing element={k} from attributes in"
f" {Ilks.rpy} msg={serder.ked}.")

cider = coring.Prefixer(qb64=data["cid"]) # raises error if unsupported code
cid = cider.qb64 # controller authorizing eid at role
aid = cid # authorizing attribution id

oobi = data["oobi"]
url = urlparse(oobi)
if url.scheme not in ("http", "https"):
raise ValidationError(f"Invalid url scheme for introduced OOBI scheme={url.scheme}")

if self.rvy is None:
raise ConfigurationError("this oobiery is not configured to handle rpy introductions")

# Process BADA RUN but with no previous reply message, always process introductions
accepted = self.rvy.acceptReply(serder=serder, saider=saider, route=route,
aid=aid, osaider=None, cigars=cigars,
tsgs=tsgs)
if not accepted:
raise UnverifiedReplyError(f"Unverified introduciton reply. {serder.ked}")

obr = basing.OobiRecord(cid=cid, date=dt)
self.hby.db.oobis.put(keys=(oobi,), val=obr)

def scoobiDo(self, tymth=None, tock=0.0):
"""
Returns doifiable Doist compatibile generator method (doer dog) to process
Expand Down
3 changes: 0 additions & 3 deletions src/keri/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
keri.core Package
"""

#__all__ = ["coring", "eventing", "parsing", "scheming"]


# Constants etc
from .coring import (Tiers, )

Expand Down
Loading

0 comments on commit 94b6744

Please sign in to comment.