Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions scripts/demo/contacts/demo_contacts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/bash
# Demo script for KLI contact management commands


KS_ALICE="contact-demo-alice"
KS_BOB="contact-demo-bob"
SALT_ALICE="0AAQmsjh-C7kAJZQEzdrzwB7"
SALT_BOB="0ABBnsjh-D8lBKZRFzesx1C8"

kli init --name ${KS_ALICE} --nopasscode --salt ${SALT_ALICE} \
--config-dir "${KERI_SCRIPT_DIR}" --config-file demo-witness-oobis
kli incept --name ${KS_ALICE} --alias alice \
--file "${KERI_DEMO_SCRIPT_DIR}/data/trans-wits-sample.json"

echo "Creating Bob's keystore and identifier..."
kli init --name ${KS_BOB} --nopasscode --salt ${SALT_BOB} \
--config-dir "${KERI_SCRIPT_DIR}" --config-file demo-witness-oobis
kli incept --name ${KS_BOB} --alias bob \
--file "${KERI_DEMO_SCRIPT_DIR}/data/trans-wits-sample.json"

ALICE_OOBI=$(kli oobi generate --name ${KS_ALICE} --alias alice --role witness | head -1)
BOB_OOBI=$(kli oobi generate --name ${KS_BOB} --alias bob --role witness | head -1)
echo "Alice OOBI: ${ALICE_OOBI}"
echo "Bob OOBI: ${BOB_OOBI}"

echo "Alice adds Bob as a contact..."
kli contacts add --name ${KS_ALICE} \
--oobi "${BOB_OOBI}" \
--alias bob \
--field company=ACME \
--field role=Engineer

echo "Bob adds Alice as a contact..."
kli contacts add --name ${KS_BOB} \
--oobi "${ALICE_OOBI}" \
--alias alice \
--field company=GLEIF

echo "Alice's contacts:"
kli contacts list --name ${KS_ALICE}

echo "Alice gets Bob by alias..."
kli contacts get --name ${KS_ALICE} --alias bob

echo "Alice renames 'bob' to 'robert'..."
kli contacts rename --name ${KS_ALICE} \
--old-alias bob \
--alias robert
kli contacts get --name ${KS_ALICE} --alias robert

echo "Alice queries robert's latest key state..."
kli contacts query --name ${KS_ALICE} --alias alice --contact-alias robert

echo "Alice deletes contact 'robert'..."
kli contacts delete --name ${KS_ALICE} --alias robert --yes

echo "Alice's contacts after delete:"
kli contacts list --name ${KS_ALICE} || echo "(no contacts)"

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

"""
import argparse
import json

from keri import help
from hio.base import doing

import keri.app.oobiing
from keri.app import connecting, habbing, oobiing
from keri.app.cli.common import existing
from keri.db import basing
from keri.help import helping

logger = help.ogler.getLogger()

parser = argparse.ArgumentParser(description='Add a contact via OOBI resolution')
parser.set_defaults(handler=lambda args: handler(args),
transferable=True)
parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', 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('--oobi', '-o', help='OOBI URL to resolve for contact', required=True)
parser.add_argument('--alias', help='alias to set for contact', required=False, default=None)
parser.add_argument('--field', '-f', help='field in key=value format', action='append', dest='fields', default=None)


def handler(args):
""" command line method for adding a contact via OOBI resolution

Parameters:
args(Namespace): parse args namespace object

"""
name = args.name
base = args.base
bran = args.bran
oobi = args.oobi
alias = args.alias
fields = args.fields

addDoer = ContactAddDoer(name=name, base=base, bran=bran,
oobi=oobi, alias=alias, fields=fields)
return [addDoer]


class ContactAddDoer(doing.DoDoer):
""" DoDoer for adding a contact via OOBI resolution """

def __init__(self, name, base, bran, oobi, alias, fields):
self.hby = existing.setupHby(name=name, base=base, bran=bran)
self.hbyDoer = habbing.HaberyDoer(habery=self.hby)
self.oobi = oobi
self.alias = alias
self.fields = fields

doers = [self.hbyDoer, doing.doify(self.add)]
super(ContactAddDoer, self).__init__(doers=doers)

def add(self, tymth, tock=0.0):
""" Resolves OOBI and creates contact

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
"""
self.wind(tymth)
self.tock = tock
_ = (yield self.tock)

obr = basing.OobiRecord(date=helping.nowIso8601())
if self.alias:
obr.oobialias = self.alias

self.hby.db.oobis.put(keys=(self.oobi,), val=obr)

obi = keri.app.oobiing.Oobiery(hby=self.hby)
authn = oobiing.Authenticator(hby=self.hby)
self.extend(obi.doers)
self.extend(authn.doers)

# Wait for resolution
while not self.hby.db.roobi.get(keys=(self.oobi,)):
yield 0.25

resolved = self.hby.db.roobi.get(keys=(self.oobi,))
cid = resolved.cid

org = connecting.Organizer(hby=self.hby)

data = {}
if self.alias:
data['alias'] = self.alias

if self.fields:
for field in self.fields:
if '=' not in field:
print(f"Invalid field format: {field}. Use key=value")
self.remove([self.hbyDoer, *obi.doers, *authn.doers])
return
key, val = field.split('=', 1)
data[key] = val

org.update(cid, data)

contact = org.get(cid)
print(json.dumps(contact, indent=2))

self.remove([self.hbyDoer, *obi.doers, *authn.doers])
91 changes: 91 additions & 0 deletions src/keri/app/cli/commands/contacts/delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# -*- encoding: utf-8 -*-
"""
KERI
keri.kli.commands.contacts.delete module

