diff --git a/setup.py b/setup.py index c1327b9e..ca624705 100644 --- a/setup.py +++ b/setup.py @@ -34,12 +34,12 @@ setup( name='keria', - version='0.0.1', # also change in src/keria/__init__.py + version='0.1.0', # 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.", - author='Samuel M. Smith', - author_email='sam@samuelsmith.org', + author='Philip S. Feairheller', + author_email='pfeairheller@gmail.com', url='https://github.com/WebOfTrust/keria', packages=find_packages('src'), package_dir={'': 'src'}, @@ -76,7 +76,7 @@ python_requires='>=3.10.4', install_requires=[ 'hio>=0.6.9', - 'keri @ git+https://git@github.com/WebOfTrust/keripy.git', + 'keri>=1.1.0', 'mnemonic>=0.20', 'multicommand>=1.0.0', 'falcon>=3.1.0', diff --git a/src/keria/__init__.py b/src/keria/__init__.py index 13aec95d..e39210af 100644 --- a/src/keria/__init__.py +++ b/src/keria/__init__.py @@ -3,5 +3,5 @@ main package """ -__version__ = '0.0.1' # also change in setup.py +__version__ = '0.1.0' # also change in setup.py diff --git a/src/keria/app/agenting.py b/src/keria/app/agenting.py index 97bc802d..963fb716 100644 --- a/src/keria/app/agenting.py +++ b/src/keria/app/agenting.py @@ -818,7 +818,7 @@ def on_post(self, req, rep): caid = icp.pre - if caid in self.agency.agents: + if self.agency.get(caid=caid) is not None: raise falcon.HTTPBadRequest(title="agent already exists", description=f"agent for controller {caid} already exists") diff --git a/src/keria/app/aiding.py b/src/keria/app/aiding.py index ae2c9a06..b9968980 100644 --- a/src/keria/app/aiding.py +++ b/src/keria/app/aiding.py @@ -52,7 +52,7 @@ def loadEnds(app, agency, authn): chaResEnd = ChallengeResourceEnd() app.add_route("/challenges/{name}", chaResEnd) chaVerResEnd = ChallengeVerifyResourceEnd() - app.add_route("/challenges/{name}/verify/{source}", chaVerResEnd) + app.add_route("/challenges_verify/{source}", chaVerResEnd) contactColEnd = ContactCollectionEnd() app.add_route("/contacts", contactColEnd) @@ -442,6 +442,9 @@ def on_get(req, rep, name): name (str): human readable name for Hab to GET """ + if not name: + raise falcon.HTTPBadRequest(description="name is required") + agent = req.context.agent hab = agent.hby.habByName(name) if hab is None: @@ -630,6 +633,9 @@ def on_get(req, rep, name): """ agent = req.context.agent + if not name: + raise falcon.HTTPBadRequest(description="name is required") + hab = agent.hby.habByName(name) if not hab: raise falcon.HTTPNotFound(description="invalid alias {name}") @@ -948,13 +954,12 @@ class ChallengeVerifyResourceEnd: """ Resource for Challenge/Response Verification Endpoints """ @staticmethod - def on_post(req, rep, name, source): + def on_post(req, rep, source): """ Challenge POST endpoint Parameters: req: falcon.Request HTTP request rep: falcon.Response HTTP response - name: human readable name of identifier to use to sign the challenge/response source: qb64 AID of of source of signed response to verify --- @@ -990,9 +995,6 @@ def on_post(req, rep, name, source): description: Success submission of signed challenge/response """ agent = req.context.agent - hab = agent.hby.habByName(name) - if hab is None: - raise falcon.HTTPNotFound(description="no matching Hab for alias {name}") body = req.get_media() words = httping.getRequiredParam(body, "words") @@ -1008,13 +1010,12 @@ def on_post(req, rep, name, source): rep.status = falcon.HTTP_202 @staticmethod - def on_put(req, rep, name, source): + def on_put(req, rep, source): """ Challenge PUT accept endpoint Parameters: req: falcon.Request HTTP request rep: falcon.Response HTTP response - name: human readable name of identifier to use to sign the challenge/response source: qb64 AID of of source of signed response to verify --- @@ -1049,10 +1050,6 @@ def on_put(req, rep, name, source): description: Success submission of signed challenge/response """ agent = req.context.agent - hab = agent.hby.habByName(name) - if hab is None: - raise falcon.HTTPNotFound(description="no matching Hab for alias {name}") - body = req.get_media() if "said" not in body: raise falcon.HTTPBadRequest(description="challenge response acceptance requires 'aid' and 'said'") diff --git a/src/keria/app/credentialing.py b/src/keria/app/credentialing.py index b6e49702..3b655f27 100644 --- a/src/keria/app/credentialing.py +++ b/src/keria/app/credentialing.py @@ -33,8 +33,10 @@ def loadEnds(app, identifierResource): credentialCollectionEnd = CredentialCollectionEnd(identifierResource) app.add_route("/identifiers/{name}/credentials", credentialCollectionEnd) - credentialResourceEnd = CredentialResourceEnd(identifierResource) - app.add_route("/identifiers/{name}/credentials/{said}", credentialResourceEnd) + credentialResourceEnd = CredentialResourceEnd() + app.add_route("/credentials/{said}", credentialResourceEnd) + credentialResourceDelEnd = CredentialResourceDeleteEnd(identifierResource) + app.add_route("/identifiers/{name}/credentials/{said}", credentialResourceDelEnd) queryCollectionEnd = CredentialQueryCollectionEnd() app.add_route("/credentials/query", queryCollectionEnd) @@ -506,23 +508,18 @@ def on_post(self, req, rep, name): class CredentialResourceEnd: - def __init__(self, identifierResource): + def __init__(self): """ - Parameters: - identifierResource (IdentifierResourceEnd): endpoint class for creating rotation and interaction events - """ - self.identifierResource = identifierResource @staticmethod - def on_get(req, rep, name, said): + def on_get(req, rep, said): """ Credentials GET endpoint Parameters: req: falcon.Request HTTP request rep: falcon.Response HTTP response - name (str): human readable alias for AID to use as issuer said (str): SAID of credential to export --- @@ -554,11 +551,6 @@ def on_get(req, rep, name, said): """ agent = req.context.agent - - hab = agent.hby.habByName(name) - if hab is None: - raise falcon.HTTPNotFound(description="name is not a valid reference to an identifier") - accept = req.get_header("accept") if accept == "application/json+cesr": rep.content_type = "application/json+cesr" @@ -623,7 +615,18 @@ def outputCred(hby, rgy, said): out.extend(signing.serialize(creder, prefixer, seqner, saider)) return out - + + +class CredentialResourceDeleteEnd: + def __init__(self, identifierResource): + """ + + Parameters: + identifierResource (IdentifierResourceEnd): endpoint class for creating rotation and interaction events + + """ + self.identifierResource = identifierResource + def on_delete(self, req, rep, name, said): """ Initiate a credential revocation @@ -655,7 +658,7 @@ def on_delete(self, req, rep, name, said): regk = rserder.ked['ri'] if regk not in agent.rgy.tevers: raise falcon.HTTPNotFound(description=f"revocation against invalid registry SAID {regk}") - + try: agent.rgy.reger.cloneCreds([coring.Saider(qb64=said)], db=agent.hby.db) except: diff --git a/src/keria/db/basing.py b/src/keria/db/basing.py index ba3d6f9c..7a39a264 100644 --- a/src/keria/db/basing.py +++ b/src/keria/db/basing.py @@ -241,6 +241,7 @@ def generateIndexes(self, said): # Assign single field Schema and ISSUER index and ISSUER/SCHEMA: self.schIdx.add(keys=(said,), val=SCHEMA_FIELD.qb64b) self.schIdx.add(keys=(said,), val=ISSUER_FIELD.qb64b) + self.schIdx.add(keys=(said,), val=REGISTRY_FIELD.qb64b) subkey = f"{ISSUER_FIELD.qb64}.{SCHEMA_FIELD.qb64}" self.schIdx.add(keys=(said,), val=subkey.encode("UTF-8")) diff --git a/src/keria/end/ending.py b/src/keria/end/ending.py index b63ae127..bee8fa6a 100644 --- a/src/keria/end/ending.py +++ b/src/keria/end/ending.py @@ -48,7 +48,7 @@ def on_get(self, _, rep, aid=None, role=None, eid=None): eid: qb64 identifier prefix of participant in role """ - if aid is None: + if not aid: if self.default is None: raise falcon.HTTPNotFound(description="no blind oobi for this node") diff --git a/tests/app/test_aiding.py b/tests/app/test_aiding.py index 8b4bef01..dc9caa17 100644 --- a/tests/app/test_aiding.py +++ b/tests/app/test_aiding.py @@ -315,6 +315,10 @@ def test_identifier_collection_end(helpers): res = client.simulate_post(path="/identifiers", body=json.dumps(body)) assert res.status_code == 202 + res = client.simulate_get(path="/identifiers/") + assert res.status_code == 400 + assert res.json == {'description': 'name is required', 'title': '400 Bad Request'} + res = client.simulate_get(path="/identifiers") assert res.status_code == 200 assert len(res.json) == 2 @@ -852,7 +856,7 @@ def test_challenge_ends(helpers): chaResEnd = aiding.ChallengeResourceEnd() app.add_route("/challenges/{name}", chaResEnd) chaVerResEnd = aiding.ChallengeVerifyResourceEnd() - app.add_route("/challenges/{name}/verify/{source}", chaVerResEnd) + app.add_route("/challenges-verify/{source}", chaVerResEnd) client = testing.TestClient(app) @@ -902,16 +906,16 @@ def test_challenge_ends(helpers): assert result.status == falcon.HTTP_404 # Missing recipient b = json.dumps(data).encode("utf-8") - result = client.simulate_put(path=f"/challenges/pal/verify/{aid['i']}", body=b) + result = client.simulate_put(path=f"/challenges-verify/{aid['i']}", body=b) assert result.status == falcon.HTTP_400 # Missing said data["said"] = exn.said b = json.dumps(data).encode("utf-8") - result = client.simulate_put(path=f"/challenges/pal/verify/EFt8G8gkCJ71e4amQaRUYss0BDK4pUpzKelEIr3yZ1D0", + result = client.simulate_put(path=f"/challenges-verify/EFt8G8gkCJ71e4amQaRUYss0BDK4pUpzKelEIr3yZ1D0", body=b) assert result.status == falcon.HTTP_404 # Missing said - result = client.simulate_put(path=f"/challenges/pal/verify/{aid['i']}", body=b) + result = client.simulate_put(path=f"/challenges-verify/{aid['i']}", body=b) assert result.status == falcon.HTTP_202 data = dict( @@ -923,12 +927,12 @@ def test_challenge_ends(helpers): assert result.status_code == 404 b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path=f"/challenges/pal/verify/EFt8G8gkCJ71e4amQaRUYss0BDK4pUpzKelEIr3yZ1D0", + result = client.simulate_post(path=f"/challenges-verify/EFt8G8gkCJ71e4amQaRUYss0BDK4pUpzKelEIr3yZ1D0", body=b) assert result.status_code == 404 b = json.dumps(data).encode("utf-8") - result = client.simulate_post(path=f"/challenges/pal/verify/{aid['i']}", body=b) + result = client.simulate_post(path=f"/challenges-verify/{aid['i']}", body=b) assert result.status == falcon.HTTP_202 op = result.json assert op["done"] is False @@ -937,7 +941,7 @@ def test_challenge_ends(helpers): agent.hby.db.reps.add(keys=(aid['i'],), val=coring.Saider(qb64=exn.said)) agent.hby.db.exns.pin(keys=(exn.said,), val=exn) - result = client.simulate_post(path=f"/challenges/pal/verify/{aid['i']}", body=b) + result = client.simulate_post(path=f"/challenges-verify/{aid['i']}", body=b) assert result.status == falcon.HTTP_202 op = result.json assert op["done"] is True @@ -1228,6 +1232,11 @@ def test_oobi_ends(helpers): iserder = serdering.SerderKERI(sad=op["response"]) assert iserder.pre == "EHgwVwQT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSysY" + # Test empty + res = client.simulate_get("/identifiers//oobis?role=agent") + assert res.status_code == 400 + assert res.json == {'description': 'name is required', 'title': '400 Bad Request'} + # Test before endroles are added res = client.simulate_get("/identifiers/pal/oobis?role=agent") assert res.status_code == 200 diff --git a/tests/app/test_basing.py b/tests/app/test_basing.py index 04e4b98b..8b5ac1f8 100644 --- a/tests/app/test_basing.py +++ b/tests/app/test_basing.py @@ -38,6 +38,7 @@ def test_seeker(helpers, seeder, mockHelpingNowUTC): # Verify the indexes created for the QVI schema assert indexes == ['5AABAA-s', '5AABAA-i', + '4AABA-ri', '5AABAA-i.5AABAA-s', '4AAB-a-i', '4AAB-a-i.5AABAA-s', @@ -73,6 +74,7 @@ def test_seeker(helpers, seeder, mockHelpingNowUTC): # Test the indexes assigned to the LE schema assert indexes == ['5AABAA-s', '5AABAA-i', + '4AABA-ri', '5AABAA-i.5AABAA-s', '4AAB-a-i', '4AAB-a-i.5AABAA-s', @@ -122,6 +124,12 @@ def test_seeker(helpers, seeder, mockHelpingNowUTC): saids = seeker.find({}).limit(50) assert len(list(saids)) == 50 + saids = seeker.find({ '-ri': "EACehJRd0wfteUAJgaTTJjMSaQqWvzeeHqAMMqxuqxU4" }) + assert len(list(saids)) == 25 + + saids = seeker.find({ '-ri': "EAzc9zFLaK22zbrKDGIgKtrpDBNKWKvl8B0FKYAo19z_" }) + assert len(list(saids)) == 0 + saids = seeker.find({'-d': "EAzc9zFLaK22zbrKDGIgKtrpDBNKWKvl8B0FKYAo19z_"}) assert list(saids) == ['EAzc9zFLaK22zbrKDGIgKtrpDBNKWKvl8B0FKYAo19z_'] diff --git a/tests/app/test_credentialing.py b/tests/app/test_credentialing.py index 2cadc4f0..3039856c 100644 --- a/tests/app/test_credentialing.py +++ b/tests/app/test_credentialing.py @@ -270,8 +270,8 @@ def test_credentialing_ends(helpers, seeder): app.add_route("/identifiers/{name}/credentials", credEnd) credResEnd = credentialing.CredentialQueryCollectionEnd() app.add_route("/credentials/query", credResEnd) - credResEnd = credentialing.CredentialResourceEnd(idResEnd) - app.add_route("/identifiers/{name}/credentials/{said}", credResEnd) + credResEnd = credentialing.CredentialResourceEnd() + app.add_route("/credentials/{said}", credResEnd) assert hab.pre == "EIqTaQiZw73plMOq8pqHTi9BDgDrrE7iE9v2XfN2Izze" @@ -375,22 +375,16 @@ def test_credentialing_ends(helpers, seeder): assert res.status_code == 200 assert len(res.json) == 4 - res = client.simulate_get(f"/identifiers/test/credentials/{saids[0]}") + res = client.simulate_get(f"/credentials/{saids[0]}") assert res.status_code == 200 assert res.headers['content-type'] == "application/json" assert res.json['sad']['d'] == saids[0] headers = {"Accept": "application/json+cesr"} - res = client.simulate_get(f"/identifiers/{hab.name}/credentials/{saids[0]}", headers=headers) - assert res.status_code == 404 - - res = client.simulate_get(f"/identifiers/test/credentials/{saids[0]}", headers=headers) + res = client.simulate_get(f"/credentials/{saids[0]}", headers=headers) assert res.status_code == 200 assert res.headers['content-type'] == "application/json+cesr" - res = client.simulate_get(f"/identifiers/bad_test/credentials/{saids[0]}", headers=headers) - assert res.status_code == 404 - def test_revoke_credential(helpers, seeder): with helpers.openKeria() as (agency, agent, app, client): @@ -406,8 +400,10 @@ def test_revoke_credential(helpers, seeder): app.add_route("/identifiers", end) endRolesEnd = aiding.EndRoleCollectionEnd() app.add_route("/identifiers/{name}/endroles", endRolesEnd) - credResEnd = credentialing.CredentialResourceEnd(idResEnd) - app.add_route("/identifiers/{name}/credentials/{said}", credResEnd) + credResEnd = credentialing.CredentialResourceEnd() + app.add_route("/credentials/{said}", credResEnd) + credResDelEnd = credentialing.CredentialResourceDeleteEnd(idResEnd) + app.add_route("/identifiers/{name}/credentials/{said}", credResDelEnd) credResEnd = credentialing.CredentialQueryCollectionEnd() app.add_route("/credentials/query", credResEnd) diff --git a/tests/app/test_specing.py b/tests/app/test_specing.py index 575b172e..134cb337 100644 --- a/tests/app/test_specing.py +++ b/tests/app/test_specing.py @@ -44,5 +44,9 @@ def test_spec_resource(helpers): assert "/states" in paths 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"}}}}, "/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": {}}, "/challenges/{name}/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"}}}}, "/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}/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/end/test_ending.py b/tests/end/test_ending.py index e97320ad..ebc5331d 100644 --- a/tests/end/test_ending.py +++ b/tests/end/test_ending.py @@ -61,6 +61,10 @@ def test_oobi_end(helpers): assert res.status_code == 404 assert res.json == {'description': 'no blind oobi for this node', 'title': '404 Not Found'} + res = client.simulate_get(path=f"/oobi/") + assert res.status_code == 404 + assert res.json == {'description': 'no blind oobi for this node', 'title': '404 Not Found'} + # Use a bad AID res = client.simulate_get(path=f"/oobi/EHXXXXXT15OJvilVvW57HE4w0-GPs_Stj2OFoAHZSys") assert res.status_code == 404