diff --git a/Makefile b/Makefile index 307699ed..3f278ddb 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: build-keria build-keria: - @docker buildx build --platform=linux/amd64 --no-cache -f images/keria.dockerfile --tag weboftrust/keria:0.1.3 --tag weboftrust/keria:latest . + @docker buildx build --platform=linux/amd64 --no-cache -f images/keria.dockerfile --tag weboftrust/keria:0.1.4-dev0 --tag weboftrust/keria:latest . publish-keria: @docker push weboftrust/keria --all-tags \ No newline at end of file diff --git a/images/keria.dockerfile b/images/keria.dockerfile index 34f9e3e4..340ce675 100644 --- a/images/keria.dockerfile +++ b/images/keria.dockerfile @@ -1,55 +1,10 @@ -# Builder stage -FROM python:3.10.13-alpine3.18 as builder +FROM weboftrust/keri:1.1.17 -# Install compilation dependencies -RUN apk --no-cache add \ - bash \ - alpine-sdk \ - libffi-dev \ - libsodium \ - libsodium-dev +WORKDIR /usr/local/var -SHELL ["/bin/bash", "-c"] +RUN mkdir keria +COPY . /usr/local/var/keria -# Install Rust for blake3 dependency build -RUN curl https://sh.rustup.rs -sSf | bash -s -- -y +WORKDIR /usr/local/var/keria -WORKDIR /keria - -RUN python -m venv venv -ENV PATH=/keria/venv/bin:${PATH} -RUN pip install --upgrade pip - -# Copy in Python dependency files -COPY requirements.txt setup.py ./ -# "src/" dir required for installation of dependencies with setup.py -RUN mkdir /keria/src -# Install Python dependencies -RUN . "$HOME/.cargo/env" && \ - pip install -r requirements.txt - -# Runtime stage -FROM python:3.10.13-alpine3.18 - -# Install runtime dependencies -RUN apk --no-cache add \ - bash \ - alpine-sdk \ - libsodium-dev - -WORKDIR /keria - -# Copy over compiled dependencies -COPY --from=builder /keria /keria -# Copy in KERIA source files - enables near instantaneous builds for source only changes -RUN mkdir -p /usr/local/var/keri -ENV KERI_AGENT_CORS=${KERI_AGENT_CORS:-false} -ENV PATH=/keria/venv/bin:${PATH} - -EXPOSE 3901 -EXPOSE 3902 -EXPOSE 3903 - -COPY src/ src/ - -ENTRYPOINT ["keria", "start", "--config-file", "demo-witness-oobis", "--config-dir", "./scripts"] +RUN pip install -r requirements.txt \ No newline at end of file diff --git a/setup.py b/setup.py index 62b62a11..c2ca7da9 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ setup( name='keria', - version='0.1.3', # also change in src/keria/__init__.py + version='0.1.4-dev0', # also change in src/keria/__init__.py license='Apache Software License 2.0', description='KERIA: KERI Agent in the cloud', long_description="KERIA: KERI Agent in the cloud.", @@ -76,7 +76,7 @@ python_requires='>=3.10.4', install_requires=[ 'hio>=0.6.9', - 'keri>=1.1.6', + 'keri>=1.1.17', 'mnemonic>=0.20', 'multicommand>=1.0.0', 'falcon>=3.1.0', diff --git a/src/keria/__init__.py b/src/keria/__init__.py index 7657da7f..9fd2f432 100644 --- a/src/keria/__init__.py +++ b/src/keria/__init__.py @@ -3,5 +3,5 @@ main package """ -__version__ = '0.1.3' # also change in setup.py +__version__ = '0.1.4-dev0' # also change in setup.py diff --git a/src/keria/app/agenting.py b/src/keria/app/agenting.py index 963fb716..a46976ee 100644 --- a/src/keria/app/agenting.py +++ b/src/keria/app/agenting.py @@ -308,11 +308,12 @@ def __init__(self, hby, rgy, agentHab, agency, caid, **opts): self.witq = agenting.WitnessInquisitor(hby=self.hby) self.witPub = agenting.WitnessPublisher(hby=self.hby) self.witDoer = agenting.WitnessReceiptor(hby=self.hby) + self.submitter = agenting.WitnessReceiptor(hby=self.hby, force=True, tock=5.0) 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.witq, self.witPub, self.rep, self.swain, - self.counselor, self.witDoer, *oobiery.doers] + self.counselor, self.witDoer, self.submitter, *oobiery.doers] signaler = signaling.Signaler() self.notifier = Notifier(hby=hby, signaler=signaler) @@ -336,7 +337,7 @@ def __init__(self, hby, rgy, agentHab, agency, caid, **opts): grouping.loadHandlers(exc=self.exc, mux=self.mux) protocoling.loadHandlers(hby=self.hby, exc=self.exc, notifier=self.notifier) self.monitor = longrunning.Monitor(hby=hby, swain=self.swain, counselor=self.counselor, temp=hby.temp, - registrar=self.registrar, credentialer=self.credentialer, exchanger=self.exc) + registrar=self.registrar, credentialer=self.credentialer, exchanger=self.exc, submitter=self.submitter) self.rvy = routing.Revery(db=hby.db, cues=self.cues) self.kvy = eventing.Kevery(db=hby.db, @@ -405,6 +406,9 @@ def inceptExtern(self, pre, verfers, digers, **kwargs): self.agency.incept(self.caid, pre) + def witnessResubmit(self, pre): + self.submitDoer.msgs.append(dict(pre=pre)) + class ParserDoer(doing.Doer): diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index b9968980..0c496f0b 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -29,6 +29,8 @@ def loadEnds(app, agency, authn): aidsEnd = IdentifierCollectionEnd() app.add_route("/identifiers", aidsEnd) + submitEnd = IdentifierSubmitResourceEnd() + app.add_route("/identifiers/{name}/submit", submitEnd) aidEnd = IdentifierResourceEnd() app.add_route("/identifiers/{name}", aidEnd) @@ -615,7 +617,35 @@ def info(hab, rm, full=False): return data +class IdentifierSubmitResourceEnd: + """ Resource class to submit identifier to its witnesses for receipts """ + @staticmethod + def on_put(req, rep, name): + """ Submit request POST endpoint + + Parameters: + req (Request): falcon.Request HTTP request object + rep (Response): falcon.Response HTTP response object + name (str): human readable name for Hab to submit + + """ + agent = req.context.agent + try: + hab = agent.hby.habByName(name) + if not hab: + raise falcon.HTTPNotFound(description=f"invalid alias {name}") + + oid = hab.pre + op = agent.monitor.submit(oid, longrunning.OpTypes.submit, metadata=dict(pre=hab.pre)) + + rep.content_type = "application/json" + rep.status = falcon.HTTP_202 + rep.data = op.to_json().encode("utf-8") + + except (kering.AuthError, ValueError) as e: + raise falcon.HTTPBadRequest(description=e.args[0]) + class IdentifierOOBICollectionEnd: """ This class represents the OOBI subresource collection endpoint for identifiers diff --git a/src/keria/core/keeping.py b/src/keria/core/keeping.py index d95b8a15..35f30f5f 100644 --- a/src/keria/core/keeping.py +++ b/src/keria/core/keeping.py @@ -12,6 +12,8 @@ from keri.db import dbing, subing, koming from keri.help import helping +from keria import core + @dataclass() class Prefix: diff --git a/src/keria/core/longrunning.py b/src/keria/core/longrunning.py index c0d41122..7d739325 100644 --- a/src/keria/core/longrunning.py +++ b/src/keria/core/longrunning.py @@ -19,11 +19,11 @@ # long running operationt types Typeage = namedtuple("Tierage", 'oobi witness delegation group query registry credential endrole challenge exchange ' - 'done') + 'submit done') OpTypes = Typeage(oobi="oobi", witness='witness', delegation='delegation', group='group', query='query', registry='registry', credential='credential', endrole='endrole', challenge='challenge', - exchange='exchange', done='done') + exchange='exchange', submit='submit', done='done') @dataclass_json @@ -93,13 +93,18 @@ class Monitor: """ def __init__(self, hby, swain, counselor=None, registrar=None, exchanger=None, credentialer=None, opr=None, - temp=False): + submitter=None, temp=False): """ Create long running operation monitor Parameters: hby (Habery): identifier database environment swain(Sealer): Delegation processes tracker + counselor (Counselor): Group processes tracker + registrar (Registrar): Registry processes tracker + exchanger (Exchanger): Exchange processes tracker + credentialer (Credentialer): Credential processes tracker opr (Operator): long running operations database + submitter (Submitter): Submit processes tracker """ self.hby = hby @@ -109,6 +114,7 @@ def __init__(self, hby, swain, counselor=None, registrar=None, exchanger=None, c self.exchanger = exchanger self.credentialer = credentialer self.opr = opr if opr is not None else Operator(name=hby.name, temp=temp) + self.submitter = submitter def submit(self, oid, typ, metadata=None): """ Submit a new long running operation to track @@ -357,7 +363,7 @@ def status(self, op): elif op.type in (OpTypes.endrole, ): if "cid" not in op.metadata or "role" not in op.metadata or "eid" not in op.metadata: raise kering.ValidationError( - f"invalid long running {op.type} operation, metadata missing 'ced' field") + f"invalid long running {op.type} operation, metadata missing 'cid'/'role'/'eid' field") cid = op.metadata['cid'] role = op.metadata['role'] @@ -378,7 +384,7 @@ def status(self, op): if "words" not in op.metadata: raise kering.ValidationError( - f"invalid long running {op.type} operation, metadata missing 'ced' field") + f"invalid long running {op.type} operation, metadata missing 'words' field") found = False words = op.metadata["words"] @@ -394,6 +400,15 @@ def status(self, op): operation.response = dict(exn=exn.ked) else: operation.done = False + elif op.type in (OpTypes.submit, ): + if "pre" not in op.metadata: + raise kering.ValidationError( + f"invalid long running {op.type} operation, metadata missing 'pre' field") + + pre = op.metadata['pre'] + self.submitter.msgs.append(dict(pre=pre)) + + operation.done = True elif op.type in (OpTypes.done, ): operation.done = True diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index 7b6efe29..69b1961a 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -1218,8 +1218,10 @@ def test_identifier_resource_end(helpers): habbing.openHby(name="p2", temp=True) as p2hby: end = aiding.IdentifierCollectionEnd() resend = aiding.IdentifierResourceEnd() + subend = aiding.IdentifierSubmitResourceEnd() app.add_route("/identifiers", end) app.add_route("/identifiers/{name}", resend) + app.add_route("/identifiers/{name}/submit", subend) client = testing.TestClient(app) salt = b'0123456789abcdef' @@ -1248,6 +1250,12 @@ def test_identifier_resource_end(helpers): assert res.status_code == 200 assert res.json['prefix'] == 'EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY' + res = client.simulate_put(path="/identifiers/bad/submit") + assert res.status_code == 404 + assert res.json == {'description': 'invalid alias bad', 'title': '404 Not Found'} + + res = client.simulate_put(path="/identifiers/aid1/submit") + assert res.status_code == 202 def test_oobi_ends(helpers): with helpers.openKeria() as (agency, agent, app, client): diff --git a/tests/app/test_specing.py b/tests/app/test_specing.py index 134cb337..e27130ed 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}": {"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}/submit": {"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_longrunning.py b/tests/core/test_longrunning.py index 267a6ed5..ebb4815b 100644 --- a/tests/core/test_longrunning.py +++ b/tests/core/test_longrunning.py @@ -16,6 +16,8 @@ def test_operations(helpers): app.add_route("/operations", opColEnd) opResEnd = longrunning.OperationResourceEnd() app.add_route("/operations/{name}", opResEnd) + subs = aiding.IdentifierSubmitResourceEnd() + app.add_route("/identifiers/{name}/submit", subs) # operations is empty @@ -168,6 +170,13 @@ def test_operations(helpers): assert op.name == f"query.{recp}" assert op.done is False + # submit + + res = client.simulate_put( + path=f"/identifiers/user1/submit", json=body) + op = res.json + assert op["done"] is True + assert op["name"] == "submit.EAF7geUfHm-M5lA-PI6Jv-4708a-KknnlMlA7U1_Wduv" def test_error(helpers): with helpers.openKeria() as (agency, agent, app, client):