"""
import argparse

from keri import help
from hio.base import doing

from keri.app import connecting
from keri.app.cli.common import existing
from keri.kering import ConfigurationError

logger = help.ogler.getLogger()

parser = argparse.ArgumentParser(description='Delete a contact')
parser.set_defaults(handler=lambda args: handler(args),
transferable=True)
parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', 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('--aid', '-a', help='contact AID prefix', required=False, default=None)
parser.add_argument('--alias', help='contact alias to lookup', required=False, default=None)
parser.add_argument('--yes', '-y', help='skip confirmation', action='store_true', default=False)


def handler(args):
kwa = dict(args=args)
return [doing.doify(delete, **kwa)]


def delete(tymth, tock=0.0, **opts):
""" Command line handler for deleting a contact

"""
_ = (yield tock)
args = opts["args"]
name = args.name
base = args.base
bran = args.bran
aid = args.aid
alias = args.alias
yes = args.yes

if aid is None and alias is None:
print("Either --aid or --alias is required")
return -1

try:
with existing.existingHby(name=name, base=base, bran=bran) as hby:
org = connecting.Organizer(hby=hby)

contact = None
pre = None

if aid:
pre = aid
contact = org.get(aid)
elif alias:
contacts = org.find('alias', f"^{alias}$") # Exact match
if len(contacts) == 0:
print(f"Contact with alias '{alias}' not found")
return -1
if len(contacts) > 1:
print(f"Multiple contacts match alias '{alias}'")
return -1
contact = contacts[0]
pre = contact['id']

if contact is None:
print("Contact not found")
return -1

alias_display = contact.get('alias', pre)

if not yes:
confirm = input(f"Delete contact '{alias_display}' ({pre})? [y/N]: ")
if confirm.lower() != 'y':
print("Aborted")
return 0

org.rem(pre)
print(f"Deleted contact '{alias_display}' ({pre})")

except ConfigurationError as e:
print(f"identifier prefix for {name} does not exist, incept must be run first")
return -1
62 changes: 62 additions & 0 deletions src/keri/app/cli/commands/contacts/find.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- encoding: utf-8 -*-
"""
KERI
keri.kli.commands.contacts.find module

"""
import argparse
import json

from keri import help
from hio.base import doing

from keri.app import connecting
from keri.app.cli.common import existing
from keri.kering import ConfigurationError

logger = help.ogler.getLogger()

parser = argparse.ArgumentParser(description='Find contacts by field value')
parser.set_defaults(handler=lambda args: handler(args),
transferable=True)
parser.add_argument('--name', '-n', help='keystore name and file location of KERI keystore', 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('--field', '-f', help='field name to search (default: alias)', default='alias')
parser.add_argument('--value', '-v', help='value or regex pattern to match', required=True)


def handler(args):
kwa = dict(args=args)
return [doing.doify(find, **kwa)]


def find(tymth, tock=0.0, **opts):
""" Command line handler for finding contacts by field value

"""
_ = (yield tock)
args = opts["args"]
name = args.name
base = args.base
bran = args.bran
field = args.field
value = args.value

try:
with existing.existingHby(name=name, base=base, bran=bran) as hby:
org = connecting.Organizer(hby=hby)

contacts = org.find(field, value)

if len(contacts) == 0:
print(f"No contacts found matching {field}='{value}'")
return

print(json.dumps(contacts, indent=2))

except ConfigurationError as e:
print(f"identifier prefix for {name} does not exist, incept must be run first")
return -1
Loading
Loading