diff --git a/.dockerignore b/.dockerignore index 910edaa6ff..7ea06888de 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,6 @@ build docs dist test-reports -.python-version \ No newline at end of file +.python-version +docker +env diff --git a/actions/run-indy-tails-server/action.yml b/.github/actions/run-indy-tails-server/action.yml similarity index 100% rename from actions/run-indy-tails-server/action.yml rename to .github/actions/run-indy-tails-server/action.yml diff --git a/actions/run-integration-tests/action.yml b/.github/actions/run-integration-tests/action.yml similarity index 100% rename from actions/run-integration-tests/action.yml rename to .github/actions/run-integration-tests/action.yml diff --git a/actions/run-von-network/action.yml b/.github/actions/run-von-network/action.yml similarity index 100% rename from actions/run-von-network/action.yml rename to .github/actions/run-von-network/action.yml diff --git a/.github/workflows/blackformat.yml b/.github/workflows/blackformat.yml index a9ae5dfdee..1885ba40c4 100644 --- a/.github/workflows/blackformat.yml +++ b/.github/workflows/blackformat.yml @@ -10,7 +10,9 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.9" - name: Black Code Formatter Check uses: psf/black@stable diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9d5ee6a7ea..e6f15917a0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -12,15 +12,18 @@ jobs: runs-on: ubuntu-latest if: (github.event_name == 'pull_request' && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request') + permissions: + security-events: write + steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/integrationtests.yml b/.github/workflows/integrationtests.yml index db62b14a34..c0ec41eee3 100644 --- a/.github/workflows/integrationtests.yml +++ b/.github/workflows/integrationtests.yml @@ -13,15 +13,15 @@ jobs: if: (github.event_name == 'pull_request' && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request') steps: - name: checkout-acapy - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: acapy #- name: run-von-network - # uses: ./acapy/actions/run-von-network + # uses: ./acapy/.github/actions/run-von-network #- name: run-indy-tails-server - # uses: ./acapy/actions/run-indy-tails-server + # uses: ./acapy/.github/actions/run-indy-tails-server - name: run-integration-tests - uses: ./acapy/actions/run-integration-tests + uses: ./acapy/.github/actions/run-integration-tests # to run with a specific set of tests include the following parameter: # with: # TEST_SCOPE: "-t @T001-RFC0037" diff --git a/.github/workflows/publish-indy.yml b/.github/workflows/publish-indy.yml new file mode 100644 index 0000000000..874851d5c9 --- /dev/null +++ b/.github/workflows/publish-indy.yml @@ -0,0 +1,97 @@ +name: Publish ACA-Py Image (Indy) +run-name: Publish ACA-Py ${{ inputs.tag || github.event.release.tag_name }} Image (Indy ${{ inputs.indy_version || '1.16.0' }}) +on: + release: + types: [released] + + workflow_dispatch: + inputs: + indy_version: + description: 'Indy SDK Version' + required: true + default: 1.16.0 + type: string + tag: + description: 'Image tag' + required: true + type: string + +# Note: +# - ACA-Py with Indy SDK image builds do not include support for the linux/arm64 platform. +# - See notes below for details. + +env: + INDY_VERSION: ${{ inputs.indy_version || '1.16.0' }} + +jobs: + publish-image: + strategy: + fail-fast: false + matrix: + python-version: ['3.6', '3.9'] + + name: Publish ACA-Py Image (Indy) + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Gather image info + id: info + run: | + echo "repo-owner=${GITHUB_REPOSITORY_OWNER,,}" >> $GITHUB_OUTPUT + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to the GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Image Metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ghcr.io/${{ steps.info.outputs.repo-owner }}/aries-cloudagent-python + tags: | + type=raw,value=py${{ matrix.python-version }}-indy-${{ env.INDY_VERSION }}-${{ inputs.tag || github.event.release.tag_name }} + + - name: Build and Push Image to ghcr.io + uses: docker/build-push-action@v3 + with: + push: true + context: . + file: docker/Dockerfile.indy + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + target: main + build-args: | + python_version=${{ matrix.python-version }} + indy_version=${{ env.INDY_VERSION }} + acapy_version=${{ inputs.tag || github.event.release.tag_name }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max + # Images do not include support for the linux/arm64 platform due to a known issue compiling the postgres plugin + # - https://github.com/hyperledger/indy-sdk/issues/2445 + # There is a pending PR to fix this issue here; https://github.com/hyperledger/indy-sdk/pull/2453 + platforms: linux/amd64,linux/386 + + # Temp fix + # https://github.com/docker/build-push-action/issues/252 + # https://github.com/moby/buildkit/issues/1896 + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000000..6ee9378c61 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,81 @@ +name: Publish ACA-Py Image +run-name: Publish ACA-Py ${{ inputs.tag || github.event.release.tag_name }} Image +on: + release: + types: [released] + + workflow_dispatch: + inputs: + tag: + description: 'Image tag' + required: true + type: string + +jobs: + publish-image: + strategy: + fail-fast: false + matrix: + python-version: ['3.6', '3.9'] + + name: Publish ACA-Py Image + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Gather image info + id: info + run: | + echo "repo-owner=${GITHUB_REPOSITORY_OWNER,,}" >> $GITHUB_OUTPUT + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to the GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Image Metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ghcr.io/${{ steps.info.outputs.repo-owner }}/aries-cloudagent-python + tags: | + type=raw,value=py${{ matrix.python-version }}-${{ inputs.tag || github.event.release.tag_name }} + + - name: Build and Push Image to ghcr.io + uses: docker/build-push-action@v3 + with: + push: true + context: . + file: docker/Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + target: main + build-args: | + python_version=${{ matrix.python-version }} + acapy_version=${{ inputs.tag || github.event.release.tag_name }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max + platforms: linux/amd64,linux/arm64,linux/386 + + # Temp fix + # https://github.com/docker/build-push-action/issues/252 + # https://github.com/moby/buildkit/issues/1896 + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml index 21f2f01de1..b42e56685b 100644 --- a/.github/workflows/pythonpublish.yml +++ b/.github/workflows/pythonpublish.yml @@ -8,19 +8,19 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/.github/workflows/tests-indy.yml b/.github/workflows/tests-indy.yml index a893acf5b5..7e69e76b30 100644 --- a/.github/workflows/tests-indy.yml +++ b/.github/workflows/tests-indy.yml @@ -29,7 +29,7 @@ jobs: ${{ runner.os }}-buildx-test- - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Build test image uses: docker/build-push-action@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b4a0f811b8..6c0600e70f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: hooks: - id: black stages: [commit] - - repo: https://gitlab.com/pycqa/flake8 + - repo: https://github.com/pycqa/flake8.git rev: 3.9.0 hooks: - id: flake8 diff --git a/ContainerImagesAndGithubActions.md b/ContainerImagesAndGithubActions.md new file mode 100644 index 0000000000..25bfca9dc8 --- /dev/null +++ b/ContainerImagesAndGithubActions.md @@ -0,0 +1,119 @@ +# Container Images and Github Actions + +Aries Cloud Agent - Python is most frequently deployed using containers. From +the first release of ACA-Py up through 0.7.4, much of the community has built +their Aries stack using the container images graciously provided by BC Gov and +hosted through their `bcgovimages` docker hub account. These images have been +critical to the adoption of not only ACA-Py but also Hyperledger Aries and SSI +more generally. + +Recognizing how critical these images are to the success of ACA-Py and +consistent with Hyperledger's commitment to open collaboration, container images +are now built and published directly from the Aries Cloud Agent - Python project +repository and made available through the [Github Packages Container +Registry](https://ghcr.io). + + +## Image + +This project builds and publishes the `ghcr.io/hyperledger/aries-cloudagent-python` image. +Multiple variants are available; see [Tags](#tags). + +### Tags + +ACA-Py is a foundation for building decentralized identity applications; to this +end, there are multiple variants of ACA-Py built to suit the needs of a variety +of environments and workflows. There are currently two main variants: + +- "Standard" - The default configuration of ACA-Py, including: + - Aries Askar for secure storage + - Indy VDR for Indy ledger communication + - Indy Shared Libraries for AnonCreds +- "Indy" - The legacy configuration of ACA-Py, including: + - Indy SDK Wallet for secure storage + - Indy SDK Ledger for Indy ledger communication + - Indy SDK for AnonCreds + +These two image variants are largely distinguished by providers for Indy Network +and AnonCreds support. The Standard variant is recommended for new projects. +Migration from an Indy based image (whether the new Indy image variant or the +original BC Gov images) to the Standard image is outside of the scope of this +document. + +The ACA-Py images built by this project are tagged to indicate which of the +above variants it is. Other tags may also be generated for use by developers. + +Below is a table of all generated images and their tags: + +Tag | Variant | Example | Description | +------------------------|----------|--------------------------|-------------------------------------------------------------------------------------------------| +py3.6-X.Y.Z | Standard | py3.6-0.7.4 | Standard image variant built on Python 3.6 for ACA-Py version X.Y.Z | +py3.7-X.Y.Z | Standard | py3.7-0.7.4 | Standard image variant built on Python 3.7 for ACA-Py version X.Y.Z | +py3.8-X.Y.Z | Standard | py3.8-0.7.4 | Standard image variant built on Python 3.8 for ACA-Py version X.Y.Z | +py3.9-X.Y.Z | Standard | py3.9-0.7.4 | Standard image variant built on Python 3.9 for ACA-Py version X.Y.Z | +py3.10-X.Y.Z | Standard | py3.10-0.7.4 | Standard image variant built on Python 3.10 for ACA-Py version X.Y.Z | +py3.7-indy-A.B.C-X.Y.Z | Indy | py3.7-indy-1.16.0-0.7.4 | Standard image variant built on Python 3.7 for ACA-Py version X.Y.Z and Indy SDK Version A.B.C | +py3.8-indy-A.B.C-X.Y.Z | Indy | py3.8-indy-1.16.0-0.7.4 | Standard image variant built on Python 3.8 for ACA-Py version X.Y.Z and Indy SDK Version A.B.C | +py3.9-indy-A.B.C-X.Y.Z | Indy | py3.9-indy-1.16.0-0.7.4 | Standard image variant built on Python 3.9 for ACA-Py version X.Y.Z and Indy SDK Version A.B.C | +py3.10-indy-A.B.C-X.Y.Z | Indy | py3.10-indy-1.16.0-0.7.4 | Standard image variant built on Python 3.10 for ACA-Py version X.Y.Z and Indy SDK Version A.B.C | + +### Image Comparison + +There are several key differences that should be noted between the two image +variants and between the BC Gov ACA-Py images. + +- Standard Image + - Based on slim variant of Debian + - Does **NOT** include `libindy` + - Default user is `aries` + - Uses container's system python environment rather than `pyenv` + - Askar and Indy Shared libraries are installed as dependencies of ACA-Py through pip from pre-compiled binaries included in the python wrappers + - Built from repo contents +- Indy Image + - Based on slim variant of Debian + - Built from multi-stage build step (`indy-base` in the Dockerfile) which includes Indy dependencies; this could be replaced with an explicit `indy-python` image from the Indy SDK repo + - Includes `libindy` but does **NOT** include the Indy CLI + - Default user is `indy` + - Uses container's system python environment rather than `pyenv` + - Askar and Indy Shared libraries are installed as dependencies of ACA-Py through pip from pre-compiled binaries included in the python wrappers + - Built from repo contents + - Includes Indy postgres storage plugin +- `bcgovimages/aries-cloudagent` + - (Usually) based on Ubuntu + - Based on `von-image` + - Default user is `indy` + - Includes `libindy` and Indy CLI + - Uses `pyenv` + - Askar and Indy Shared libraries built from source + - Built from ACA-Py python package uploaded to PyPI + - Includes Indy postgres storage plugin + +## Github Actions + +- Tests (`.github/workflows/tests.yml`) - A reusable workflow that runs tests + for the Standard ACA-Py variant for a given python version. +- Tests (Indy) (`.github/workflows/tests-indy.yml`) - A reusable workflow that + runs tests for the Indy ACA-Py variant for a given python and indy version. +- PR Tests (`.github/workflows/pr-tests.yml`) - Run on pull requests; runs tests + for the Standard and Indy ACA-Py variants for a "default" python version. + Check this workflow for the current default python and Indy versions in use. +- Nightly Tests (`.github/workflows/nightly-tests.yml`) - Run nightly; runs + tests for the Standard and Indy ACA-Py variants for all currently supported + python versions. Check this workflow for the set of currently supported + versions and Indy version(s) in use. +- Publish (`.github/workflows/publish.yml`) - Run on new release published or + when manually triggered; builds and pushes the Standard ACA-Py variant to the + Github Container Registry. +- Publish (Indy) (`.github/workflows/publish-indy.yml`) - Run on new release + published or when manually triggered; builds and pushes the Indy ACA-Py + variant to the Github Container Registry. +- Integration Tests (`.github/workflows/integrationtests.yml`) - Run on pull + requests (to the hyperledger fork only); runs BDD integration tests. +- Black Format (`.github/workflows/blackformat.yml`) - Run on pull requests; + checks formatting of files modified by the PR. +- CodeQL (`.github/workflows/codeql.yml`) - Run on pull requests; performs + CodeQL analysis. +- Python Publish (`.github/workflows/pythonpublish.yml`) - Run on release + created; publishes ACA-Py python package to PyPI. +- PIP Audit (`.github/workflows/pipaudit.yml`) - Run when manually triggered; + performs pip audit. diff --git a/DIDMethods.md b/DIDMethods.md new file mode 100644 index 0000000000..179103aba8 --- /dev/null +++ b/DIDMethods.md @@ -0,0 +1,45 @@ +# DID methods in ACA-Py +Decentralized Identifiers, or DIDs, are URIs that point to documents that describe cryptographic primitives and protocols used in decentralized identity management. +DIDs include methods that describe where and how documents can be retrieved. +DID methods support specific types of keys and may or may not require the holder to specify the DID itself. + +ACA-Py provides a `DIDMethods` registry holding all the DID methods supported for storage in a wallet + +> :warning: Askar and InMemory are the only wallets supporting this registry. + +## Registering a DID method +By default, ACA-Py supports `did:key` and `did:sov`. +Plugins can register DID additional methods to make them available to holders. +Here's a snippet adding support for `did:web` to the registry from a plugin `setup` method. + +```python= +WEB = DIDMethod( + name="web", + key_types=[ED25519, BLS12381G2], + rotation=True, + holder_defined_did=HolderDefinedDid.REQUIRED # did:web is not derived from key material but from a user-provided respository name +) + +async def setup(context: InjectionContext): + methods = context.inject(DIDMethods) + methods.register(WEB) +``` + +## Creating a DID + +`POST /wallet/did/create` can be provided with parameters for any registered DID method. Here's a follow-up to the +`did:web` method example: + +```json= +{ + "method": "web", + "options": { + "did": "did:web:doma.in", + "key_type": "ed25519" + } +} +``` + +## Resolving DIDs + +For specifics on how DIDs are resolved in ACA-Py, see: [DID Resolution](DIDResolution.md). diff --git a/Mediation.md b/Mediation.md index 425996e16e..c301a39e77 100644 --- a/Mediation.md +++ b/Mediation.md @@ -15,6 +15,7 @@ * `--open-mediation` - Instructs mediators to automatically grant all incoming mediation requests. * `--mediator-invitation` - Receive invitation, send mediation request and set as default mediator. +* `--mediator-connections-invite` - Connect to mediator through a connection invitation. If not specified, connect using an OOB invitation. * `--default-mediator-id` - Set pre-existing mediator as default mediator. * `--clear-default-mediator` - Clear the stored default mediator. @@ -72,4 +73,4 @@ See [Aries RFC 0211: Coordinate Mediation Protocol](https://github.com/hyperledg ## Using a Mediator After establishing a connection with a mediator also having mediation granted, you can use that mediator id for future did_comm connections. - When creating, receiving or accepting a invitation intended to be Mediated, you provide `mediation_id` with the desired mediator id. if using a single mediator for all future connections, You can set a default mediation id. If no mediation_id is provided the default mediation id will be used instead. \ No newline at end of file + When creating, receiving or accepting a invitation intended to be Mediated, you provide `mediation_id` with the desired mediator id. if using a single mediator for all future connections, You can set a default mediation id. If no mediation_id is provided the default mediation id will be used instead. diff --git a/aries_cloudagent/admin/server.py b/aries_cloudagent/admin/server.py index c7ab79334d..def29588eb 100644 --- a/aries_cloudagent/admin/server.py +++ b/aries_cloudagent/admin/server.py @@ -433,7 +433,7 @@ async def setup_context(request: web.Request, handler): ) server_routes = [ - web.get("/", self.redirect_handler, allow_head=False), + web.get("/", self.redirect_handler, allow_head=True), web.get("/plugins", self.plugins_handler, allow_head=False), web.get("/status", self.status_handler, allow_head=False), web.get("/status/config", self.config_handler, allow_head=False), @@ -491,7 +491,7 @@ async def start(self) -> None: def sort_dict(raw: dict) -> dict: """Order (JSON, string keys) dict asciibetically by key, recursively.""" - for (k, v) in raw.items(): + for k, v in raw.items(): if isinstance(v, dict): raw[k] = sort_dict(v) return dict(sorted([item for item in raw.items()], key=lambda x: x[0])) diff --git a/aries_cloudagent/askar/didcomm/v2.py b/aries_cloudagent/askar/didcomm/v2.py index c74b5cd9e7..7d89a3ec9c 100644 --- a/aries_cloudagent/askar/didcomm/v2.py +++ b/aries_cloudagent/askar/didcomm/v2.py @@ -33,7 +33,7 @@ def ecdh_es_encrypt(to_verkeys: Mapping[str, Key], message: bytes) -> bytes: except AskarError: raise DidcommEnvelopeError("Error creating content encryption key") - for (kid, recip_key) in to_verkeys.items(): + for kid, recip_key in to_verkeys.items(): try: epk = Key.generate(recip_key.algorithm, ephemeral=True) except AskarError: @@ -145,7 +145,7 @@ def ecdh_1pu_encrypt( apu = b64url(sender_kid) apv = [] - for (kid, recip_key) in to_verkeys.items(): + for kid, recip_key in to_verkeys.items(): if agree_alg: if agree_alg != recip_key.algorithm: raise DidcommEnvelopeError("Recipient key types must be consistent") @@ -173,7 +173,7 @@ def ecdh_1pu_encrypt( raise DidcommEnvelopeError("Error encrypting message payload") wrapper.set_payload(payload.ciphertext, payload.nonce, payload.tag) - for (kid, recip_key) in to_verkeys.items(): + for kid, recip_key in to_verkeys.items(): enc_key = ecdh.Ecdh1PU(alg_id, apu, apv).sender_wrap_key( wrap_alg, epk, sender_key, recip_key, cek, cc_tag=payload.tag ) diff --git a/aries_cloudagent/config/argparse.py b/aries_cloudagent/config/argparse.py index 259d6b9881..2d32f54830 100644 --- a/aries_cloudagent/config/argparse.py +++ b/aries_cloudagent/config/argparse.py @@ -643,6 +643,14 @@ def add_arguments(self, parser: ArgumentParser): "resolver instance." ), ) + parser.add_argument( + "--universal-resolver-bearer-token", + type=str, + nargs="?", + metavar="", + env_var="ACAPY_UNIVERSAL_RESOLVER_BEARER_TOKEN", + help="Bearer token if universal resolver instance requires authentication.", + ), def get_settings(self, args: Namespace) -> dict: """Extract general settings.""" @@ -688,12 +696,21 @@ def get_settings(self, args: Namespace) -> dict: "--universal-resolver-regex cannot be used without --universal-resolver" ) + if args.universal_resolver_bearer_token and not args.universal_resolver: + raise ArgsParseError( + "--universal-resolver-bearer-token " + + "cannot be used without --universal-resolver" + ) + if args.universal_resolver: settings["resolver.universal"] = args.universal_resolver if args.universal_resolver_regex: settings["resolver.universal.supported"] = args.universal_resolver_regex + if args.universal_resolver_bearer_token: + settings["resolver.universal.token"] = args.universal_resolver_bearer_token + return settings @@ -1039,7 +1056,17 @@ def add_arguments(self, parser: ArgumentParser): action="store_true", env_var="ACAPY_PUBLIC_INVITES", help=( - "Send invitations out, and receive connection requests, " + "Send invitations out using the public DID for the agent, " + "and receive connection requests solicited by invitations " + "which use the public DID. Default: false." + ), + ) + parser.add_argument( + "--requests-through-public-did", + action="store_true", + env_var="ACAPY_REQUESTS_THROUGH_PUBLIC_DID", + help=( + "Allow agent to receive unsolicited connection requests, " "using the public DID for the agent. Default: false." ), ) @@ -1134,6 +1161,13 @@ def get_settings(self, args: Namespace) -> dict: settings["monitor_forward"] = args.monitor_forward if args.public_invites: settings["public_invites"] = True + if args.requests_through_public_did: + if not args.public_invites: + raise ArgsParseError( + "--public-invites is required to use " + "--requests-through-public-did" + ) + settings["requests_through_public_did"] = True if args.timing: settings["timing.enabled"] = True if args.timing_log: diff --git a/aries_cloudagent/config/tests/test_wallet.py b/aries_cloudagent/config/tests/test_wallet.py index 9d514bc735..3ff2e5a8b8 100644 --- a/aries_cloudagent/config/tests/test_wallet.py +++ b/aries_cloudagent/config/tests/test_wallet.py @@ -187,7 +187,6 @@ async def test_wallet_config_bad_seed_x(self): ) as mock_seed_to_did, async_mock.patch.object( test_module, "add_or_update_version_to_storage", async_mock.CoroutineMock() ): - with self.assertRaises(test_module.ConfigError): await test_module.wallet_config(self.context, provision=True) diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index 4308705c31..815843b4c0 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -150,7 +150,7 @@ async def create_did_document( routing_keys = [*routing_keys, *mediator_routing_keys] svc_endpoints = [mediation_record.endpoint] - for (endpoint_index, svc_endpoint) in enumerate(svc_endpoints or []): + for endpoint_index, svc_endpoint in enumerate(svc_endpoints or []): endpoint_ident = "indy" if endpoint_index == 0 else f"indy{endpoint_index}" service = Service( did_info.did, @@ -269,16 +269,18 @@ async def resolve_invitation( endpoint = first_didcomm_service.service_endpoint recipient_keys: List[VerificationMethod] = [ - doc.dereference(url) for url in first_didcomm_service.recipient_keys + await resolver.dereference(self._profile, url, document=doc) + for url in first_didcomm_service.recipient_keys ] routing_keys: List[VerificationMethod] = [ - doc.dereference(url) for url in first_didcomm_service.routing_keys + await resolver.dereference(self._profile, url, document=doc) + for url in first_didcomm_service.routing_keys ] for key in [*recipient_keys, *routing_keys]: if not isinstance(key, self.SUPPORTED_KEY_TYPES): raise BaseConnectionManagerError( - f"Key type {key.type} is not supported" + f"Key type {type(key).__name__} is not supported" ) return ( diff --git a/aries_cloudagent/connections/models/conn_record.py b/aries_cloudagent/connections/models/conn_record.py index eb0342970a..ca9e21b07f 100644 --- a/aries_cloudagent/connections/models/conn_record.py +++ b/aries_cloudagent/connections/models/conn_record.py @@ -677,11 +677,7 @@ class Meta: required=False, description="Routing state of connection", validate=validate.OneOf( - [ - getattr(ConnRecord, m) - for m in vars(ConnRecord) - if m.startswith("ROUTING_STATE_") - ] + ConnRecord.get_attributes_by_prefix("ROUTING_STATE_", walk_mro=False) ), example=ConnRecord.ROUTING_STATE_ACTIVE, ) @@ -690,11 +686,7 @@ class Meta: description="Connection acceptance: manual or auto", example=ConnRecord.ACCEPT_AUTO, validate=validate.OneOf( - [ - getattr(ConnRecord, a) - for a in vars(ConnRecord) - if a.startswith("ACCEPT_") - ] + ConnRecord.get_attributes_by_prefix("ACCEPT_", walk_mro=False) ), ) error_msg = fields.Str( @@ -707,11 +699,7 @@ class Meta: description="Invitation mode", example=ConnRecord.INVITATION_MODE_ONCE, validate=validate.OneOf( - [ - getattr(ConnRecord, i) - for i in vars(ConnRecord) - if i.startswith("INVITATION_MODE_") - ] + ConnRecord.get_attributes_by_prefix("INVITATION_MODE_", walk_mro=False) ), ) alias = fields.Str( diff --git a/aries_cloudagent/connections/models/diddoc/diddoc.py b/aries_cloudagent/connections/models/diddoc/diddoc.py index d1b8bcb0cc..ea47866487 100644 --- a/aries_cloudagent/connections/models/diddoc/diddoc.py +++ b/aries_cloudagent/connections/models/diddoc/diddoc.py @@ -176,7 +176,6 @@ def add_service_pubkeys( rv = [] for tag in [tags] if isinstance(tags, str) else list(tags): - for svc_key in service.get(tag, {}): canon_key = canon_ref(self.did, svc_key) pubkey = None diff --git a/aries_cloudagent/connections/models/diddoc/tests/test_diddoc.py b/aries_cloudagent/connections/models/diddoc/tests/test_diddoc.py index be002ddc27..141a0e6051 100644 --- a/aries_cloudagent/connections/models/diddoc/tests/test_diddoc.py +++ b/aries_cloudagent/connections/models/diddoc/tests/test_diddoc.py @@ -26,7 +26,6 @@ class TestDIDDoc(AsyncTestCase): async def test_basic(self): - # One authn key by reference dd_in = { "@context": "https://w3id.org/did/v1", diff --git a/aries_cloudagent/core/conductor.py b/aries_cloudagent/core/conductor.py index 9dfe8f8ab2..1f09dbc13a 100644 --- a/aries_cloudagent/core/conductor.py +++ b/aries_cloudagent/core/conductor.py @@ -455,11 +455,9 @@ async def start(self) -> None: auto_accept=True, ) async with self.root_profile.session() as session: - await ( - MediationInviteStore( - session.context.inject(BaseStorage) - ).mark_default_invite_as_used() - ) + await MediationInviteStore( + session.context.inject(BaseStorage) + ).mark_default_invite_as_used() await record.metadata_set( session, MediationManager.SEND_REQ_AFTER_CONNECTION, True diff --git a/aries_cloudagent/core/in_memory/didcomm/tests/test_1pu.py b/aries_cloudagent/core/in_memory/didcomm/tests/test_1pu.py index f12bdc25d0..e57b7bddda 100644 --- a/aries_cloudagent/core/in_memory/didcomm/tests/test_1pu.py +++ b/aries_cloudagent/core/in_memory/didcomm/tests/test_1pu.py @@ -6,7 +6,6 @@ def test_1pu_hex_example(): - # Previously randomly generated 3 sets of keys aliceSecretKey = "23832cbef38641b8754a35f1f79bbcbc248e09ac93b01c2eaf12474f2ac406b6" alicePublicKey = "04fd4ca9eb7954a03517ac8249e6070aa3112e582f596b10f0d45d757b56d5dc0395a7d207d06503a4d6ad6e2ad3a1fd8cc233c072c0dc0f32213deb712c32cbdf" @@ -41,7 +40,6 @@ def test_1pu_hex_example(): # Example key exchange in https://tools.ietf.org/id/draft-madden-jose-ecdh-1pu-03.html#rfc.appendix.A def test_1pu_appendix_example(): - # Convert the three JWK keys into hex encoded byte format # Alice Key @@ -106,7 +104,6 @@ def test_1pu_appendix_example(): def main(): - test_1pu_hex_example() test_1pu_appendix_example() diff --git a/aries_cloudagent/core/in_memory/didcomm/tests/test_ecdh.py b/aries_cloudagent/core/in_memory/didcomm/tests/test_ecdh.py index 16f15754d9..943456c7b8 100644 --- a/aries_cloudagent/core/in_memory/didcomm/tests/test_ecdh.py +++ b/aries_cloudagent/core/in_memory/didcomm/tests/test_ecdh.py @@ -2,9 +2,9 @@ from ..derive_ecdh import * + # Generate the same shared secret from imported generated keys def test_ecdh_derive_shared_secret(): - # Import keys for two participating users aliceSecretKey = "23832cbef38641b8754a35f1f79bbcbc248e09ac93b01c2eaf12474f2ac406b6" alicePublicKey = "04fd4ca9eb7954a03517ac8249e6070aa3112e582f596b10f0d45d757b56d5dc0395a7d207d06503a4d6ad6e2ad3a1fd8cc233c072c0dc0f32213deb712c32cbdf" @@ -23,7 +23,6 @@ def test_ecdh_derive_shared_secret(): # Generate the same shared secret from random keys def test_ecdh_derive_shared_secret_random(): - # Generate random keys for the two participating users aliceSecretKey = SigningKey.generate(curve=NIST256p) alice = ECDH(curve=NIST256p) @@ -46,7 +45,6 @@ def test_ecdh_derive_shared_secret_random(): # Test the entire key generation flow, DeriveECDHSecret() into ConcatKDF() def test_ecdh_generate_key(): - aliceSecretKey = "23832cbef38641b8754a35f1f79bbcbc248e09ac93b01c2eaf12474f2ac406b6" alicePublicKey = "04fd4ca9eb7954a03517ac8249e6070aa3112e582f596b10f0d45d757b56d5dc0395a7d207d06503a4d6ad6e2ad3a1fd8cc233c072c0dc0f32213deb712c32cbdf" @@ -78,7 +76,6 @@ def test_ecdh_generate_key(): # Test the entire key generation flow, derive_shared_secret() into concat_kdf() def test_ecdh_generate_key_random(): - aliceSecretKey = SigningKey.generate(curve=NIST256p) alice = ECDH(curve=NIST256p) alice.load_private_key(aliceSecretKey) @@ -113,7 +110,6 @@ def test_ecdh_generate_key_random(): def main(): - test_ecdh_derive_shared_secret() test_ecdh_derive_shared_secret_random() test_ecdh_generate_key() diff --git a/aries_cloudagent/core/tests/test_conductor.py b/aries_cloudagent/core/tests/test_conductor.py index c105a66307..d4e73b4a8f 100644 --- a/aries_cloudagent/core/tests/test_conductor.py +++ b/aries_cloudagent/core/tests/test_conductor.py @@ -36,7 +36,7 @@ from ...utils.stats import Collector from ...version import __version__ from ...wallet.base import BaseWallet -from ...wallet.did_method import SOV +from ...wallet.did_method import SOV, DIDMethods from ...wallet.key_type import ED25519 from .. import conductor as test_module @@ -87,6 +87,7 @@ async def build_context(self) -> InjectionContext: context.injector.bind_instance(ProfileManager, InMemoryProfileManager()) context.injector.bind_instance(ProtocolRegistry, ProtocolRegistry()) context.injector.bind_instance(BaseWireFormat, self.wire_format) + context.injector.bind_instance(DIDMethods, DIDMethods()) context.injector.bind_instance(DIDResolver, DIDResolver([])) context.injector.bind_instance(EventBus, MockEventBus()) return context @@ -221,7 +222,6 @@ async def test_stats(self): ) as mock_outbound_mgr, async_mock.patch.object( test_module, "LoggingConfigurator", autospec=True ) as mock_logger: - mock_inbound_mgr.return_value.sessions = ["dummy"] mock_outbound_mgr.return_value.outbound_buffer = [ async_mock.MagicMock(state=QueuedOutboundMessage.STATE_ENCODE), @@ -262,7 +262,6 @@ async def test_inbound_message_handler(self): with async_mock.patch.object( conductor.dispatcher, "queue_message", autospec=True ) as mock_dispatch_q: - message_body = "{}" receipt = MessageReceipt(direct_response_mode="snail mail") message = InboundMessage(message_body, receipt) diff --git a/aries_cloudagent/core/tests/test_oob_processor.py b/aries_cloudagent/core/tests/test_oob_processor.py index 5197daedbd..7c73a946ad 100644 --- a/aries_cloudagent/core/tests/test_oob_processor.py +++ b/aries_cloudagent/core/tests/test_oob_processor.py @@ -59,7 +59,6 @@ async def test_clean_finished_oob_record_no_multi_use_no_request_attach(self): "retrieve_by_tag_filter", async_mock.CoroutineMock(return_value=mock_oob), ) as mock_retrieve_oob: - await self.oob_processor.clean_finished_oob_record( self.profile, test_message ) @@ -88,7 +87,6 @@ async def test_clean_finished_oob_record_multi_use(self): "retrieve_by_tag_filter", async_mock.CoroutineMock(return_value=mock_oob), ) as mock_retrieve_oob: - await self.oob_processor.clean_finished_oob_record( self.profile, test_message ) @@ -143,7 +141,6 @@ async def test_find_oob_target_for_outbound_message(self): "retrieve_by_tag_filter", async_mock.CoroutineMock(return_value=mock_oob), ) as mock_retrieve_oob: - target = await self.oob_processor.find_oob_target_for_outbound_message( self.profile, outbound ) @@ -182,7 +179,6 @@ async def test_find_oob_target_for_outbound_message_oob_not_found(self): "retrieve_by_tag_filter", async_mock.CoroutineMock(side_effect=(StorageNotFoundError(),)), ) as mock_retrieve_oob: - target = await self.oob_processor.find_oob_target_for_outbound_message( self.profile, outbound ) @@ -217,7 +213,6 @@ async def test_find_oob_target_for_outbound_message_update_service_thread(self): "retrieve_by_tag_filter", async_mock.CoroutineMock(return_value=mock_oob), ): - message = json.dumps({}) outbound = OutboundMessage(reply_thread_id="the-thid", payload=message) await self.oob_processor.find_oob_target_for_outbound_message( @@ -525,7 +520,6 @@ async def test_find_oob_record_for_inbound_message_attach_thread_id_not_in_list( async def test_find_oob_record_for_inbound_message_not_attach_thread_id_matching( self, ): - with async_mock.patch.object( OobRecord, "retrieve_by_tag_filter", @@ -639,7 +633,6 @@ async def test_find_oob_record_for_inbound_message_their_service_set_on_oob_reco async def test_find_oob_record_for_inbound_message_session_emit_delete( self, ): - with async_mock.patch.object( OobRecord, "retrieve_by_tag_filter", @@ -664,7 +657,6 @@ async def test_find_oob_record_for_inbound_message_session_emit_delete( async def test_find_oob_record_for_inbound_message_session_connectionless_save( self, ): - self.oob_record.connection_id = None with async_mock.patch.object( @@ -751,7 +743,6 @@ async def test_handle_message_connectionless(self): self.inbound_message_router.assert_called_once_with(self.profile, ANY, False) async def test_handle_message_unsupported_message_type(self): - with self.assertRaises(OobMessageProcessorError) as err: await self.oob_processor.handle_message( self.profile, [{"@type": "unsupported"}], async_mock.MagicMock() diff --git a/aries_cloudagent/did/tests/test_did_key_bls12381g1g2.py b/aries_cloudagent/did/tests/test_did_key_bls12381g1g2.py index f5ed77e64d..5877bed1c6 100644 --- a/aries_cloudagent/did/tests/test_did_key_bls12381g1g2.py +++ b/aries_cloudagent/did/tests/test_did_key_bls12381g1g2.py @@ -26,6 +26,7 @@ [b"\xee\x01", b58_to_bytes(TEST_BLS12381G1G2_BASE58_KEY)] ) + # The tests here are a bit quirky because g1g2 is a concatenation of g1 and g2 public key bytes # but it works with the already existing did key implementation. class TestDIDKey(TestCase): diff --git a/aries_cloudagent/indy/models/pres_preview.py b/aries_cloudagent/indy/models/pres_preview.py index 5f73978f33..0edb1cdb06 100644 --- a/aries_cloudagent/indy/models/pres_preview.py +++ b/aries_cloudagent/indy/models/pres_preview.py @@ -403,7 +403,7 @@ def non_revoc(cred_def_id: str) -> IndyNonRevocationInterval: }, } - for (reft, attr_spec) in attr_specs_names.items(): + for reft, attr_spec in attr_specs_names.items(): proof_req["requested_attributes"][ "{}_{}_uuid".format( len(proof_req["requested_attributes"]), canon(attr_spec["names"][0]) diff --git a/aries_cloudagent/indy/models/xform.py b/aries_cloudagent/indy/models/xform.py index 9e7d5e7601..afcb635fe2 100644 --- a/aries_cloudagent/indy/models/xform.py +++ b/aries_cloudagent/indy/models/xform.py @@ -30,7 +30,7 @@ async def indy_proof_req_preview2indy_requested_creds( "requested_predicates": {}, } - for (referent, req_item) in indy_proof_req["requested_attributes"].items(): + for referent, req_item in indy_proof_req["requested_attributes"].items(): credentials = await holder.get_credentials_for_presentation_request_by_referent( presentation_request=indy_proof_req, referents=(referent,), @@ -116,7 +116,7 @@ def indy_proof_req2non_revoc_intervals(indy_proof_req: dict): """Return non-revocation intervals by requested item referent in proof request.""" non_revoc_intervals = {} for req_item_type in ("requested_attributes", "requested_predicates"): - for (reft, req_item) in indy_proof_req[req_item_type].items(): + for reft, req_item in indy_proof_req[req_item_type].items(): interval = req_item.get( "non_revoked", indy_proof_req.get("non_revoked"), diff --git a/aries_cloudagent/indy/sdk/holder.py b/aries_cloudagent/indy/sdk/holder.py index 48cb2abac0..efb1a4f3ba 100644 --- a/aries_cloudagent/indy/sdk/holder.py +++ b/aries_cloudagent/indy/sdk/holder.py @@ -226,11 +226,13 @@ async def fetch(reft, limit): with IndyErrorHandler( "Error when constructing wallet credential query", IndyHolderError ): - search_handle = await ( - indy.anoncreds.prover_search_credentials_for_proof_req( - self.wallet.handle, - json.dumps(presentation_request), - json.dumps(extra_query), + search_handle = ( + await ( + indy.anoncreds.prover_search_credentials_for_proof_req( + self.wallet.handle, + json.dumps(presentation_request), + json.dumps(extra_query), + ) ) ) diff --git a/aries_cloudagent/indy/verifier.py b/aries_cloudagent/indy/verifier.py index f61ca829f2..9a83ce5b8f 100644 --- a/aries_cloudagent/indy/verifier.py +++ b/aries_cloudagent/indy/verifier.py @@ -59,12 +59,12 @@ def non_revoc_intervals(self, pres_req: dict, pres: dict, cred_defs: dict) -> li """ msgs = [] - for (req_proof_key, pres_key) in { + for req_proof_key, pres_key in { "revealed_attrs": "requested_attributes", "revealed_attr_groups": "requested_attributes", "predicates": "requested_predicates", }.items(): - for (uuid, spec) in pres["requested_proof"].get(req_proof_key, {}).items(): + for uuid, spec in pres["requested_proof"].get(req_proof_key, {}).items(): if ( "revocation" not in cred_defs[ @@ -132,7 +132,7 @@ async def check_timestamps( LOGGER.debug(f">>> got non-revoc intervals: {non_revoc_intervals}") # timestamp for irrevocable credential cred_defs = [] - for (index, ident) in enumerate(pres["identifiers"]): + for index, ident in enumerate(pres["identifiers"]): LOGGER.debug(f">>> got (index, ident): ({index},{ident})") cred_def_id = ident["cred_def_id"] multitenant_mgr = profile.inject_or(BaseMultitenantManager) @@ -184,7 +184,7 @@ async def check_timestamps( revealed_groups = pres["requested_proof"].get("revealed_attr_groups", {}) self_attested = pres["requested_proof"].get("self_attested_attrs", {}) preds = pres["requested_proof"].get("predicates", {}) - for (uuid, req_attr) in pres_req["requested_attributes"].items(): + for uuid, req_attr in pres_req["requested_attributes"].items(): if "name" in req_attr: if uuid in revealed_attrs: index = revealed_attrs[uuid]["sub_proof_index"] @@ -258,7 +258,7 @@ async def check_timestamps( f"{non_revoc_intervals[uuid]}" ) - for (uuid, req_pred) in pres_req["requested_predicates"].items(): + for uuid, req_pred in pres_req["requested_predicates"].items(): pred_spec = preds.get(uuid) if pred_spec is None or "sub_proof_index" not in pred_spec: raise ValueError( @@ -314,7 +314,7 @@ async def pre_verify(self, pres_req: dict, pres: dict) -> list: if "proof" not in pres: raise ValueError("Presentation missing 'proof'") - for (uuid, req_pred) in pres_req["requested_predicates"].items(): + for uuid, req_pred in pres_req["requested_predicates"].items(): try: canon_attr = canon(req_pred["name"]) matched = False @@ -340,7 +340,7 @@ async def pre_verify(self, pres_req: dict, pres: dict) -> list: unrevealed_attrs = pres["requested_proof"].get("unrevealed_attrs", {}) revealed_groups = pres["requested_proof"].get("revealed_attr_groups", {}) self_attested = pres["requested_proof"].get("self_attested_attrs", {}) - for (uuid, req_attr) in pres_req["requested_attributes"].items(): + for uuid, req_attr in pres_req["requested_attributes"].items(): if "name" in req_attr: if uuid in revealed_attrs: pres_req_attr_spec = {req_attr["name"]: revealed_attrs[uuid]} @@ -375,7 +375,7 @@ async def pre_verify(self, pres_req: dict, pres: dict) -> list: f"Request attribute missing 'name' and 'names': '{uuid}'" ) - for (attr, spec) in pres_req_attr_spec.items(): + for attr, spec in pres_req_attr_spec.items(): try: primary_enco = pres["proof"]["proofs"][spec["sub_proof_index"]][ "primary_proof" diff --git a/aries_cloudagent/ledger/indy.py b/aries_cloudagent/ledger/indy.py index de3e8d40fe..a127f8c028 100644 --- a/aries_cloudagent/ledger/indy.py +++ b/aries_cloudagent/ledger/indy.py @@ -389,6 +389,8 @@ async def _submit( if taa_accept: acceptance = await self.get_latest_txn_author_acceptance() if acceptance: + # flake8 and black 23.1.0 check collision fix + # fmt: off request_json = await ( indy.ledger.append_txn_author_agreement_acceptance_to_request( request_json, @@ -399,6 +401,7 @@ async def _submit( acceptance["time"], ) ) + # fmt: on if write_ledger: submit_op = indy.ledger.sign_and_submit_request( self.pool.handle, diff --git a/aries_cloudagent/ledger/merkel_validation/trie.py b/aries_cloudagent/ledger/merkel_validation/trie.py index 2c7acedf98..08eb958140 100644 --- a/aries_cloudagent/ledger/merkel_validation/trie.py +++ b/aries_cloudagent/ledger/merkel_validation/trie.py @@ -79,7 +79,7 @@ async def verify_spv_proof(expected_value, proof_nodes, serialized=True): json.loads(rlp_decode(decoded_node[1])[0].decode("utf-8")) ) == expected_value: return True - except (DecodingError): + except DecodingError: continue return False except Exception: diff --git a/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_manager.py b/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_manager.py index de629b2d78..572992a313 100644 --- a/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_manager.py +++ b/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_manager.py @@ -389,7 +389,10 @@ async def test_lookup_did_in_configured_ledgers_cached_prod_ledger(self): cache = InMemoryCache() await cache.set("did_ledger_id_resolver::Av63wJYM7xYR4AiygYq4c3", "test_prod_2") self.profile.context.injector.bind_instance(BaseCache, cache) - (ledger_id, ledger_inst,) = await self.manager.lookup_did_in_configured_ledgers( + ( + ledger_id, + ledger_inst, + ) = await self.manager.lookup_did_in_configured_ledgers( "Av63wJYM7xYR4AiygYq4c3", cache_did=True ) assert ledger_id == "test_prod_2" @@ -401,7 +404,10 @@ async def test_lookup_did_in_configured_ledgers_cached_non_prod_ledger(self): "did_ledger_id_resolver::Av63wJYM7xYR4AiygYq4c3", "test_non_prod_2", None ) self.profile.context.injector.bind_instance(BaseCache, cache) - (ledger_id, ledger_inst,) = await self.manager.lookup_did_in_configured_ledgers( + ( + ledger_id, + ledger_inst, + ) = await self.manager.lookup_did_in_configured_ledgers( "Av63wJYM7xYR4AiygYq4c3", cache_did=True ) assert ledger_id == "test_non_prod_2" diff --git a/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_vdr_manager.py b/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_vdr_manager.py index a09fb13983..86aa27a3d5 100644 --- a/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_vdr_manager.py +++ b/aries_cloudagent/ledger/multiple_ledger/tests/test_indy_vdr_manager.py @@ -443,7 +443,10 @@ async def test_lookup_did_in_configured_ledgers_cached_prod_ledger(self): cache = InMemoryCache() await cache.set("did_ledger_id_resolver::Av63wJYM7xYR4AiygYq4c3", "test_prod_1") self.profile.context.injector.bind_instance(BaseCache, cache) - (ledger_id, ledger_inst,) = await self.manager.lookup_did_in_configured_ledgers( + ( + ledger_id, + ledger_inst, + ) = await self.manager.lookup_did_in_configured_ledgers( "Av63wJYM7xYR4AiygYq4c3", cache_did=True ) assert ledger_id == "test_prod_1" @@ -455,7 +458,10 @@ async def test_lookup_did_in_configured_ledgers_cached_non_prod_ledger(self): "did_ledger_id_resolver::Av63wJYM7xYR4AiygYq4c3", "test_non_prod_2", None ) self.profile.context.injector.bind_instance(BaseCache, cache) - (ledger_id, ledger_inst,) = await self.manager.lookup_did_in_configured_ledgers( + ( + ledger_id, + ledger_inst, + ) = await self.manager.lookup_did_in_configured_ledgers( "Av63wJYM7xYR4AiygYq4c3", cache_did=True ) assert ledger_id == "test_non_prod_2" diff --git a/aries_cloudagent/ledger/tests/test_indy.py b/aries_cloudagent/ledger/tests/test_indy.py index 0b36b67452..fe9c8216ee 100644 --- a/aries_cloudagent/ledger/tests/test_indy.py +++ b/aries_cloudagent/ledger/tests/test_indy.py @@ -337,7 +337,6 @@ async def test_submit_signed_taa_accept( mock_create_config, mock_set_proto, ): - mock_append_taa.return_value = "{}" mock_sign_submit.return_value = '{"op": "REPLY"}' @@ -387,7 +386,6 @@ async def test_submit_unsigned( mock_create_config, mock_set_proto, ): - mock_did = async_mock.MagicMock() future = asyncio.Future() @@ -419,7 +417,6 @@ async def test_submit_unsigned_ledger_transaction_error( mock_create_config, mock_set_proto, ): - mock_did = async_mock.MagicMock() future = asyncio.Future() @@ -454,7 +451,6 @@ async def test_submit_rejected( mock_create_config, mock_set_proto, ): - mock_did = async_mock.MagicMock() future = asyncio.Future() @@ -690,7 +686,6 @@ async def test_send_schema_ledger_transaction_error_already_exists( mock_create_config, mock_set_proto, ): - mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) mock_is_ledger_read_only.return_value = False @@ -736,7 +731,6 @@ async def test_send_schema_ledger_read_only( mock_create_config, mock_set_proto, ): - mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) @@ -778,7 +772,6 @@ async def test_send_schema_issuer_error( mock_create_config, mock_set_proto, ): - mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) mock_is_ledger_read_only.return_value = False @@ -823,7 +816,6 @@ async def test_send_schema_ledger_transaction_error( mock_create_config, mock_set_proto, ): - mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) diff --git a/aries_cloudagent/ledger/tests/test_indy_vdr.py b/aries_cloudagent/ledger/tests/test_indy_vdr.py index 5f820aeeab..6a575a5737 100644 --- a/aries_cloudagent/ledger/tests/test_indy_vdr.py +++ b/aries_cloudagent/ledger/tests/test_indy_vdr.py @@ -28,7 +28,7 @@ @pytest.fixture() def ledger(): - profile = InMemoryProfile.test_profile() + profile = InMemoryProfile.test_profile(bind={DIDMethods: DIDMethods()}) ledger = IndyVdrLedger(IndyVdrLedgerPool("test-ledger"), profile) async def open(): diff --git a/aries_cloudagent/ledger/tests/test_routes.py b/aries_cloudagent/ledger/tests/test_routes.py index 95bce0d497..14853fc6bb 100644 --- a/aries_cloudagent/ledger/tests/test_routes.py +++ b/aries_cloudagent/ledger/tests/test_routes.py @@ -418,7 +418,6 @@ async def test_register_nym_create_transaction_for_endorser_storage_x(self): ) as mock_conn_rec_retrieve, async_mock.patch.object( test_module, "TransactionManager", async_mock.MagicMock() ) as mock_txn_mgr: - mock_txn_mgr.return_value = async_mock.MagicMock( create_record=async_mock.AsyncMock( side_effect=test_module.StorageError() diff --git a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py index 24b5a41bda..3a0cccb1b2 100644 --- a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py +++ b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py @@ -158,7 +158,6 @@ async def test_send_credential_definition_create_transaction_for_endorser_storag ) as mock_conn_rec_retrieve, async_mock.patch.object( test_module, "TransactionManager", async_mock.MagicMock() ) as mock_txn_mgr: - mock_conn_rec_retrieve.return_value = async_mock.MagicMock( metadata_get=async_mock.CoroutineMock( return_value={ diff --git a/aries_cloudagent/messaging/decorators/tests/test_decorator_set.py b/aries_cloudagent/messaging/decorators/tests/test_decorator_set.py index bed13f62c7..96e2c034af 100644 --- a/aries_cloudagent/messaging/decorators/tests/test_decorator_set.py +++ b/aries_cloudagent/messaging/decorators/tests/test_decorator_set.py @@ -33,7 +33,6 @@ def test_deco_set(self): assert all(k in deco_set.models for k in DEFAULT_MODELS) def test_extract(self): - decor_value = {} message = {"~decorator": decor_value, "one": "TWO"} @@ -47,7 +46,6 @@ def test_extract(self): assert remain == {"one": "TWO"} def test_dict(self): - decors = BaseDecoratorSet() decors["test"] = "TEST" assert decors["test"] == "TEST" @@ -55,7 +53,6 @@ def test_dict(self): assert result == {"~test": "TEST"} def test_decorator_model(self): - decor_value = {} message = {"~test": {"value": "TEST"}} @@ -70,7 +67,6 @@ def test_decorator_model(self): assert result == message def test_field_decorator(self): - decor_value = {} message = {"test~decorator": decor_value, "one": "TWO"} @@ -86,7 +82,6 @@ def test_field_decorator(self): assert "test~decorator" in decors.to_dict() def test_skip_decorator(self): - decor_value = {} message = {"handled~decorator": decor_value, "one": "TWO"} diff --git a/aries_cloudagent/messaging/decorators/tests/test_localization_decorator.py b/aries_cloudagent/messaging/decorators/tests/test_localization_decorator.py index 93e7e32d4b..6140f4e1f4 100644 --- a/aries_cloudagent/messaging/decorators/tests/test_localization_decorator.py +++ b/aries_cloudagent/messaging/decorators/tests/test_localization_decorator.py @@ -4,7 +4,6 @@ class TestThreadDecorator(TestCase): - LOCALE = "en-ca" LOCALIZABLE = ["a", "b"] CATALOGS = ["http://192.168.56.111/my-project/catalog.json"] diff --git a/aries_cloudagent/messaging/decorators/tests/test_thread_decorator.py b/aries_cloudagent/messaging/decorators/tests/test_thread_decorator.py index 898930233f..8ad7be4878 100644 --- a/aries_cloudagent/messaging/decorators/tests/test_thread_decorator.py +++ b/aries_cloudagent/messaging/decorators/tests/test_thread_decorator.py @@ -4,14 +4,12 @@ class TestThreadDecorator(TestCase): - thread_id = "tid-001" parent_id = "tid-000" sender_order = 1 received_orders = {"did": 2} def test_init(self): - decorator = ThreadDecorator( thid=self.thread_id, pthid=self.parent_id, @@ -24,7 +22,6 @@ def test_init(self): assert decorator.received_orders == self.received_orders def test_serialize_load(self): - decorator = ThreadDecorator( thid=self.thread_id, pthid=self.parent_id, diff --git a/aries_cloudagent/messaging/decorators/tests/test_trace_decorator.py b/aries_cloudagent/messaging/decorators/tests/test_trace_decorator.py index 2d2613fe96..b67da74477 100644 --- a/aries_cloudagent/messaging/decorators/tests/test_trace_decorator.py +++ b/aries_cloudagent/messaging/decorators/tests/test_trace_decorator.py @@ -4,7 +4,6 @@ class TestTraceDecorator(TestCase): - target_api = "http://example.com/api/trace/" full_thread_api = False target_msg = TRACE_MESSAGE_TARGET @@ -20,7 +19,6 @@ class TestTraceDecorator(TestCase): outcome = "OK ..." def test_init_api(self): - decorator = TraceDecorator( target=self.target_api, full_thread=self.full_thread_api, @@ -29,7 +27,6 @@ def test_init_api(self): assert decorator.full_thread == self.full_thread_api def test_init_message(self): - x_msg_id = self.msg_id x_thread_id = self.thread_id x_trace_report = TraceReport( @@ -64,7 +61,6 @@ def test_init_message(self): assert trace_report.outcome == self.outcome def test_serialize_load(self): - x_msg_id = self.msg_id x_thread_id = self.thread_id x_trace_report = TraceReport( diff --git a/aries_cloudagent/messaging/jsonld/routes.py b/aries_cloudagent/messaging/jsonld/routes.py index f0ff1b8c29..f89db9752a 100644 --- a/aries_cloudagent/messaging/jsonld/routes.py +++ b/aries_cloudagent/messaging/jsonld/routes.py @@ -5,7 +5,6 @@ from marshmallow import INCLUDE, Schema, fields from pydid.verification_method import ( Ed25519VerificationKey2018, - KnownVerificationMethods, ) from ...admin.request_context import AdminRequestContext @@ -85,7 +84,7 @@ async def sign(request: web.BaseRequest): session, doc.get("credential"), doc.get("options"), body.get("verkey") ) response["signed_doc"] = doc_with_proof - except (BaseJSONLDMessagingError) as err: + except BaseJSONLDMessagingError as err: response["error"] = str(err) except (WalletError, InjectionError): raise web.HTTPForbidden(reason="No wallet available") @@ -148,7 +147,6 @@ async def verify(request: web.BaseRequest): vmethod = await resolver.dereference( profile, doc["proof"]["verificationMethod"], - cls=KnownVerificationMethods, ) if not isinstance(vmethod, SUPPORTED_VERIFICATION_METHOD_TYPES): diff --git a/aries_cloudagent/messaging/jsonld/tests/test_routes.py b/aries_cloudagent/messaging/jsonld/tests/test_routes.py index ebddf2b9f3..ec41daf578 100644 --- a/aries_cloudagent/messaging/jsonld/tests/test_routes.py +++ b/aries_cloudagent/messaging/jsonld/tests/test_routes.py @@ -14,7 +14,7 @@ from ....resolver.did_resolver import DIDResolver from ....vc.ld_proofs.document_loader import DocumentLoader from ....wallet.base import BaseWallet -from ....wallet.did_method import SOV +from ....wallet.did_method import SOV, DIDMethods from ....wallet.error import WalletError from ....wallet.key_type import ED25519 from ..error import ( @@ -234,22 +234,22 @@ async def test_verify_bad_ver_meth_deref_req_error( assert "error" in mock_response.call_args[0][0] -@pytest.mark.asyncio -async def test_verify_bad_ver_meth_not_ver_meth( - mock_resolver, mock_verify_request, mock_response, request_body -): - request_body["doc"]["proof"][ - "verificationMethod" - ] = "did:example:1234abcd#did-communication" - await test_module.verify(mock_verify_request(request_body)) - assert "error" in mock_response.call_args[0][0] - - +@pytest.mark.parametrize( + "vmethod", + [ + "did:example:1234abcd#key-2", + "did:example:1234abcd#did-communication", + ], +) @pytest.mark.asyncio async def test_verify_bad_vmethod_unsupported( - mock_resolver, mock_verify_request, mock_response, request_body + mock_resolver, + mock_verify_request, + mock_response, + request_body, + vmethod, ): - request_body["doc"]["proof"]["verificationMethod"] = "did:example:1234abcd#key-2" + request_body["doc"]["proof"]["verificationMethod"] = vmethod with pytest.raises(web.HTTPBadRequest): await test_module.verify(mock_verify_request(request_body)) @@ -274,6 +274,7 @@ async def setUp(self): self.context.profile.context.injector.bind_instance( DocumentLoader, custom_document_loader ) + self.context.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.did_info = await (await self.context.session()).wallet.create_local_did( SOV, ED25519 ) diff --git a/aries_cloudagent/messaging/models/base_record.py b/aries_cloudagent/messaging/models/base_record.py index da1f7914f1..c696cf6771 100644 --- a/aries_cloudagent/messaging/models/base_record.py +++ b/aries_cloudagent/messaging/models/base_record.py @@ -81,6 +81,7 @@ class Meta: EVENT_NAMESPACE: str = "acapy::record" LOG_STATE_FLAG = None TAG_NAMES = {"state"} + STATE_DELETED = "deleted" def __init__( self, @@ -420,7 +421,7 @@ async def delete_record(self, session: ProfileSession): storage = session.inject(BaseStorage) if self.state: self._previous_state = self.state - self.state = "deleted" + self.state = BaseRecord.STATE_DELETED await self.emit_event(session, self.serialize()) await storage.delete_record(self.storage_record) @@ -497,6 +498,24 @@ def __eq__(self, other: Any) -> bool: return self.value == other.value and self.tags == other.tags return False + @classmethod + def get_attributes_by_prefix(cls, prefix: str, walk_mro: bool = True): + """ + List all values for attributes with common prefix. + + Args: + prefix: Common prefix to look for + walk_mro: Walk MRO to find attributes inherited from superclasses + """ + + bases = cls.__mro__ if walk_mro else [cls] + return [ + vars(base)[name] + for base in bases + for name in vars(base) + if name.startswith(prefix) + ] + class BaseExchangeRecord(BaseRecord): """Represents a base record with event tracing capability.""" diff --git a/aries_cloudagent/messaging/schemas/tests/test_routes.py b/aries_cloudagent/messaging/schemas/tests/test_routes.py index 91a826d628..f5347a6b2b 100644 --- a/aries_cloudagent/messaging/schemas/tests/test_routes.py +++ b/aries_cloudagent/messaging/schemas/tests/test_routes.py @@ -157,7 +157,6 @@ async def test_send_schema_create_transaction_for_endorser_storage_x(self): ) as mock_conn_rec_retrieve, async_mock.patch.object( test_module, "TransactionManager", async_mock.MagicMock() ) as mock_txn_mgr: - mock_txn_mgr.return_value = async_mock.MagicMock( create_record=async_mock.CoroutineMock( side_effect=test_module.StorageError() diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 6253c6b304..5af2a2d7be 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -23,60 +23,41 @@ class StrOrDictField(Field): """URI or Dict field for Marshmallow.""" - def _serialize(self, value, attr, obj, **kwargs): - return value - def _deserialize(self, value, attr, data, **kwargs): - if isinstance(value, (str, dict)): - return value - else: + if not isinstance(value, (str, dict)): raise ValidationError("Field should be str or dict") + return super()._deserialize(value, attr, data, **kwargs) class StrOrNumberField(Field): """String or Number field for Marshmallow.""" - def _serialize(self, value, attr, obj, **kwargs): - return value - def _deserialize(self, value, attr, data, **kwargs): - if isinstance(value, (str, float, int)): - return value - else: + if not isinstance(value, (str, float, int)): raise ValidationError("Field should be str or int or float") + return super()._deserialize(value, attr, data, **kwargs) class DictOrDictListField(Field): """Dict or Dict List field for Marshmallow.""" - def _serialize(self, value, attr, obj, **kwargs): - return value - def _deserialize(self, value, attr, data, **kwargs): - # dict - if isinstance(value, dict): - return value - # list of dicts - elif isinstance(value, list) and all(isinstance(item, dict) for item in value): - return value - else: - raise ValidationError("Field should be dict or list of dicts") + if not isinstance(value, dict): + if not isinstance(value, list) or not all( + isinstance(item, dict) for item in value + ): + raise ValidationError("Field should be dict or list of dicts") + return super()._deserialize(value, attr, data, **kwargs) class UriOrDictField(StrOrDictField): """URI or Dict field for Marshmallow.""" - def __init__(self, *args, **kwargs): - """Initialize new UriOrDictField instance.""" - super().__init__(*args, **kwargs) - - # Insert validation into self.validators so that multiple errors can be stored. - self.validators.insert(0, self._uri_validator) - - def _uri_validator(self, value): - # Check if URI when + def _deserialize(self, value, attr, data, **kwargs): if isinstance(value, str): - return Uri()(value) + # Check regex + Uri()(value) + return super()._deserialize(value, attr, data, **kwargs) class IntEpoch(Range): @@ -775,7 +756,7 @@ def __call__(self, value): except ValidationError: raise ValidationError( f"credential subject id {value[0]} must be URI" - ) + ) from None return value diff --git a/aries_cloudagent/multitenant/base.py b/aries_cloudagent/multitenant/base.py index ae5f5c60bd..9e242d3351 100644 --- a/aries_cloudagent/multitenant/base.py +++ b/aries_cloudagent/multitenant/base.py @@ -378,7 +378,7 @@ async def _get_wallet_by_key(self, recipient_key: str) -> Optional[WalletRecord] ) return wallet - except (RouteNotFoundError): + except RouteNotFoundError: pass async def get_profile_for_key( diff --git a/aries_cloudagent/multitenant/route_manager.py b/aries_cloudagent/multitenant/route_manager.py index ff8f1d37e7..d430aa0c51 100644 --- a/aries_cloudagent/multitenant/route_manager.py +++ b/aries_cloudagent/multitenant/route_manager.py @@ -66,7 +66,7 @@ async def _route_for_key( # If no error is thrown, it means there is already a record return None - except (StorageNotFoundError): + except StorageNotFoundError: pass await routing_mgr.create_route_record( diff --git a/aries_cloudagent/multitenant/tests/test_base.py b/aries_cloudagent/multitenant/tests/test_base.py index a58cbc9375..c302b44813 100644 --- a/aries_cloudagent/multitenant/tests/test_base.py +++ b/aries_cloudagent/multitenant/tests/test_base.py @@ -209,7 +209,6 @@ async def test_create_wallet_fails_if_wallet_name_exists(self): ) async def test_create_wallet_saves_wallet_record_creates_profile(self): - mock_route_manager = async_mock.MagicMock() mock_route_manager.route_public_did = async_mock.CoroutineMock() self.context.injector.bind_instance(RouteManager, mock_route_manager) diff --git a/aries_cloudagent/protocols/actionmenu/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/actionmenu/v1_0/tests/test_routes.py index 48148e5ff9..f766d9a77f 100644 --- a/aries_cloudagent/protocols/actionmenu/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/actionmenu/v1_0/tests/test_routes.py @@ -78,7 +78,6 @@ async def test_actionmenu_perform(self): ) as mock_perform, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_conn_record.retrieve_by_id = async_mock.CoroutineMock() res = await test_module.actionmenu_perform(self.request) @@ -97,7 +96,6 @@ async def test_actionmenu_perform_no_conn_record(self): ) as mock_conn_record, async_mock.patch.object( test_module, "Perform", autospec=True ) as mock_perform: - # Emulate storage not found (bad connection id) mock_conn_record.retrieve_by_id = async_mock.CoroutineMock( side_effect=StorageNotFoundError @@ -115,7 +113,6 @@ async def test_actionmenu_perform_conn_not_ready(self): ) as mock_conn_record, async_mock.patch.object( test_module, "Perform", autospec=True ) as mock_perform: - # Emulate connection not ready mock_conn_record.retrieve_by_id = async_mock.CoroutineMock() mock_conn_record.retrieve_by_id.return_value.is_ready = False @@ -134,7 +131,6 @@ async def test_actionmenu_request(self): ) as menu_request, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_conn_record.retrieve_by_id = async_mock.CoroutineMock() res = await test_module.actionmenu_request(self.request) @@ -153,7 +149,6 @@ async def test_actionmenu_request_no_conn_record(self): ) as mock_conn_record, async_mock.patch.object( test_module, "Perform", autospec=True ) as mock_perform: - # Emulate storage not found (bad connection id) mock_conn_record.retrieve_by_id = async_mock.CoroutineMock( side_effect=StorageNotFoundError @@ -171,7 +166,6 @@ async def test_actionmenu_request_conn_not_ready(self): ) as mock_conn_record, async_mock.patch.object( test_module, "Perform", autospec=True ) as mock_perform: - # Emulate connection not ready mock_conn_record.retrieve_by_id = async_mock.CoroutineMock() mock_conn_record.retrieve_by_id.return_value.is_ready = False @@ -190,7 +184,6 @@ async def test_actionmenu_send(self): ) as mock_menu, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_conn_record.retrieve_by_id = async_mock.CoroutineMock() mock_menu.deserialize = async_mock.MagicMock() @@ -210,7 +203,6 @@ async def test_actionmenu_send_deserialize_x(self): ) as mock_conn_record, async_mock.patch.object( test_module, "Menu", autospec=True ) as mock_menu: - mock_conn_record.retrieve_by_id = async_mock.CoroutineMock() mock_menu.deserialize = async_mock.MagicMock( side_effect=test_module.BaseModelError("cannot deserialize") @@ -228,7 +220,6 @@ async def test_actionmenu_send_no_conn_record(self): ) as mock_conn_record, async_mock.patch.object( test_module, "Menu", autospec=True ) as mock_menu: - mock_menu.deserialize = async_mock.MagicMock() # Emulate storage not found (bad connection id) @@ -248,7 +239,6 @@ async def test_actionmenu_send_conn_not_ready(self): ) as mock_conn_record, async_mock.patch.object( test_module, "Menu", autospec=True ) as mock_menu: - mock_menu.deserialize = async_mock.MagicMock() # Emulate connection not ready diff --git a/aries_cloudagent/protocols/actionmenu/v1_0/tests/test_service.py b/aries_cloudagent/protocols/actionmenu/v1_0/tests/test_service.py index 8a94c08361..4573997115 100644 --- a/aries_cloudagent/protocols/actionmenu/v1_0/tests/test_service.py +++ b/aries_cloudagent/protocols/actionmenu/v1_0/tests/test_service.py @@ -17,8 +17,8 @@ async def test_get_active_menu(self): mock_event_bus = MockEventBus() self.context.profile.context.injector.bind_instance(EventBus, mock_event_bus) - self.menu_service = await ( - test_module.DriverMenuService.service_handler()(self.context) + self.menu_service = await test_module.DriverMenuService.service_handler()( + self.context ) connection = async_mock.MagicMock() @@ -41,8 +41,8 @@ async def test_perform_menu_action(self): mock_event_bus = MockEventBus() self.context.profile.context.injector.bind_instance(EventBus, mock_event_bus) - self.menu_service = await ( - test_module.DriverMenuService.service_handler()(self.context) + self.menu_service = await test_module.DriverMenuService.service_handler()( + self.context ) action_name = "action" diff --git a/aries_cloudagent/protocols/basicmessage/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/basicmessage/v1_0/tests/test_routes.py index 6265cbd762..ca730021b0 100644 --- a/aries_cloudagent/protocols/basicmessage/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/basicmessage/v1_0/tests/test_routes.py @@ -34,7 +34,6 @@ async def test_connections_send_message(self): ) as mock_basic_message, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_connection_record.retrieve_by_id = async_mock.CoroutineMock() res = await test_module.connections_send_message(self.request) @@ -50,7 +49,6 @@ async def test_connections_send_message_no_conn_record(self): ) as mock_connection_record, async_mock.patch.object( test_module, "BasicMessage", autospec=True ) as mock_basic_message: - # Emulate storage not found (bad connection id) mock_connection_record.retrieve_by_id = async_mock.CoroutineMock( side_effect=StorageNotFoundError @@ -68,7 +66,6 @@ async def test_connections_send_message_not_ready(self): ) as mock_connection_record, async_mock.patch.object( test_module, "BasicMessage", autospec=True ) as mock_basic_message: - # Emulate connection not ready mock_connection_record.retrieve_by_id = async_mock.CoroutineMock() mock_connection_record.retrieve_by_id.return_value.is_ready = False diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py index 23c8057645..8d69a62821 100644 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/manager.py @@ -127,10 +127,42 @@ async def create_invitation( or_default=True, ) image_url = self.profile.context.settings.get("image_url") + invitation = None + connection = None + + invitation_mode = ConnRecord.INVITATION_MODE_ONCE + if multi_use: + invitation_mode = ConnRecord.INVITATION_MODE_MULTI if not my_label: my_label = self.profile.settings.get("default_label") + accept = ( + ConnRecord.ACCEPT_AUTO + if ( + auto_accept + or ( + auto_accept is None + and self.profile.settings.get("debug.auto_accept_requests") + ) + ) + else ConnRecord.ACCEPT_MANUAL + ) + + if recipient_keys: + # TODO: register recipient keys for relay + # TODO: check that recipient keys are in wallet + invitation_key = recipient_keys[0] # TODO first key appropriate? + else: + # Create and store new invitation key + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + invitation_signing_key = await wallet.create_signing_key( + key_type=ED25519 + ) + invitation_key = invitation_signing_key.verkey + recipient_keys = [invitation_key] + if public: if not self.profile.settings.get("public_invites"): raise ConnectionManagerError("Public invitations are not enabled") @@ -143,89 +175,64 @@ async def create_invitation( "Cannot create public invitation with no public DID" ) - if multi_use: - raise ConnectionManagerError( - "Cannot use public and multi_use at the same time" - ) - - if metadata: - raise ConnectionManagerError( - "Cannot use public and set metadata at the same time" - ) - # FIXME - allow ledger instance to format public DID with prefix? invitation = ConnectionInvitation( label=my_label, did=f"did:sov:{public_did.did}", image_url=image_url ) + connection = ConnRecord( # create connection record + invitation_key=public_did.verkey, + invitation_msg_id=invitation._id, + invitation_mode=invitation_mode, + their_role=ConnRecord.Role.REQUESTER.rfc23, + state=ConnRecord.State.INVITATION.rfc23, + accept=accept, + alias=alias, + connection_protocol=CONN_PROTO, + ) + + async with self.profile.session() as session: + await connection.save(session, reason="Created new invitation") + # Add mapping for multitenant relaying. # Mediation of public keys is not supported yet await self._route_manager.route_public_did(self.profile, public_did.verkey) - return None, invitation - - invitation_mode = ConnRecord.INVITATION_MODE_ONCE - if multi_use: - invitation_mode = ConnRecord.INVITATION_MODE_MULTI - - if recipient_keys: - # TODO: register recipient keys for relay - # TODO: check that recipient keys are in wallet - invitation_key = recipient_keys[0] # TODO first key appropriate? else: - # Create and store new invitation key + # Create connection record + connection = ConnRecord( + invitation_key=invitation_key, # TODO: determine correct key to use + their_role=ConnRecord.Role.REQUESTER.rfc160, + state=ConnRecord.State.INVITATION.rfc160, + accept=accept, + invitation_mode=invitation_mode, + alias=alias, + connection_protocol=CONN_PROTO, + ) async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - invitation_signing_key = await wallet.create_signing_key( - key_type=ED25519 - ) - invitation_key = invitation_signing_key.verkey - recipient_keys = [invitation_key] + await connection.save(session, reason="Created new invitation") - accept = ( - ConnRecord.ACCEPT_AUTO - if ( - auto_accept - or ( - auto_accept is None - and self.profile.settings.get("debug.auto_accept_requests") - ) + await self._route_manager.route_invitation( + self.profile, connection, mediation_record + ) + routing_keys, my_endpoint = await self._route_manager.routing_info( + self.profile, + my_endpoint or cast(str, self.profile.settings.get("default_endpoint")), + mediation_record, ) - else ConnRecord.ACCEPT_MANUAL - ) - - # Create connection record - connection = ConnRecord( - invitation_key=invitation_key, # TODO: determine correct key to use - their_role=ConnRecord.Role.REQUESTER.rfc160, - state=ConnRecord.State.INVITATION.rfc160, - accept=accept, - invitation_mode=invitation_mode, - alias=alias, - connection_protocol=CONN_PROTO, - ) - async with self.profile.session() as session: - await connection.save(session, reason="Created new invitation") - await self._route_manager.route_invitation( - self.profile, connection, mediation_record - ) - routing_keys, my_endpoint = await self._route_manager.routing_info( - self.profile, - my_endpoint or cast(str, self.profile.settings.get("default_endpoint")), - mediation_record, - ) + # Create connection invitation message + # Note: Need to split this into two stages + # to support inbound routing of invites + # Would want to reuse create_did_document and convert the result + invitation = ConnectionInvitation( + label=my_label, + recipient_keys=recipient_keys, + routing_keys=routing_keys, + endpoint=my_endpoint, + image_url=image_url, + ) - # Create connection invitation message - # Note: Need to split this into two stages to support inbound routing of invites - # Would want to reuse create_did_document and convert the result - invitation = ConnectionInvitation( - label=my_label, - recipient_keys=recipient_keys, - routing_keys=routing_keys, - endpoint=my_endpoint, - image_url=image_url, - ) async with self.profile.session() as session: await connection.attach_invitation(session, invitation) @@ -472,7 +479,7 @@ async def receive_request( new_connection = ConnRecord( invitation_key=connection_key, my_did=my_info.did, - state=ConnRecord.State.INVITATION.rfc160, + state=ConnRecord.State.REQUEST.rfc160, accept=connection.accept, their_role=connection.their_role, connection_protocol=CONN_PROTO, @@ -529,6 +536,11 @@ async def receive_request( their_role=ConnRecord.Role.REQUESTER.rfc160, ) if not connection: + if not self.profile.settings.get("requests_through_public_did"): + raise ConnectionManagerError( + "Unsolicited connection requests to " + "public DID is not enabled" + ) connection = ConnRecord() connection.invitation_key = connection_key connection.my_did = my_info.did diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_invitation.py b/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_invitation.py index 58d38b5917..b35bf95e1a 100644 --- a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_invitation.py +++ b/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_invitation.py @@ -84,7 +84,6 @@ def test_from_no_url(self): class TestConnectionInvitationSchema(TestCase): - connection_invitation = ConnectionInvitation( label="label", did="did:sov:QmWbsNYhMrjHiqZDTUTEJs" ) diff --git a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_response.py b/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_response.py index fd47e9c896..0fadf94ff4 100644 --- a/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_response.py +++ b/aries_cloudagent/protocols/connections/v1_0/messages/tests/test_connection_response.py @@ -19,7 +19,6 @@ class TestConfig: - test_seed = "testseed000000000000000000000001" test_did = "55GkHamhTU1ZbTbV2ab9DE" test_verkey = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" diff --git a/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py b/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py index cdbac51cd7..96a64abed1 100644 --- a/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py +++ b/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py @@ -23,7 +23,7 @@ def _serialize(self, value, attr, obj, **kwargs): """ return value.serialize() - def _deserialize(self, value, attr, data, **kwargs): + def _deserialize(self, value, attr=None, data=None, **kwargs): """ Deserialize a value into a DIDDoc. diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py index be52b0aefb..03c7e64647 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -23,7 +23,7 @@ from .....storage.error import StorageNotFoundError from .....transport.inbound.receipt import MessageReceipt from .....wallet.base import DIDInfo -from .....wallet.did_method import SOV +from .....wallet.did_method import SOV, DIDMethods from .....wallet.error import WalletNotFoundError from .....wallet.in_memory import InMemoryWallet from .....wallet.key_type import ED25519 @@ -34,6 +34,7 @@ from ....discovery.v2_0.manager import V20DiscoveryMgr from ..manager import ConnectionManager, ConnectionManagerError +from .. import manager as test_module from ..messages.connection_invitation import ConnectionInvitation from ..messages.connection_request import ConnectionRequest from ..messages.connection_response import ConnectionResponse @@ -93,6 +94,7 @@ async def setUp(self): BaseCache: InMemoryCache(), OobMessageProcessor: self.oob_mock, RouteManager: self.route_manager, + DIDMethods: DIDMethods(), }, ) self.context = self.profile.context @@ -111,21 +113,6 @@ async def setUp(self): self.manager = ConnectionManager(self.profile) assert self.manager.profile - async def test_create_invitation_public_and_multi_use_fails(self): - self.context.update_settings({"public_invites": True}) - with async_mock.patch.object( - InMemoryWallet, "get_public_did", autospec=True - ) as mock_wallet_get_public_did: - mock_wallet_get_public_did.return_value = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - with self.assertRaises(ConnectionManagerError): - await self.manager.create_invitation(public=True, multi_use=True) - async def test_create_invitation_non_multi_use_invitation_fails_on_reuse(self): connect_record, connect_invite = await self.manager.create_invitation() @@ -173,7 +160,7 @@ async def test_create_invitation_public(self): public=True, my_endpoint="testendpoint" ) - assert connect_record is None + assert connect_record assert connect_invite.did.endswith(self.test_did) self.route_manager.route_public_did.assert_called_once_with( self.profile, self.test_verkey @@ -265,23 +252,6 @@ async def test_create_invitation_metadata_assigned(self): assert await record.metadata_get_all(session) == {"hello": "world"} - async def test_create_invitation_public_and_metadata_fails(self): - self.context.update_settings({"public_invites": True}) - with async_mock.patch.object( - InMemoryWallet, "get_public_did", autospec=True - ) as mock_wallet_get_public_did: - mock_wallet_get_public_did.return_value = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=SOV, - key_type=ED25519, - ) - with self.assertRaises(ConnectionManagerError): - await self.manager.create_invitation( - public=True, metadata={"hello": "world"} - ) - async def test_create_invitation_multi_use_metadata_transfers_to_connection(self): async with self.profile.session() as session: connect_record, _ = await self.manager.create_invitation( @@ -642,6 +612,41 @@ async def test_receive_request_public_did_oob_invite(self): self.profile, mock_request ) + async def test_receive_request_public_did_unsolicited_fails(self): + async with self.profile.session() as session: + mock_request = async_mock.MagicMock() + mock_request.connection = async_mock.MagicMock() + mock_request.connection.did = self.test_did + mock_request.connection.did_doc = async_mock.MagicMock() + mock_request.connection.did_doc.did = self.test_did + + receipt = MessageReceipt( + recipient_did=self.test_did, recipient_did_public=True + ) + await session.wallet.create_local_did( + method=SOV, + key_type=ED25519, + seed=None, + did=self.test_did, + ) + + self.context.update_settings({"public_invites": True}) + with self.assertRaises(ConnectionManagerError), async_mock.patch.object( + ConnRecord, "connection_id", autospec=True + ), async_mock.patch.object( + ConnRecord, "save", autospec=True + ) as mock_conn_rec_save, async_mock.patch.object( + ConnRecord, "attach_request", autospec=True + ) as mock_conn_attach_request, async_mock.patch.object( + ConnRecord, "retrieve_by_id", autospec=True + ) as mock_conn_retrieve_by_id, async_mock.patch.object( + ConnRecord, "retrieve_request", autospec=True + ), async_mock.patch.object( + ConnRecord, "retrieve_by_invitation_msg_id", async_mock.CoroutineMock() + ) as mock_conn_retrieve_by_invitation_msg_id: + mock_conn_retrieve_by_invitation_msg_id.return_value = None + conn_rec = await self.manager.receive_request(mock_request, receipt) + async def test_receive_request_public_did_conn_invite(self): async with self.profile.session() as session: mock_request = async_mock.MagicMock() @@ -660,7 +665,49 @@ async def test_receive_request_public_did_conn_invite(self): did=self.test_did, ) + mock_connection_record = async_mock.MagicMock() + mock_connection_record.save = async_mock.CoroutineMock() + mock_connection_record.attach_request = async_mock.CoroutineMock() + self.context.update_settings({"public_invites": True}) + with async_mock.patch.object( + ConnRecord, "connection_id", autospec=True + ), async_mock.patch.object( + ConnRecord, "save", autospec=True + ) as mock_conn_rec_save, async_mock.patch.object( + ConnRecord, "attach_request", autospec=True + ) as mock_conn_attach_request, async_mock.patch.object( + ConnRecord, "retrieve_by_id", autospec=True + ) as mock_conn_retrieve_by_id, async_mock.patch.object( + ConnRecord, "retrieve_request", autospec=True + ), async_mock.patch.object( + ConnRecord, + "retrieve_by_invitation_msg_id", + async_mock.CoroutineMock(return_value=mock_connection_record), + ) as mock_conn_retrieve_by_invitation_msg_id: + conn_rec = await self.manager.receive_request(mock_request, receipt) + assert conn_rec + + async def test_receive_request_public_did_unsolicited(self): + async with self.profile.session() as session: + mock_request = async_mock.MagicMock() + mock_request.connection = async_mock.MagicMock() + mock_request.connection.did = self.test_did + mock_request.connection.did_doc = async_mock.MagicMock() + mock_request.connection.did_doc.did = self.test_did + + receipt = MessageReceipt( + recipient_did=self.test_did, recipient_did_public=True + ) + await session.wallet.create_local_did( + method=SOV, + key_type=ED25519, + seed=None, + did=self.test_did, + ) + + self.context.update_settings({"public_invites": True}) + self.context.update_settings({"requests_through_public_did": True}) with async_mock.patch.object( ConnRecord, "connection_id", autospec=True ), async_mock.patch.object( @@ -2068,6 +2115,9 @@ async def test_fetch_connection_targets_conn_invitation_did_resolver(self): return_value=self.test_endpoint ) self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) + self.resolver.dereference = async_mock.CoroutineMock( + return_value=did_doc.verification_method[0] + ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( @@ -2137,6 +2187,9 @@ async def test_fetch_connection_targets_conn_invitation_btcr_resolver(self): return_value=self.test_endpoint ) self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) + self.resolver.dereference = async_mock.CoroutineMock( + return_value=did_doc.verification_method[0] + ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( method=SOV, @@ -2288,6 +2341,9 @@ async def test_fetch_connection_targets_conn_invitation_unsupported_key_type(sel return_value=self.test_endpoint ) self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) + self.resolver.dereference = async_mock.CoroutineMock( + return_value=did_doc.verification_method[0] + ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( method=SOV, @@ -2357,6 +2413,9 @@ async def test_fetch_connection_targets_oob_invitation_svc_did_resolver(self): self.resolver = async_mock.MagicMock() self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) + self.resolver.dereference = async_mock.CoroutineMock( + return_value=did_doc.verification_method[0] + ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py index 5886e5aed9..274ef8b6e6 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py @@ -360,7 +360,6 @@ async def test_connections_create_invitation(self): ) as mock_conn_mgr, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_conn_mgr.return_value.create_invitation = async_mock.CoroutineMock( return_value=( async_mock.MagicMock( # connection record @@ -543,7 +542,6 @@ async def test_connections_accept_invitation(self): ) as mock_conn_mgr, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_conn_rec_retrieve_by_id.return_value = mock_conn_rec mock_conn_mgr.return_value.create_request = async_mock.CoroutineMock() diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_request_handler.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_request_handler.py index 61d2b4d449..d3c342ec07 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_request_handler.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_request_handler.py @@ -13,6 +13,7 @@ from ...models.mediation_record import MediationRecord from ..mediation_request_handler import MediationRequestHandler +from ......wallet.did_method import DIDMethods TEST_CONN_ID = "conn-id" TEST_VERKEY = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" @@ -24,6 +25,7 @@ class TestMediationRequestHandler(AsyncTestCase): async def setUp(self): """setup dependencies of messaging""" self.context = RequestContext.test_context() + self.context.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.session = await self.context.session() self.context.message = MediationRequest() self.context.connection_ready = True diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py index 97bc2f80f7..7e93eaa841 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py @@ -26,6 +26,7 @@ from ..messages.mediate_grant import MediationGrant from ..messages.mediate_request import MediationRequest from ..models.mediation_record import MediationRecord +from .....wallet.did_method import DIDMethods TEST_CONN_ID = "conn-id" TEST_THREAD_ID = "thread-id" @@ -42,7 +43,9 @@ def profile() -> Iterable[Profile]: """Fixture for profile used in tests.""" # pylint: disable=W0621 - yield InMemoryProfile.test_profile(bind={EventBus: MockEventBus()}) + yield InMemoryProfile.test_profile( + bind={EventBus: MockEventBus(), DIDMethods: DIDMethods()} + ) @pytest.fixture diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py index 4cbd017a45..ababf0ea16 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py @@ -6,11 +6,13 @@ from .....storage.error import StorageError, StorageNotFoundError from ..models.mediation_record import MediationRecord from ..route_manager import RouteManager +from .....wallet.did_method import DIDMethods class TestCoordinateMediationRoutes(AsyncTestCase): def setUp(self): self.profile = InMemoryProfile.test_profile() + self.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.context = AdminRequestContext.test_context(profile=self.profile) self.outbound_message_router = async_mock.CoroutineMock() self.request_dict = { diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_complete_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_complete_handler.py index bbeba7c27f..19372e515c 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_complete_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_complete_handler.py @@ -10,11 +10,13 @@ from ...messages.problem_report_reason import ProblemReportReason from .. import complete_handler as test_module +from ......wallet.did_method import DIDMethods @pytest.fixture() def request_context() -> RequestContext: ctx = RequestContext.test_context() + ctx.injector.bind_instance(DIDMethods, DIDMethods()) ctx.message_receipt = MessageReceipt() yield ctx diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_invitation_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_invitation_handler.py index 95a728310c..279d905863 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_invitation_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_invitation_handler.py @@ -9,11 +9,13 @@ from ...handlers.invitation_handler import InvitationHandler from ...messages.problem_report_reason import ProblemReportReason +from ......wallet.did_method import DIDMethods @pytest.fixture() def request_context() -> RequestContext: ctx = RequestContext.test_context() + ctx.injector.bind_instance(DIDMethods, DIDMethods()) ctx.message_receipt = MessageReceipt() yield ctx diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py index 8f8c4381b1..6c7a5892b8 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py @@ -9,7 +9,7 @@ Service, ) from ......core.in_memory import InMemoryProfile -from ......wallet.did_method import SOV +from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.request_context import RequestContext @@ -76,6 +76,7 @@ async def setUp(self): "debug.auto_accept_requests_public": True, } ) + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.conn_rec = conn_record.ConnRecord( my_did="55GkHamhTU1ZbTbV2ab9DE", diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py index ff9750ca14..ba81955e36 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py @@ -13,7 +13,7 @@ from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder from ......transport.inbound.receipt import MessageReceipt -from ......wallet.did_method import SOV +from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 from .....problem_report.v1_0.message import ProblemReport @@ -63,6 +63,7 @@ async def setUp(self): self.ctx = RequestContext.test_context() self.ctx.message_receipt = MessageReceipt() + self.ctx.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) wallet = (await self.ctx.session()).wallet self.did_info = await wallet.create_local_did( method=SOV, diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 2509766e9d..b209114b01 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -483,6 +483,10 @@ async def receive_request( ) else: # request is against implicit invitation on public DID + if not self.profile.settings.get("requests_through_public_did"): + raise DIDXManagerError( + "Unsolicited connection requests to " "public DID is not enabled" + ) async with self.profile.session() as session: wallet = session.inject(BaseWallet) my_info = await wallet.create_local_did( diff --git a/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_request.py b/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_request.py index 37569858e6..dc4c8b189e 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_request.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_request.py @@ -5,7 +5,7 @@ from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service from ......core.in_memory import InMemoryProfile from ......messaging.decorators.attach_decorator import AttachDecorator -from ......wallet.did_method import SOV +from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 from .....didcomm_prefix import DIDCommPrefix from ...message_types import DIDX_REQUEST @@ -49,7 +49,9 @@ def make_did_doc(self): class TestDIDXRequest(AsyncTestCase, TestConfig): async def setUp(self): - self.wallet = InMemoryProfile.test_session().wallet + self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.wallet = self.session.wallet self.did_info = await self.wallet.create_local_did( method=SOV, key_type=ED25519, @@ -106,7 +108,9 @@ class TestDIDXRequestSchema(AsyncTestCase, TestConfig): """Test request schema.""" async def setUp(self): - self.wallet = InMemoryProfile.test_session().wallet + self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.wallet = self.session.wallet self.did_info = await self.wallet.create_local_did( method=SOV, key_type=ED25519, diff --git a/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_response.py b/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_response.py index 59657ef2dc..3be60ca51e 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_response.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_response.py @@ -5,7 +5,7 @@ from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service from ......core.in_memory import InMemoryProfile from ......messaging.decorators.attach_decorator import AttachDecorator -from ......wallet.did_method import SOV +from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 from .....didcomm_prefix import DIDCommPrefix from ...message_types import DIDX_RESPONSE @@ -48,7 +48,10 @@ def make_did_doc(self): class TestDIDXResponse(AsyncTestCase, TestConfig): async def setUp(self): - self.wallet = InMemoryProfile.test_session().wallet + self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.wallet = self.session.wallet + self.did_info = await self.wallet.create_local_did( method=SOV, key_type=ED25519, @@ -102,7 +105,10 @@ class TestDIDXResponseSchema(AsyncTestCase, TestConfig): """Test response schema.""" async def setUp(self): - self.wallet = InMemoryProfile.test_session().wallet + self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.wallet = self.session.wallet + self.did_info = await self.wallet.create_local_did( method=SOV, key_type=ED25519, diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py index 18f87d6b30..f5ddf31b4d 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -24,7 +24,7 @@ from .....storage.error import StorageNotFoundError from .....transport.inbound.receipt import MessageReceipt from .....wallet.did_info import DIDInfo -from .....wallet.did_method import SOV +from .....wallet.did_method import SOV, DIDMethods from .....wallet.error import WalletError from .....wallet.in_memory import InMemoryWallet from .....wallet.key_type import ED25519 @@ -41,7 +41,6 @@ class TestConfig: - test_seed = "testseed000000000000000000000001" test_did = "55GkHamhTU1ZbTbV2ab9DE" test_verkey = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" @@ -102,6 +101,7 @@ async def setUp(self): BaseCache: InMemoryCache(), OobMessageProcessor: self.oob_mock, RouteManager: self.route_manager, + DIDMethods: DIDMethods(), }, ) self.context = self.profile.context @@ -964,6 +964,151 @@ async def test_receive_request_public_did_no_auto_accept(self): messages = self.responder.messages assert not messages + async def test_receive_request_implicit_public_did_not_enabled(self): + async with self.profile.session() as session: + mock_request = async_mock.MagicMock( + did=TestConfig.test_did, + did_doc_attach=async_mock.MagicMock( + data=async_mock.MagicMock( + verify=async_mock.CoroutineMock(return_value=True), + signed=async_mock.MagicMock( + decode=async_mock.MagicMock(return_value="dummy-did-doc") + ), + ) + ), + _thread=async_mock.MagicMock(pthid="did:sov:publicdid0000000000000"), + ) + mediation_record = MediationRecord( + role=MediationRecord.ROLE_CLIENT, + state=MediationRecord.STATE_GRANTED, + connection_id=self.test_mediator_conn_id, + routing_keys=self.test_mediator_routing_keys, + endpoint=self.test_mediator_endpoint, + ) + await mediation_record.save(session) + + await session.wallet.create_local_did( + method=SOV, + key_type=ED25519, + seed=None, + did=TestConfig.test_did, + ) + + self.profile.context.update_settings({"public_invites": True}) + + with async_mock.patch.object( + test_module, "ConnRecord", async_mock.MagicMock() + ) as mock_conn_rec_cls, async_mock.patch.object( + test_module, "DIDDoc", autospec=True + ) as mock_did_doc, async_mock.patch.object( + test_module, "DIDPosture", autospec=True + ) as mock_did_posture, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + ): + mock_did_posture.get = async_mock.MagicMock( + return_value=test_module.DIDPosture.PUBLIC + ) + mock_conn_rec_cls.retrieve_by_invitation_key = async_mock.CoroutineMock( + side_effect=StorageNotFoundError() + ) + mock_conn_rec_cls.retrieve_by_invitation_msg_id = ( + async_mock.CoroutineMock(return_value=None) + ) + + with self.assertRaises(DIDXManagerError) as context: + await self.manager.receive_request( + request=mock_request, + recipient_did=TestConfig.test_did, + my_endpoint=None, + alias=None, + auto_accept_implicit=None, + ) + assert "Unsolicited connection requests" in str(context.exception) + + async def test_receive_request_implicit_public_did(self): + async with self.profile.session() as session: + mock_request = async_mock.MagicMock( + did=TestConfig.test_did, + did_doc_attach=async_mock.MagicMock( + data=async_mock.MagicMock( + verify=async_mock.CoroutineMock(return_value=True), + signed=async_mock.MagicMock( + decode=async_mock.MagicMock(return_value="dummy-did-doc") + ), + ) + ), + _thread=async_mock.MagicMock(pthid="did:sov:publicdid0000000000000"), + ) + mediation_record = MediationRecord( + role=MediationRecord.ROLE_CLIENT, + state=MediationRecord.STATE_GRANTED, + connection_id=self.test_mediator_conn_id, + routing_keys=self.test_mediator_routing_keys, + endpoint=self.test_mediator_endpoint, + ) + await mediation_record.save(session) + + await session.wallet.create_local_did( + method=SOV, + key_type=ED25519, + seed=None, + did=TestConfig.test_did, + ) + + self.profile.context.update_settings({"public_invites": True}) + self.profile.context.update_settings({"requests_through_public_did": True}) + ACCEPT_AUTO = ConnRecord.ACCEPT_AUTO + STATE_REQUEST = ConnRecord.State.REQUEST + + with async_mock.patch.object( + test_module, "ConnRecord", async_mock.MagicMock() + ) as mock_conn_rec_cls, async_mock.patch.object( + test_module, "DIDDoc", autospec=True + ) as mock_did_doc, async_mock.patch.object( + test_module, "DIDPosture", autospec=True + ) as mock_did_posture, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did)), + ): + mock_did_posture.get = async_mock.MagicMock( + return_value=test_module.DIDPosture.PUBLIC + ) + mock_conn_rec_cls.retrieve_by_invitation_key = async_mock.CoroutineMock( + side_effect=StorageNotFoundError() + ) + mock_conn_rec_cls.retrieve_by_invitation_msg_id = ( + async_mock.CoroutineMock(return_value=None) + ) + + mock_conn_record = async_mock.MagicMock( + accept=ACCEPT_AUTO, + my_did=None, + state=STATE_REQUEST.rfc23, + attach_request=async_mock.CoroutineMock(), + retrieve_request=async_mock.CoroutineMock(), + metadata_get_all=async_mock.CoroutineMock(return_value={}), + metadata_get=async_mock.CoroutineMock(return_value=True), + save=async_mock.CoroutineMock(), + ) + + mock_conn_rec_cls.return_value = mock_conn_record + + conn_rec = await self.manager.receive_request( + request=mock_request, + recipient_did=TestConfig.test_did, + recipient_verkey=None, + my_endpoint=None, + alias=None, + auto_accept_implicit=None, + ) + assert conn_rec + self.oob_mock.clean_finished_oob_record.assert_called_once_with( + self.profile, mock_request + ) + async def test_receive_request_peer_did(self): async with self.profile.session() as session: mock_request = async_mock.MagicMock( diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_routes.py index 44584c5d1e..8de2345b31 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_routes.py @@ -43,7 +43,6 @@ async def test_didx_accept_invitation(self): ) as mock_didx_mgr, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_conn_rec_class.retrieve_by_id.return_value = mock_conn_rec mock_didx_mgr.return_value.create_request = async_mock.CoroutineMock() diff --git a/aries_cloudagent/protocols/discovery/v1_0/messages/tests/test_disclose.py b/aries_cloudagent/protocols/discovery/v1_0/messages/tests/test_disclose.py index 658f25bc67..84e02cf12e 100644 --- a/aries_cloudagent/protocols/discovery/v1_0/messages/tests/test_disclose.py +++ b/aries_cloudagent/protocols/discovery/v1_0/messages/tests/test_disclose.py @@ -42,7 +42,6 @@ def test_serialize(self, mock_disclose_schema_dump): class TestDiscloseSchema(TestCase): - disclose = Disclose(protocols=[]) def test_make_model(self): diff --git a/aries_cloudagent/protocols/discovery/v1_0/messages/tests/test_query.py b/aries_cloudagent/protocols/discovery/v1_0/messages/tests/test_query.py index 114c02f80c..18af3b6ba7 100644 --- a/aries_cloudagent/protocols/discovery/v1_0/messages/tests/test_query.py +++ b/aries_cloudagent/protocols/discovery/v1_0/messages/tests/test_query.py @@ -39,7 +39,6 @@ def test_serialize(self, mock_query_schema_dump): class TestQuerySchema(TestCase): - query = Query(query="*", comment="comment") def test_make_model(self): diff --git a/aries_cloudagent/protocols/discovery/v2_0/messages/tests/test_queries.py b/aries_cloudagent/protocols/discovery/v2_0/messages/tests/test_queries.py index e8957dc912..01dfa793f3 100644 --- a/aries_cloudagent/protocols/discovery/v2_0/messages/tests/test_queries.py +++ b/aries_cloudagent/protocols/discovery/v2_0/messages/tests/test_queries.py @@ -60,7 +60,6 @@ def test_serialize(self, mock_queries_schema_dump): class TestQuerySchema(TestCase): - test_queries = [ QueryItem(feature_type="protocol", match=TEST_QUERY_PROTOCOL), QueryItem(feature_type="goal-code", match=TEST_QUERY_GOAL_CODE), diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py index 1ce20b7ca5..df10ffe4ee 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py @@ -12,7 +12,7 @@ from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError from .....wallet.base import BaseWallet -from .....wallet.did_method import SOV +from .....wallet.did_method import SOV, DIDMethods from .....wallet.key_type import ED25519 from ..manager import TransactionManager, TransactionManagerError from ..models.transaction_record import TransactionRecord @@ -112,6 +112,7 @@ async def setUp(self): self.profile = self.context.profile injector = self.profile.context.injector injector.bind_instance(BaseLedger, self.ledger) + injector.bind_instance(DIDMethods, DIDMethods()) async with self.profile.session() as session: self.wallet: BaseWallet = session.inject_or(BaseWallet) @@ -495,7 +496,6 @@ async def test_complete_transaction(self): ) as save_record, async_mock.patch.object( ConnRecord, "retrieve_by_id" ) as mock_conn_rec_retrieve: - mock_conn_rec_retrieve.return_value = async_mock.MagicMock( metadata_get=async_mock.CoroutineMock( return_value={ diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_routes.py index 611d1245ce..4d6e3b0897 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_routes.py @@ -1553,7 +1553,6 @@ async def test_transaction_write_schema_txn(self): ) as mock_txn_mgr, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_txn_mgr.return_value.complete_transaction = async_mock.CoroutineMock() mock_txn_mgr.return_value.complete_transaction.return_value = ( @@ -1600,7 +1599,6 @@ async def test_transaction_write_wrong_state_x(self): with async_mock.patch.object( TransactionRecord, "retrieve_by_id", async_mock.CoroutineMock() ) as mock_txn_rec_retrieve: - mock_txn_rec_retrieve.return_value = async_mock.MagicMock( serialize=async_mock.MagicMock(return_value={"...": "..."}), state=TransactionRecord.STATE_TRANSACTION_CREATED, diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index 90d1a15217..1bca37dea8 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -356,13 +356,15 @@ async def receive_offer( # Get credential exchange record (holder sent proposal first) # or create it (issuer sent offer first) try: - cred_ex_record = await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, - connection_id, - message._thread_id, - role=V10CredentialExchange.ROLE_HOLDER, - for_update=True, + cred_ex_record = ( + await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, + connection_id, + message._thread_id, + role=V10CredentialExchange.ROLE_HOLDER, + for_update=True, + ) ) ) except StorageNotFoundError: # issuer sent this offer free of any proposal @@ -532,13 +534,15 @@ async def receive_request( async with self._profile.transaction() as txn: try: - cred_ex_record = await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, - connection_id, - message._thread_id, - role=V10CredentialExchange.ROLE_ISSUER, - for_update=True, + cred_ex_record = ( + await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, + connection_id, + message._thread_id, + role=V10CredentialExchange.ROLE_ISSUER, + for_update=True, + ) ) ) except StorageNotFoundError: @@ -741,13 +745,15 @@ async def receive_credential( async with self._profile.transaction() as txn: try: - cred_ex_record = await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, - connection_id, - message._thread_id, - role=V10CredentialExchange.ROLE_HOLDER, - for_update=True, + cred_ex_record = ( + await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, + connection_id, + message._thread_id, + role=V10CredentialExchange.ROLE_HOLDER, + for_update=True, + ) ) ) except StorageNotFoundError: @@ -947,13 +953,15 @@ async def receive_credential_ack( """ async with self._profile.transaction() as txn: try: - cred_ex_record = await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, - connection_id, - message._thread_id, - role=V10CredentialExchange.ROLE_ISSUER, - for_update=True, + cred_ex_record = ( + await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, + connection_id, + message._thread_id, + role=V10CredentialExchange.ROLE_ISSUER, + for_update=True, + ) ) ) except StorageNotFoundError: @@ -987,9 +995,11 @@ async def receive_problem_report( """ async with self._profile.transaction() as txn: try: - cred_ex_record = await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, connection_id, message._thread_id, for_update=True + cred_ex_record = ( + await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, connection_id, message._thread_id, for_update=True + ) ) ) except StorageNotFoundError: diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py index 94ec08f0f6..838f5abdf4 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py @@ -1357,7 +1357,6 @@ async def test_store_credential(self): ) as save_ex, async_mock.patch.object( V10CredentialExchange, "delete_record", autospec=True ) as delete_ex: - mock_rev_reg.from_definition = async_mock.MagicMock( return_value=async_mock.MagicMock( get_or_fetch_local_tails_path=async_mock.CoroutineMock() diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_routes.py index 1afc367d20..129f7eceb1 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_routes.py @@ -253,7 +253,6 @@ async def test_credential_exchange_send_no_conn_record(self): ) as mock_conn_rec, async_mock.patch.object( test_module, "CredentialManager", autospec=True ) as mock_credential_manager: - # Emulate storage not found (bad connection id) mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock( side_effect=test_module.StorageNotFoundError() @@ -280,7 +279,6 @@ async def test_credential_exchange_send_not_ready(self): ) as mock_conn_rec, async_mock.patch.object( test_module, "CredentialManager", autospec=True ) as mock_credential_manager: - # Emulate connection not ready mock_conn_rec.retrieve_by_id.return_value.is_ready = False @@ -343,7 +341,6 @@ async def test_credential_exchange_send_proposal(self): ) as mock_credential_manager, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_cred_ex_record = async_mock.MagicMock() mock_credential_manager.return_value.create_proposal.return_value = ( mock_cred_ex_record @@ -367,7 +364,6 @@ async def test_credential_exchange_send_proposal_no_conn_record(self): ) as mock_credential_manager, async_mock.patch.object( test_module.CredentialPreview, "deserialize", autospec=True ) as mock_preview_deserialize: - # Emulate storage not found (bad connection id) mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock( side_effect=test_module.StorageNotFoundError() @@ -405,7 +401,6 @@ async def test_credential_exchange_send_proposal_not_ready(self): ) as mock_credential_manager, async_mock.patch.object( test_module.CredentialPreview, "deserialize", autospec=True ) as mock_preview_deserialize: - # Emulate connection not ready mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock() mock_conn_rec.retrieve_by_id.return_value.is_ready = False @@ -595,7 +590,6 @@ async def test_credential_exchange_send_free_offer(self): ) as mock_credential_manager, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_credential_manager.return_value.create_offer = ( async_mock.CoroutineMock() ) @@ -647,7 +641,6 @@ async def test_credential_exchange_send_free_offer_no_conn_record(self): ) as mock_conn_rec, async_mock.patch.object( test_module, "CredentialManager", autospec=True ) as mock_credential_manager: - # Emulate storage not found (bad connection id) mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock( side_effect=test_module.StorageNotFoundError() @@ -673,7 +666,6 @@ async def test_credential_exchange_send_free_offer_not_ready(self): ) as mock_conn_rec, async_mock.patch.object( test_module, "CredentialManager", autospec=True ) as mock_credential_manager: - # Emulate connection not ready mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock() mock_conn_rec.retrieve_by_id.return_value.is_ready = False @@ -739,7 +731,6 @@ async def test_credential_exchange_send_bound_offer(self): ) as mock_cred_ex, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_cred_ex.retrieve_by_id = async_mock.CoroutineMock() mock_cred_ex.retrieve_by_id.return_value.state = ( mock_cred_ex.STATE_PROPOSAL_RECEIVED @@ -878,7 +869,6 @@ async def test_credential_exchange_send_request(self): ) as mock_cred_ex, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_cred_ex.retrieve_by_id = async_mock.CoroutineMock() mock_cred_ex.retrieve_by_id.return_value.state = ( mock_cred_ex.STATE_OFFER_RECEIVED @@ -912,7 +902,6 @@ async def test_credential_exchange_send_request_no_conn(self): ) as mock_cred_ex, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_oob_rec.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=async_mock.MagicMock(our_recipient_key="our-recipient_key") ) @@ -1037,7 +1026,6 @@ async def test_credential_exchange_issue(self): ) as mock_cred_ex, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_cred_ex.retrieve_by_id = async_mock.CoroutineMock() mock_cred_ex.retrieve_by_id.return_value.state = ( mock_cred_ex.STATE_REQUEST_RECEIVED @@ -1087,7 +1075,6 @@ async def test_credential_exchange_issue_no_conn_record(self): ) as mock_credential_manager, async_mock.patch.object( test_module, "V10CredentialExchange", autospec=True ) as mock_cred_ex_cls: - mock_cred_ex_rec.state = mock_cred_ex_cls.STATE_REQUEST_RECEIVED mock_cred_ex_cls.retrieve_by_id = async_mock.CoroutineMock( return_value=mock_cred_ex_rec @@ -1120,7 +1107,6 @@ async def test_credential_exchange_issue_not_ready(self): ) as mock_credential_manager, async_mock.patch.object( test_module, "V10CredentialExchange", autospec=True ) as mock_cred_ex: - mock_cred_ex.retrieve_by_id = async_mock.CoroutineMock() mock_cred_ex.retrieve_by_id.return_value.state = ( mock_cred_ex.STATE_REQUEST_RECEIVED @@ -1216,7 +1202,6 @@ async def test_credential_exchange_store(self): ) as mock_cred_ex, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_cred_ex.retrieve_by_id = async_mock.CoroutineMock() mock_cred_ex.retrieve_by_id.return_value.state = ( mock_cred_ex.STATE_CREDENTIAL_RECEIVED @@ -1253,7 +1238,6 @@ async def test_credential_exchange_store_bad_cred_id_json(self): ) as mock_cred_ex, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_cred_ex.retrieve_by_id = async_mock.CoroutineMock() mock_cred_ex.retrieve_by_id.return_value.state = ( mock_cred_ex.STATE_CREDENTIAL_RECEIVED diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py index ea4565a6b6..f0e11070e6 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py @@ -274,7 +274,6 @@ async def test_get_suite_for_detail(self): "_did_info_for_did", async_mock.CoroutineMock(), ) as mock_did_info: - suite = await self.handler._get_suite_for_detail(detail) assert suite.signature_type == detail.options.proof_type diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/manager.py b/aries_cloudagent/protocols/issue_credential/v2_0/manager.py index f1d3f541c3..ad6f3f9313 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/manager.py @@ -284,13 +284,11 @@ async def receive_offer( # or create it (issuer sent offer first) try: async with self._profile.session() as session: - cred_ex_record = await ( - V20CredExRecord.retrieve_by_conn_and_thread( - session, - connection_id, - cred_offer_message._thread_id, - role=V20CredExRecord.ROLE_HOLDER, - ) + cred_ex_record = await V20CredExRecord.retrieve_by_conn_and_thread( + session, + connection_id, + cred_offer_message._thread_id, + role=V20CredExRecord.ROLE_HOLDER, ) except StorageNotFoundError: # issuer sent this offer free of any proposal cred_ex_record = V20CredExRecord( @@ -420,13 +418,11 @@ async def receive_request( async with self._profile.session() as session: try: - cred_ex_record = await ( - V20CredExRecord.retrieve_by_conn_and_thread( - session, - connection_id, - cred_request_message._thread_id, - role=V20CredExRecord.ROLE_ISSUER, - ) + cred_ex_record = await V20CredExRecord.retrieve_by_conn_and_thread( + session, + connection_id, + cred_request_message._thread_id, + role=V20CredExRecord.ROLE_ISSUER, ) except StorageNotFoundError: # holder sent this request free of any offer @@ -553,13 +549,11 @@ async def receive_credential( # FIXME use transaction, fetch for_update async with self._profile.session() as session: - cred_ex_record = await ( - V20CredExRecord.retrieve_by_conn_and_thread( - session, - connection_id, - cred_issue_message._thread_id, - role=V20CredExRecord.ROLE_HOLDER, - ) + cred_ex_record = await V20CredExRecord.retrieve_by_conn_and_thread( + session, + connection_id, + cred_issue_message._thread_id, + role=V20CredExRecord.ROLE_HOLDER, ) cred_request_message = cred_ex_record.cred_request @@ -697,13 +691,11 @@ async def receive_credential_ack( """ # FIXME use transaction, fetch for_update async with self._profile.session() as session: - cred_ex_record = await ( - V20CredExRecord.retrieve_by_conn_and_thread( - session, - connection_id, - cred_ack_message._thread_id, - role=V20CredExRecord.ROLE_ISSUER, - ) + cred_ex_record = await V20CredExRecord.retrieve_by_conn_and_thread( + session, + connection_id, + cred_ack_message._thread_id, + role=V20CredExRecord.ROLE_ISSUER, ) cred_ex_record.state = V20CredExRecord.STATE_DONE @@ -740,12 +732,10 @@ async def receive_problem_report( """ # FIXME use transaction, fetch for_update async with self._profile.session() as session: - cred_ex_record = await ( - V20CredExRecord.retrieve_by_conn_and_thread( - session, - connection_id, - message._thread_id, - ) + cred_ex_record = await V20CredExRecord.retrieve_by_conn_and_thread( + session, + connection_id, + message._thread_id, ) cred_ex_record.state = V20CredExRecord.STATE_ABANDONED diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/models/cred_ex_record.py b/aries_cloudagent/protocols/issue_credential/v2_0/models/cred_ex_record.py index 7979c95a04..1e200ae134 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/models/cred_ex_record.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/models/cred_ex_record.py @@ -324,11 +324,7 @@ class Meta: description="Issue-credential exchange initiator: self or external", example=V20CredExRecord.INITIATOR_SELF, validate=validate.OneOf( - [ - getattr(V20CredExRecord, m) - for m in vars(V20CredExRecord) - if m.startswith("INITIATOR_") - ] + V20CredExRecord.get_attributes_by_prefix("INITIATOR_", walk_mro=False) ), ) role = fields.Str( @@ -336,11 +332,7 @@ class Meta: description="Issue-credential exchange role: holder or issuer", example=V20CredExRecord.ROLE_ISSUER, validate=validate.OneOf( - [ - getattr(V20CredExRecord, m) - for m in vars(V20CredExRecord) - if m.startswith("ROLE_") - ] + V20CredExRecord.get_attributes_by_prefix("ROLE_", walk_mro=False) ), ) state = fields.Str( @@ -348,11 +340,7 @@ class Meta: description="Issue-credential exchange state", example=V20CredExRecord.STATE_DONE, validate=validate.OneOf( - [ - getattr(V20CredExRecord, m) - for m in vars(V20CredExRecord) - if m.startswith("STATE_") - ] + V20CredExRecord.get_attributes_by_prefix("STATE_", walk_mro=True) ), ) cred_preview = fields.Nested( diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py index 583f52e9c5..55d654c5f1 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py @@ -165,7 +165,6 @@ async def test_create_proposal(self): ) as mock_save, async_mock.patch.object( V20CredFormat.Format, "handler" ) as mock_handler: - mock_handler.return_value.create_proposal = async_mock.CoroutineMock( return_value=( V20CredFormat( @@ -209,7 +208,6 @@ async def test_create_proposal_no_preview(self): ) as mock_save, async_mock.patch.object( V20CredFormat.Format, "handler" ) as mock_handler: - mock_handler.return_value.create_proposal = async_mock.CoroutineMock( return_value=( V20CredFormat( @@ -261,7 +259,6 @@ async def test_receive_proposal(self): ) as mock_save, async_mock.patch.object( V20CredFormat.Format, "handler" ) as mock_handler: - mock_handler.return_value.receive_proposal = async_mock.CoroutineMock() cred_proposal = V20CredProposal( @@ -335,7 +332,6 @@ async def test_create_free_offer(self): ) as mock_save, async_mock.patch.object( V20CredFormat.Format, "handler" ) as mock_handler: - mock_handler.return_value.create_offer = async_mock.CoroutineMock( return_value=( V20CredFormat( @@ -627,7 +623,6 @@ async def test_create_bound_request(self): ) as mock_save, async_mock.patch.object( V20CredFormat.Format, "handler" ) as mock_handler: - mock_handler.return_value.create_request = async_mock.CoroutineMock( return_value=( V20CredFormat( @@ -715,7 +710,6 @@ async def test_create_free_request(self): ) as mock_save, async_mock.patch.object( V20CredFormat.Format, "handler" ) as mock_handler: - mock_handler.return_value.create_request = async_mock.CoroutineMock( return_value=( V20CredFormat( @@ -986,7 +980,6 @@ async def test_issue_credential(self): ) as mock_save, async_mock.patch.object( V20CredFormat.Format, "handler" ) as mock_handler: - mock_handler.return_value.issue_credential = async_mock.CoroutineMock( return_value=( V20CredFormat( @@ -1099,7 +1092,6 @@ async def test_receive_cred(self): ) as mock_retrieve, async_mock.patch.object( V20CredFormat.Format, "handler" ) as mock_handler: - mock_handler.return_value.receive_credential = async_mock.CoroutineMock() mock_retrieve.return_value = stored_cx_rec ret_cx_rec = await self.manager.receive_credential( @@ -1247,7 +1239,6 @@ async def test_store_credential(self): ) as mock_delete, async_mock.patch.object( V20CredFormat.Format, "handler" ) as mock_handler: - mock_handler.return_value.store_credential = async_mock.CoroutineMock() ret_cx_rec = await self.manager.store_credential( diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py index 7a689b66b4..845f21555c 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py @@ -362,7 +362,6 @@ async def test_credential_exchange_send_request_no_conn_no_holder_did(self): ) as mock_cred_ex, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_oob_rec.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=async_mock.MagicMock(our_recipient_key="our-recipient_key") ) @@ -407,7 +406,6 @@ async def test_credential_exchange_send_no_conn_record(self): ) as mock_conn_rec, async_mock.patch.object( test_module, "V20CredManager", autospec=True ) as mock_cred_mgr: - # Emulate storage not found (bad connection id) mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock( side_effect=test_module.StorageNotFoundError() @@ -584,7 +582,6 @@ async def test_credential_exchange_send_proposal_not_ready(self): ) as mock_cred_mgr, async_mock.patch.object( test_module.V20CredPreview, "deserialize", autospec=True ) as mock_preview_deser: - # Emulate connection not ready mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock() mock_conn_rec.retrieve_by_id.return_value.is_ready = False @@ -731,7 +728,6 @@ async def test_credential_exchange_send_free_offer_no_conn_record(self): ) as mock_conn_rec, async_mock.patch.object( test_module, "V20CredManager", autospec=True ) as mock_cred_mgr: - # Emulate storage not found (bad connection id) mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock( side_effect=test_module.StorageNotFoundError() @@ -762,7 +758,6 @@ async def test_credential_exchange_send_free_offer_not_ready(self): ) as mock_conn_rec, async_mock.patch.object( test_module, "V20CredManager", autospec=True ) as mock_cred_mgr: - # Emulate connection not ready mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock() mock_conn_rec.retrieve_by_id.return_value.is_ready = False @@ -986,7 +981,6 @@ async def test_credential_exchange_send_request(self): ) as mock_cx_rec_cls, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_cx_rec_cls.retrieve_by_id = async_mock.CoroutineMock() mock_cx_rec_cls.retrieve_by_id.return_value.state = ( test_module.V20CredExRecord.STATE_OFFER_RECEIVED @@ -1096,7 +1090,6 @@ async def test_credential_exchange_send_free_request(self): ) as mock_cred_mgr, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_cred_mgr.return_value.create_request = async_mock.CoroutineMock() mock_cx_rec = async_mock.MagicMock() @@ -1131,7 +1124,6 @@ async def test_credential_exchange_send_free_request_no_conn_record(self): ) as mock_conn_rec, async_mock.patch.object( test_module, "V20CredManager", autospec=True ) as mock_cred_mgr: - # Emulate storage not found (bad connection id) mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock( side_effect=test_module.StorageNotFoundError() @@ -1158,7 +1150,6 @@ async def test_credential_exchange_send_free_request_not_ready(self): ) as mock_conn_rec, async_mock.patch.object( test_module, "V20CredManager", autospec=True ) as mock_cred_mgr: - # Emulate connection not ready mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock() mock_conn_rec.retrieve_by_id.return_value.is_ready = False @@ -1186,7 +1177,6 @@ async def test_credential_exchange_send_free_request_x(self): ) as mock_cred_mgr, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_cred_mgr.return_value.create_request = async_mock.CoroutineMock( side_effect=[ test_module.LedgerError(), @@ -1277,7 +1267,6 @@ async def test_credential_exchange_issue_no_conn_record(self): ) as mock_cred_mgr, async_mock.patch.object( test_module, "V20CredExRecord", autospec=True ) as mock_cx_rec_cls: - mock_cx_rec.state = mock_cx_rec_cls.STATE_REQUEST_RECEIVED mock_cx_rec_cls.retrieve_by_id = async_mock.CoroutineMock( return_value=mock_cx_rec @@ -1308,7 +1297,6 @@ async def test_credential_exchange_issue_not_ready(self): ) as mock_cred_mgr, async_mock.patch.object( test_module, "V20CredExRecord", autospec=True ) as mock_cx_rec: - mock_cx_rec.retrieve_by_id = async_mock.CoroutineMock() mock_cx_rec.retrieve_by_id.return_value.state = ( test_module.V20CredExRecord.STATE_REQUEST_RECEIVED @@ -1343,7 +1331,6 @@ async def test_credential_exchange_issue_rev_reg_full(self): ) as mock_cred_mgr, async_mock.patch.object( test_module, "V20CredExRecord", autospec=True ) as mock_cx_rec_cls: - mock_cx_rec.state = mock_cx_rec_cls.STATE_REQUEST_RECEIVED mock_cx_rec_cls.retrieve_by_id = async_mock.CoroutineMock( return_value=mock_cx_rec @@ -1464,7 +1451,6 @@ async def test_credential_exchange_store_bad_cred_id_json(self): ) as mock_ld_proof_get_detail_record, async_mock.patch.object( IndyCredFormatHandler, "get_detail_record", autospec=True ) as mock_indy_get_detail_record: - mock_cx_rec_cls.retrieve_by_id = async_mock.CoroutineMock() mock_cx_rec_cls.retrieve_by_id.return_value.state = ( test_module.V20CredExRecord.STATE_CREDENTIAL_RECEIVED diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 0792c2854d..58d46431f6 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -140,15 +140,6 @@ async def create_invitation( raise OutOfBandManagerError( "Cannot store metadata without handshake protocols" ) - if public: - if multi_use: - raise OutOfBandManagerError( - "Cannot create public invitation with multi_use" - ) - if metadata: - raise OutOfBandManagerError( - "Cannot store metadata on public invitations" - ) if attachments and multi_use: raise OutOfBandManagerError( @@ -223,6 +214,7 @@ async def create_invitation( async with self.profile.session() as session: wallet = session.inject(BaseWallet) public_did = await wallet.get_public_did() + if not public_did: raise OutOfBandManagerError( "Cannot create public invitation with no public DID" @@ -246,9 +238,15 @@ async def create_invitation( # Only create connection record if hanshake_protocols is defined if handshake_protocols: + invitation_mode = ( + ConnRecord.INVITATION_MODE_MULTI + if multi_use + else ConnRecord.INVITATION_MODE_ONCE + ) conn_rec = ConnRecord( # create connection record invitation_key=public_did.verkey, invitation_msg_id=invi_msg._id, + invitation_mode=invitation_mode, their_role=ConnRecord.Role.REQUESTER.rfc23, state=ConnRecord.State.INVITATION.rfc23, accept=ConnRecord.ACCEPT_AUTO @@ -261,6 +259,12 @@ async def create_invitation( async with self.profile.session() as session: await conn_rec.save(session, reason="Created new invitation") await conn_rec.attach_invitation(session, invi_msg) + + await conn_rec.attach_invitation(session, invi_msg) + + if metadata: + for key, value in metadata.items(): + await conn_rec.metadata_set(session, key, value) else: our_service = ServiceDecorator( recipient_keys=[our_recipient_key], @@ -269,7 +273,6 @@ async def create_invitation( ).serialize() else: - if not my_endpoint: my_endpoint = self.profile.settings.get("default_endpoint") diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py b/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py index e461201975..be3c0b80a8 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py @@ -96,13 +96,17 @@ def _serialize(self, value, attr, obj, **kwargs): def _deserialize(self, value, attr, data, **kwargs): if isinstance(value, dict): return Service.deserialize(value) + elif isinstance(value, Service): + return value elif isinstance(value, str): - if bool(DIDValidation.PATTERN.match(value)): - return value - else: + if not DIDValidation.PATTERN.match(value): raise ValidationError( "Service item must be a valid decentralized identifier (DID)" ) + return value + raise ValidationError( + "Service item must be a valid decentralized identifier (DID) or object" + ) class InvitationMessage(AgentMessage): @@ -221,9 +225,6 @@ class Meta: fields.Str( description="Handshake protocol", example=DIDCommPrefix.qualify_current(HSProto.RFC23.name), - validate=lambda hsp: ( - DIDCommPrefix.unqualify(hsp) in [p.name for p in HSProto] - ), ), required=False, ) @@ -276,13 +277,10 @@ def validate_fields(self, data, **kwargs): """ handshake_protocols = data.get("handshake_protocols") requests_attach = data.get("requests_attach") - if not ( - (handshake_protocols and len(handshake_protocols) > 0) - or (requests_attach and len(requests_attach) > 0) - ): + if not handshake_protocols and not requests_attach: raise ValidationError( "Model must include non-empty " - "handshake_protocols or requests_attach or both" + "handshake_protocols or requests~attach or both" ) # services = data.get("services") diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py b/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py index 801b17680e..004c7d5ca5 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py @@ -139,9 +139,8 @@ def test_invalid_invi_wrong_type_services(self): "services": [123], } - invi_schema = InvitationMessageSchema() - with pytest.raises(test_module.ValidationError): - invi_schema.validate_fields(obj_x) + errs = InvitationMessageSchema().validate(obj_x) + assert errs and "services" in errs def test_assign_msg_type_version_to_model_inst(self): test_msg = InvitationMessage() diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/models/oob_record.py b/aries_cloudagent/protocols/out_of_band/v1_0/models/oob_record.py index 69f668335b..e550d9eb54 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/models/oob_record.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/models/oob_record.py @@ -3,7 +3,7 @@ import json from typing import Any, Mapping, Optional, Union -from marshmallow import fields +from marshmallow import fields, validate from .....connections.models.conn_record import ConnRecord from .....core.profile import ProfileSession @@ -248,6 +248,9 @@ class Meta: required=True, description="Out of band message exchange state", example=OobRecord.STATE_AWAIT_RESPONSE, + validate=validate.OneOf( + OobRecord.get_attributes_by_prefix("STATE_", walk_mro=True) + ), ) invi_msg_id = fields.Str( required=True, @@ -287,4 +290,7 @@ class Meta: description="OOB Role", required=False, example=OobRecord.ROLE_RECEIVER, + validate=validate.OneOf( + OobRecord.get_attributes_by_prefix("ROLE_", walk_mro=False) + ), ) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py index 07010c744c..4cbb29ab65 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/tests/test_manager.py @@ -91,7 +91,6 @@ class TestConfig: - test_did = "55GkHamhTU1ZbTbV2ab9DE" test_verkey = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" test_endpoint = "http://localhost" @@ -712,7 +711,6 @@ async def test_create_invitation_public_x_multi_use(self): assert "Cannot create public invitation with" in str(context.exception) async def test_create_invitation_requests_attach_x_multi_use(self): - with self.assertRaises(OutOfBandManagerError) as context: await self.manager.create_invitation( public=False, @@ -1094,7 +1092,6 @@ async def test_receive_reuse_accepted_x(self): mock_retrieve_oob.side_effect = (StorageNotFoundError,) with self.assertRaises(test_module.OutOfBandManagerError) as err: - await self.manager.receive_reuse_accepted_message( reuse_msg_accepted, receipt, self.test_conn_rec ) diff --git a/aries_cloudagent/protocols/present_proof/dif/pres_exch.py b/aries_cloudagent/protocols/present_proof/dif/pres_exch.py index 3155d28860..5b9cdfe1de 100644 --- a/aries_cloudagent/protocols/present_proof/dif/pres_exch.py +++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch.py @@ -837,7 +837,7 @@ class Meta: fmt = fields.Str( description="Format", required=False, - default="ldp_vp", + default="ldp_vc", data_key="format", ) path = fields.Str( diff --git a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py index 9d4344ee6a..76cde3729d 100644 --- a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py @@ -1377,7 +1377,7 @@ async def merge( if f"{cred_id}-{cred_id}" not in dict_of_descriptors: descriptor_map = InputDescriptorMapping( id=desc_id, - fmt="ldp_vp", + fmt="ldp_vc", path=(f"$.verifiableCredential[{dict_of_creds[cred_id]}]"), ) descriptors.append(descriptor_map) diff --git a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py index 2709024263..b8515188cd 100644 --- a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py +++ b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py @@ -363,7 +363,7 @@ def test_verifiable_presentation_wrapper(self): "descriptor_map": [ { "id": "citizenship_input_1", - "format": "ldp_vp", + "format": "ldp_vc", "path": "$.verifiableCredential[0]", } ], diff --git a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py index 72525e2fe8..87597359cc 100644 --- a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py @@ -15,7 +15,7 @@ from .....storage.vc_holder.vc_record import VCRecord from .....wallet.base import BaseWallet, DIDInfo from .....wallet.crypto import KeyType -from .....wallet.did_method import SOV, KEY +from .....wallet.did_method import SOV, KEY, DIDMethods from .....wallet.error import WalletNotFoundError from .....vc.ld_proofs import ( BbsBlsSignature2020, @@ -69,7 +69,7 @@ def event_loop(request): @pytest.fixture(scope="class") def profile(): - profile = InMemoryProfile.test_profile() + profile = InMemoryProfile.test_profile(bind={DIDMethods: DIDMethods()}) context = profile.context context.injector.bind_instance(DIDResolver, DIDResolver([])) context.injector.bind_instance(DocumentLoader, custom_document_loader) diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index 51c6c264a9..4bff88cc89 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -194,7 +194,7 @@ async def return_presentation( f"Failed to create revocation state: {e.error_code}, {e.message}" ) raise e - for (referent, precis) in requested_referents.items(): + for referent, precis in requested_referents.items(): if "timestamp" not in precis: continue if referent in requested_credentials["requested_attributes"]: diff --git a/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py b/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py index add1efa8e9..83329461a4 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py @@ -102,7 +102,6 @@ async def test_called(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V10PresentationExchange", autospec=True ) as mock_pres_ex_cls: - mock_pres_ex_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance ) @@ -163,7 +162,6 @@ async def test_called_not_found(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V10PresentationExchange", autospec=True ) as mock_pres_ex_cls: - mock_pres_ex_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( side_effect=StorageNotFoundError ) @@ -335,7 +333,6 @@ async def test_called_auto_present_x(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V10PresentationExchange", autospec=True ) as mock_pres_ex_cls: - mock_pres_ex_cls.return_value = mock_px_rec mock_pres_ex_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=mock_px_rec @@ -415,7 +412,6 @@ async def test_called_auto_present_no_preview(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V10PresentationExchange", autospec=True ) as mock_pres_ex_cls: - mock_pres_ex_cls.return_value = px_rec_instance mock_pres_ex_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance @@ -491,7 +487,6 @@ async def test_called_auto_present_pred_no_match(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V10PresentationExchange", autospec=True ) as mock_pres_ex_cls: - mock_pres_ex_cls.return_value = px_rec_instance mock_pres_ex_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance @@ -565,7 +560,6 @@ async def test_called_auto_present_pred_single_match(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V10PresentationExchange", autospec=True ) as mock_pres_ex_cls: - mock_pres_ex_cls.return_value = px_rec_instance mock_pres_ex_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance @@ -646,7 +640,6 @@ async def test_called_auto_present_pred_multi_match(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V10PresentationExchange", autospec=True ) as mock_pres_ex_cls: - mock_pres_ex_cls.return_value = px_rec_instance mock_pres_ex_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance @@ -778,7 +771,6 @@ async def test_called_auto_present_multi_cred_match_reft(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V10PresentationExchange", autospec=True ) as mock_pres_ex_cls: - mock_pres_ex_cls.return_value = px_rec_instance mock_pres_ex_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance @@ -892,7 +884,6 @@ async def test_called_auto_present_bait_and_switch(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V10PresentationExchange", autospec=True ) as mock_pres_ex_cls: - mock_pres_ex_cls.return_value = px_rec_instance mock_pres_ex_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance diff --git a/aries_cloudagent/protocols/present_proof/v1_0/manager.py b/aries_cloudagent/protocols/present_proof/v1_0/manager.py index 2f3af46da5..7453506a45 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/manager.py @@ -354,7 +354,7 @@ async def receive_presentation( presentation_preview = exchange_pres_proposal.presentation_proposal proof_req = presentation_exchange_record._presentation_request.ser - for (reft, attr_spec) in presentation["requested_proof"][ + for reft, attr_spec in presentation["requested_proof"][ "revealed_attrs" ].items(): name = proof_req["requested_attributes"][reft]["name"] @@ -540,12 +540,10 @@ async def receive_problem_report( """ # FIXME use transaction, fetch for_update async with self._profile.session() as session: - pres_ex_record = await ( - V10PresentationExchange.retrieve_by_tag_filter( - session, - {"thread_id": message._thread_id}, - {"connection_id": connection_id}, - ) + pres_ex_record = await V10PresentationExchange.retrieve_by_tag_filter( + session, + {"thread_id": message._thread_id}, + {"connection_id": connection_id}, ) pres_ex_record.state = V10PresentationExchange.STATE_ABANDONED diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py index 555c079b58..77e3ea1ca7 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py @@ -67,7 +67,6 @@ async def test_presentation_exchange_list(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -101,7 +100,6 @@ async def test_presentation_exchange_list_x(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -122,7 +120,6 @@ async def test_presentation_exchange_credentials_list_not_found(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -158,7 +155,6 @@ async def test_presentation_exchange_credentials_x(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -191,7 +187,6 @@ async def test_presentation_exchange_credentials_list_single_referent(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -229,7 +224,6 @@ async def test_presentation_exchange_credentials_list_multiple_referents(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -253,7 +247,6 @@ async def test_presentation_exchange_retrieve(self): ), autospec=True, ) as mock_pres_ex: - # Since we are mocking import importlib.reload(test_module) @@ -280,7 +273,6 @@ async def test_presentation_exchange_retrieve_not_found(self): ), autospec=True, ) as mock_pres_ex: - # Since we are mocking import importlib.reload(test_module) @@ -307,7 +299,6 @@ async def test_presentation_exchange_retrieve_x(self): ), autospec=True, ) as mock_pres_ex: - # Since we are mocking import importlib.reload(test_module) @@ -334,7 +325,6 @@ async def test_presentation_exchange_send_proposal(self): "aries_cloudagent.indy.models.pres_preview.IndyPresPreview", autospec=True, ) as mock_preview: - # Since we are mocking import importlib.reload(test_module) @@ -360,7 +350,6 @@ async def test_presentation_exchange_send_proposal_no_conn_record(self): "aries_cloudagent.connections.models.conn_record.ConnRecord", autospec=True, ) as mock_connection_record: - # Since we are mocking import importlib.reload(test_module) @@ -388,7 +377,6 @@ async def test_presentation_exchange_send_proposal_not_ready(self): ), autospec=True, ) as mock_proposal: - # Since we are mocking import importlib.reload(test_module) @@ -411,7 +399,6 @@ async def test_presentation_exchange_send_proposal_x(self): "aries_cloudagent.indy.models.pres_preview.IndyPresPreview", autospec=True, ) as mock_preview: - # Since we are mocking import importlib.reload(test_module) @@ -456,7 +443,6 @@ async def test_presentation_exchange_create_request(self): "aries_cloudagent.indy.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce: - # Since we are mocking import importlib.reload(test_module) @@ -510,7 +496,6 @@ async def test_presentation_exchange_create_request_x(self): "aries_cloudagent.indy.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce: - # Since we are mocking import importlib.reload(test_module) @@ -562,7 +547,6 @@ async def test_presentation_exchange_send_free_request(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -601,7 +585,6 @@ async def test_presentation_exchange_send_free_request_not_found(self): "aries_cloudagent.connections.models.conn_record.ConnRecord", autospec=True, ) as mock_connection_record: - # Since we are mocking import importlib.reload(test_module) @@ -620,7 +603,6 @@ async def test_presentation_exchange_send_free_request_not_ready(self): "aries_cloudagent.connections.models.conn_record.ConnRecord", autospec=True, ) as mock_connection_record: - # Since we are mocking import importlib.reload(test_module) @@ -664,7 +646,6 @@ async def test_presentation_exchange_send_free_request_x(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -731,7 +712,6 @@ async def test_presentation_exchange_send_bound_request(self): "models.presentation_exchange.V10PresentationExchange", autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -793,7 +773,6 @@ async def test_presentation_exchange_send_bound_request_not_found(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -838,7 +817,6 @@ async def test_presentation_exchange_send_bound_request_not_ready(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -883,7 +861,6 @@ async def test_presentation_exchange_send_bound_request_bad_state(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -923,7 +900,6 @@ async def test_presentation_exchange_send_bound_request_x(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -998,7 +974,6 @@ async def test_presentation_exchange_send_presentation(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1075,7 +1050,6 @@ async def test_presentation_exchange_send_presentation_not_found(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1120,7 +1094,6 @@ async def test_presentation_exchange_send_presentation_not_ready(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1150,7 +1123,6 @@ async def test_presentation_exchange_send_presentation_bad_state(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1196,7 +1168,6 @@ async def test_presentation_exchange_send_presentation_x(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1251,7 +1222,6 @@ async def test_presentation_exchange_verify_presentation(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1311,7 +1281,6 @@ async def test_presentation_exchange_verify_presentation_bad_state(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1354,7 +1323,6 @@ async def test_presentation_exchange_verify_presentation_x(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1412,7 +1380,6 @@ async def test_presentation_exchange_problem_report(self): ) as mock_problem_report, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - # Since we are mocking import importlib.reload(test_module) @@ -1444,7 +1411,6 @@ async def test_presentation_exchange_problem_report_bad_pres_ex_id(self): ), autospec=True, ) as mock_pres_ex: - # Since we are mocking import importlib.reload(test_module) @@ -1474,7 +1440,6 @@ async def test_presentation_exchange_problem_report_x(self): ) as mock_problem_report, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - # Since we are mocking import importlib.reload(test_module) mock_pres_ex.retrieve_by_id = async_mock.CoroutineMock( @@ -1494,7 +1459,6 @@ async def test_presentation_exchange_remove(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1523,7 +1487,6 @@ async def test_presentation_exchange_remove_not_found(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) @@ -1545,7 +1508,6 @@ async def test_presentation_exchange_remove_x(self): ), autospec=True, ) as mock_presentation_exchange: - # Since we are mocking import importlib.reload(test_module) diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py index 9234d2e3e1..5d7eb47a39 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py @@ -268,7 +268,7 @@ "descriptor_map": [ { "id": "citizenship_input_1", - "format": "ldp_vp", + "format": "ldp_vc", "path": "$.verifiableCredential[0]", } ], diff --git a/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py index bd1e3d2174..c0e28fa043 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py @@ -219,7 +219,6 @@ async def test_called(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_px_rec_cls: - mock_px_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance ) @@ -279,7 +278,6 @@ async def test_called_not_found(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_px_rec_cls: - mock_px_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( side_effect=StorageNotFoundError ) @@ -349,7 +347,6 @@ async def test_called_auto_present_x(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_pres_ex_rec_cls: - mock_pres_ex_rec_cls.return_value = mock_px_rec mock_pres_ex_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=mock_px_rec @@ -419,7 +416,6 @@ async def test_called_auto_present_indy(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_pres_ex_rec_cls: - mock_pres_ex_rec_cls.return_value = mock_px_rec mock_pres_ex_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=mock_px_rec @@ -494,7 +490,6 @@ async def test_called_auto_present_dif(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_pres_ex_rec_cls: - mock_pres_ex_rec_cls.return_value = px_rec_instance mock_pres_ex_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance @@ -565,7 +560,6 @@ async def test_called_auto_present_no_preview(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_pres_ex_rec_cls: - mock_pres_ex_rec_cls.return_value = px_rec_instance mock_pres_ex_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance @@ -640,7 +634,6 @@ async def test_called_auto_present_pred_no_match(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_pres_ex_rec_cls: - mock_pres_ex_rec_cls.return_value = mock_px_rec mock_pres_ex_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=mock_px_rec @@ -705,7 +698,6 @@ async def test_called_auto_present_pred_single_match(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_pres_ex_rec_cls: - mock_pres_ex_rec_cls.return_value = px_rec_instance mock_pres_ex_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance @@ -777,7 +769,6 @@ async def test_called_auto_present_pred_multi_match(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_pres_ex_rec_cls: - mock_pres_ex_rec_cls.return_value = px_rec_instance mock_pres_ex_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance @@ -894,7 +885,6 @@ async def test_called_auto_present_multi_cred_match_reft(self): ) as mock_pres_mgr, async_mock.patch.object( test_module, "V20PresExRecord", autospec=True ) as mock_pres_ex_rec_cls: - mock_pres_ex_rec_cls.return_value = px_rec_instance mock_pres_ex_rec_cls.retrieve_by_tag_filter = async_mock.CoroutineMock( return_value=px_rec_instance diff --git a/aries_cloudagent/protocols/present_proof/v2_0/manager.py b/aries_cloudagent/protocols/present_proof/v2_0/manager.py index ef542f0c29..ea65e37c64 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/manager.py @@ -472,12 +472,10 @@ async def receive_problem_report( """ # FIXME use transaction, fetch for_update async with self._profile.session() as session: - pres_ex_record = await ( - V20PresExRecord.retrieve_by_tag_filter( - session, - {"thread_id": message._thread_id}, - {"connection_id": connection_id}, - ) + pres_ex_record = await V20PresExRecord.retrieve_by_tag_filter( + session, + {"thread_id": message._thread_id}, + {"connection_id": connection_id}, ) pres_ex_record.state = V20PresExRecord.STATE_ABANDONED diff --git a/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py b/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py index cc314aa9db..4b5e22a10b 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py @@ -244,11 +244,7 @@ class Meta: description="Present-proof exchange initiator: self or external", example=V20PresExRecord.INITIATOR_SELF, validate=validate.OneOf( - [ - getattr(V20PresExRecord, m) - for m in vars(V20PresExRecord) - if m.startswith("INITIATOR_") - ] + V20PresExRecord.get_attributes_by_prefix("INITIATOR_", walk_mro=False) ), ) role = fields.Str( @@ -256,22 +252,14 @@ class Meta: description="Present-proof exchange role: prover or verifier", example=V20PresExRecord.ROLE_PROVER, validate=validate.OneOf( - [ - getattr(V20PresExRecord, m) - for m in vars(V20PresExRecord) - if m.startswith("ROLE_") - ] + V20PresExRecord.get_attributes_by_prefix("ROLE_", walk_mro=False) ), ) state = fields.Str( required=False, description="Present-proof exchange state", validate=validate.OneOf( - [ - getattr(V20PresExRecord, m) - for m in vars(V20PresExRecord) - if m.startswith("STATE_") - ] + V20PresExRecord.get_attributes_by_prefix("STATE_", walk_mro=True) ), ) pres_proposal = fields.Nested( diff --git a/aries_cloudagent/protocols/present_proof/v2_0/routes.py b/aries_cloudagent/protocols/present_proof/v2_0/routes.py index 02d5b7fff3..68a55087ef 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/routes.py @@ -331,7 +331,7 @@ async def _add_nonce(indy_proof_request: Mapping) -> Mapping: def _formats_attach(by_format: Mapping, msg_type: str, spec: str) -> Mapping: """Break out formats and proposals/requests/presentations for v2.0 messages.""" attach = [] - for (fmt_api, item_by_fmt) in by_format.items(): + for fmt_api, item_by_fmt in by_format.items(): if fmt_api == V20PresFormat.Format.INDY.api: attach.append( AttachDecorator.data_base64(mapping=item_by_fmt, ident=fmt_api) @@ -1090,7 +1090,6 @@ async def present_proof_send_presentation(request: web.BaseRequest): if pres_ex_record.connection_id: try: async with profile.session() as session: - conn_record = await ConnRecord.retrieve_by_id( session, pres_ex_record.connection_id ) diff --git a/aries_cloudagent/protocols/routing/v1_0/messages/tests/test_forward.py b/aries_cloudagent/protocols/routing/v1_0/messages/tests/test_forward.py index 4988e052b0..2dea1d988d 100644 --- a/aries_cloudagent/protocols/routing/v1_0/messages/tests/test_forward.py +++ b/aries_cloudagent/protocols/routing/v1_0/messages/tests/test_forward.py @@ -9,7 +9,6 @@ class TestForward(TestCase): - to = "to" msg = {"msg": "body"} diff --git a/aries_cloudagent/resolver/default/indy.py b/aries_cloudagent/resolver/default/indy.py index ad34b90487..ce18f75c67 100644 --- a/aries_cloudagent/resolver/default/indy.py +++ b/aries_cloudagent/resolver/default/indy.py @@ -11,6 +11,7 @@ from ...config.injection_context import InjectionContext from ...core.profile import Profile +from ...did.did_key import DIDKey from ...ledger.endpoint_type import EndpointType from ...ledger.error import LedgerError from ...ledger.multiple_ledger.ledger_requests_executor import ( @@ -19,7 +20,7 @@ ) from ...messaging.valid import IndyDID from ...multitenant.base import BaseMultitenantManager - +from ...wallet.key_type import ED25519 from ..base import BaseDIDResolver, DIDNotFound, ResolverError, ResolverType LOGGER = logging.getLogger(__name__) @@ -29,6 +30,26 @@ class NoIndyLedger(ResolverError): """Raised when there is no Indy ledger instance configured.""" +def _routing_keys_as_did_key_urls(routing_keys: Sequence[str]) -> Sequence[str]: + """Convert raw base58 keys to did:key values. + + If a did:key is passed in, convert to a did:key URL. + """ + + did_key_urls = [] + for routing_key in routing_keys: + if not routing_key.startswith("did:key:"): + did_key_urls.append(DIDKey.from_public_key_b58(routing_key, ED25519).key_id) + else: + if "#" not in routing_key: + did_key_urls.append( + f"{routing_key}#{DIDKey.from_did(routing_key).fingerprint}" + ) + else: + return routing_keys + return did_key_urls + + class IndyDIDResolver(BaseDIDResolver): """Indy DID Resolver.""" @@ -101,7 +122,7 @@ def add_services( type_=self.SERVICE_TYPE_DID_COMMUNICATION, service_endpoint=endpoint, priority=1, - routing_keys=routing_keys, + routing_keys=_routing_keys_as_did_key_urls(routing_keys), recipient_keys=[recipient_key.id], accept=( service_accept if service_accept else ["didcomm/aip2;env=rfc19"] @@ -114,7 +135,7 @@ def add_services( type_=self.SERVICE_TYPE_DIDCOMM, service_endpoint=endpoint, recipient_keys=[recipient_key.id], - routing_keys=routing_keys, + routing_keys=_routing_keys_as_did_key_urls(routing_keys), # CHECKME # accept=(service_accept if service_accept else ["didcomm/v2"]), accept=["didcomm/v2"], diff --git a/aries_cloudagent/resolver/default/tests/test_indy.py b/aries_cloudagent/resolver/default/tests/test_indy.py index c2c53a3e92..6287fb5352 100644 --- a/aries_cloudagent/resolver/default/tests/test_indy.py +++ b/aries_cloudagent/resolver/default/tests/test_indy.py @@ -17,7 +17,7 @@ from ....multitenant.manager import MultitenantManager from ...base import DIDNotFound, ResolverError -from ..indy import IndyDIDResolver +from ..indy import IndyDIDResolver, _routing_keys_as_did_key_urls # pylint: disable=W0621 TEST_DID0 = "did:sov:WgWxqztrNooG92RXvxSTWv" @@ -127,7 +127,7 @@ async def test_supports_updated_did_sov_rules( """Test that new attrib structure is supported.""" example = { "endpoint": "https://example.com/endpoint", - "routingKeys": ["a-routing-key"], + "routingKeys": ["HQhjaj4mcaS3Xci27a9QhnBrNpS91VNFUU4TDrtMxa9j"], "types": ["DIDComm", "did-communication", "endpoint"], "profile": "https://example.com", "linked_domains": "https://example.com", @@ -177,3 +177,18 @@ async def test_supports_updated_did_sov_rules_no_endpoint_url( ) def test_process_endpoint_types(self, resolver: IndyDIDResolver, types, result): assert resolver.process_endpoint_types(types) == result + + @pytest.mark.parametrize( + "keys", + [ + ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], + ["did:key:z6MkgzZFYHiH9RhyMmkoyvNvVwnvgLxkVrJbureLx9HXsuKA"], + [ + "did:key:z6MkgzZFYHiH9RhyMmkoyvNvVwnvgLxkVrJbureLx9HXsuKA#z6MkgzZFYHiH9RhyMmkoyvNvVwnvgLxkVrJbureLx9HXsuKA" + ], + ], + ) + def test_routing_keys_as_did_key_urls(self, keys): + for key in _routing_keys_as_did_key_urls(keys): + assert key.startswith("did:key:") + assert "#" in key diff --git a/aries_cloudagent/resolver/default/tests/test_universal.py b/aries_cloudagent/resolver/default/tests/test_universal.py index 381e9194e3..8d9df9638d 100644 --- a/aries_cloudagent/resolver/default/tests/test_universal.py +++ b/aries_cloudagent/resolver/default/tests/test_universal.py @@ -54,7 +54,7 @@ class MockClientSession: def __init__(self, response: MockResponse = None): self.response = response - def __call__(self): + def __call__(self, headers): return self async def __aenter__(self): @@ -101,7 +101,7 @@ async def test_resolve_not_found(profile, resolver, mock_client_session): @pytest.mark.asyncio -async def test_resolve_unexpeceted_status(profile, resolver, mock_client_session): +async def test_resolve_unexpected_status(profile, resolver, mock_client_session): mock_client_session.response = MockResponse( 500, "Server failed to complete request" ) @@ -112,21 +112,21 @@ async def test_resolve_unexpeceted_status(profile, resolver, mock_client_session @pytest.mark.asyncio async def test_fetch_resolver_props(mock_client_session: MockClientSession): mock_client_session.response = MockResponse(200, {"test": "json"}) - assert await test_module._fetch_resolver_props("test") == {"test": "json"} + assert await UniversalResolver()._fetch_resolver_props() == {"test": "json"} mock_client_session.response = MockResponse(404, "Not found") with pytest.raises(ResolverError): - await test_module._fetch_resolver_props("test") + await UniversalResolver()._fetch_resolver_props() @pytest.mark.asyncio async def test_get_supported_did_regex(): props = {"example": {"http": {"pattern": "match a test string"}}} with async_mock.patch.object( - test_module, + UniversalResolver, "_fetch_resolver_props", async_mock.CoroutineMock(return_value=props), ): - pattern = await test_module._get_supported_did_regex("test") + pattern = await UniversalResolver()._get_supported_did_regex() assert pattern.fullmatch("match a test string") @@ -169,7 +169,7 @@ async def test_setup_endpoint_set(resolver: UniversalResolver): context = async_mock.MagicMock() context.settings = settings with async_mock.patch.object( - test_module, + UniversalResolver, "_get_supported_did_regex", async_mock.CoroutineMock(return_value="pattern"), ): @@ -189,7 +189,7 @@ async def test_setup_endpoint_default(resolver: UniversalResolver): context = async_mock.MagicMock() context.settings = settings with async_mock.patch.object( - test_module, + UniversalResolver, "_get_supported_did_regex", async_mock.CoroutineMock(return_value="pattern"), ): @@ -205,7 +205,7 @@ async def test_setup_endpoint_unset(resolver: UniversalResolver): context = async_mock.MagicMock() context.settings = settings with async_mock.patch.object( - test_module, + UniversalResolver, "_get_supported_did_regex", async_mock.CoroutineMock(return_value="pattern"), ): diff --git a/aries_cloudagent/resolver/default/universal.py b/aries_cloudagent/resolver/default/universal.py index 85ca9e2dba..2efee46009 100644 --- a/aries_cloudagent/resolver/default/universal.py +++ b/aries_cloudagent/resolver/default/universal.py @@ -11,25 +11,7 @@ from ..base import BaseDIDResolver, DIDNotFound, ResolverError, ResolverType LOGGER = logging.getLogger(__name__) -DEFAULT_ENDPOINT = "https://dev.uniresolver.io" - - -async def _fetch_resolver_props(endpoint: str) -> dict: - """Retrieve universal resolver properties.""" - async with aiohttp.ClientSession() as session: - async with session.get(f"{endpoint}/1.0/properties/") as resp: - if resp.status >= 200 and resp.status < 400: - return await resp.json() - raise ResolverError( - "Failed to retrieve resolver properties: " + await resp.text() - ) - - -async def _get_supported_did_regex(endpoint: str) -> Pattern: - props = await _fetch_resolver_props(endpoint) - return _compile_supported_did_regex( - driver["http"]["pattern"] for driver in props.values() - ) +DEFAULT_ENDPOINT = "https://dev.uniresolver.io/1.0" def _compile_supported_did_regex(patterns: Iterable[Union[str, Pattern]]): @@ -54,25 +36,37 @@ def __init__( *, endpoint: Optional[str] = None, supported_did_regex: Optional[Pattern] = None, + bearer_token: Optional[str] = None, ): """Initialize UniversalResolver.""" super().__init__(ResolverType.NON_NATIVE) self._endpoint = endpoint self._supported_did_regex = supported_did_regex + self.__default_headers = ( + {"Authorization": f"Bearer {bearer_token}"} if bearer_token else {} + ) + async def setup(self, context: InjectionContext): - """Preform setup, populate supported method list, configuration.""" + """Perform setup, populate supported method list, configuration.""" + + # configure endpoint endpoint = context.settings.get_str("resolver.universal") if endpoint == "DEFAULT" or not endpoint: endpoint = DEFAULT_ENDPOINT + self._endpoint = endpoint + + # configure authorization + token = context.settings.get_str("resolver.universal.token") + self.__default_headers = {"Authorization": f"Bearer {token}"} if token else {} + # configure supported methods supported = context.settings.get("resolver.universal.supported") if supported is None: - supported_did_regex = await _get_supported_did_regex(endpoint) + supported_did_regex = await self._get_supported_did_regex() else: supported_did_regex = _compile_supported_did_regex(supported) - self._endpoint = endpoint self._supported_did_regex = supported_did_regex @property @@ -91,8 +85,8 @@ async def _resolve( ) -> dict: """Resolve DID through remote universal resolver.""" - async with aiohttp.ClientSession() as session: - async with session.get(f"{self._endpoint}/1.0/identifiers/{did}") as resp: + async with aiohttp.ClientSession(headers=self.__default_headers) as session: + async with session.get(f"{self._endpoint}/identifiers/{did}") as resp: if resp.status == 200: doc = await resp.json() did_doc = doc["didDocument"] @@ -103,5 +97,21 @@ async def _resolve( text = await resp.text() raise ResolverError( - f"Unexecpted status from universal resolver ({resp.status}): {text}" + f"Unexpected status from universal resolver ({resp.status}): {text}" + ) + + async def _fetch_resolver_props(self) -> dict: + """Retrieve universal resolver properties.""" + async with aiohttp.ClientSession(headers=self.__default_headers) as session: + async with session.get(f"{self._endpoint}/properties/") as resp: + if 200 <= resp.status < 400: + return await resp.json() + raise ResolverError( + "Failed to retrieve resolver properties: " + await resp.text() ) + + async def _get_supported_did_regex(self) -> Pattern: + props = await self._fetch_resolver_props() + return _compile_supported_did_regex( + driver["http"]["pattern"] for driver in props.values() + ) diff --git a/aries_cloudagent/resolver/did_resolver.py b/aries_cloudagent/resolver/did_resolver.py index a2bdf48cbc..b523b59607 100644 --- a/aries_cloudagent/resolver/did_resolver.py +++ b/aries_cloudagent/resolver/did_resolver.py @@ -8,10 +8,11 @@ from datetime import datetime from itertools import chain import logging -from typing import Optional, List, Sequence, Tuple, Text, Type, TypeVar, Union +from typing import List, Optional, Sequence, Text, Tuple, Union -from pydid import DID, DIDError, DIDUrl, Resource, NonconformantDocument -from pydid.doc.doc import IDNotFoundError +from pydid import DID, DIDError, DIDUrl, Resource +import pydid +from pydid.doc.doc import BaseDIDDocument, IDNotFoundError from ..core.profile import Profile from .base import ( @@ -26,9 +27,6 @@ LOGGER = logging.getLogger(__name__) -ResourceType = TypeVar("ResourceType", bound=Resource) - - class DIDResolver: """did resolver singleton.""" @@ -115,8 +113,12 @@ async def _match_did_to_resolver( return resolvers async def dereference( - self, profile: Profile, did_url: str, *, cls: Type[ResourceType] = Resource - ) -> ResourceType: + self, + profile: Profile, + did_url: str, + *, + document: Optional[BaseDIDDocument] = None, + ) -> Resource: """Dereference a DID URL to its corresponding DID Doc object.""" # TODO Use cached DID Docs when possible try: @@ -128,12 +130,15 @@ async def dereference( "Failed to parse DID URL from {}".format(did_url) ) from err - doc_dict = await self.resolve(profile, parsed.did) - # Use non-conformant doc as the "least common denominator" + if document and parsed.did != document.id: + document = None + + if not document: + doc_dict = await self.resolve(profile, parsed.did) + document = pydid.deserialize_document(doc_dict) + try: - return NonconformantDocument.deserialize(doc_dict).dereference_as( - cls, parsed - ) + return document.dereference(parsed) except IDNotFoundError as error: raise ResolverError( "Failed to dereference DID URL: {}".format(error) diff --git a/aries_cloudagent/resolver/tests/test_did_resolver.py b/aries_cloudagent/resolver/tests/test_did_resolver.py index 2bcbb4ed42..58c459b307 100644 --- a/aries_cloudagent/resolver/tests/test_did_resolver.py +++ b/aries_cloudagent/resolver/tests/test_did_resolver.py @@ -7,7 +7,7 @@ import pytest from asynctest import mock as async_mock -from pydid import DID, DIDDocument, VerificationMethod +from pydid import DID, DIDDocument, VerificationMethod, BasicDIDDocument from ..base import ( BaseDIDResolver, @@ -153,6 +153,17 @@ async def test_dereference(resolver, profile): assert expected == actual.serialize() +@pytest.mark.asyncio +async def test_dereference_diddoc(resolver, profile): + url = "did:example:1234abcd#4" + doc = BasicDIDDocument( + id="did:example:z6Mkmpe2DyE4NsDiAb58d75hpi1BjqbH6wYMschUkjWDEEuR" + ) + result = await resolver.dereference(profile, url, document=doc) + assert isinstance(result, VerificationMethod) + assert result.id == url + + @pytest.mark.asyncio async def test_dereference_x(resolver, profile): url = "non-did" diff --git a/aries_cloudagent/revocation/tests/test_routes.py b/aries_cloudagent/revocation/tests/test_routes.py index 34cb11d323..d651a442df 100644 --- a/aries_cloudagent/revocation/tests/test_routes.py +++ b/aries_cloudagent/revocation/tests/test_routes.py @@ -87,7 +87,6 @@ async def test_revoke(self): ) as mock_mgr, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_mgr.return_value.revoke_credential = async_mock.CoroutineMock() await test_module.revoke(self.request) @@ -107,7 +106,6 @@ async def test_revoke_by_cred_ex_id(self): ) as mock_mgr, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_mgr.return_value.revoke_credential = async_mock.CoroutineMock() await test_module.revoke(self.request) @@ -128,7 +126,6 @@ async def test_revoke_not_found(self): ) as mock_mgr, async_mock.patch.object( test_module.web, "json_response" ) as mock_response: - mock_mgr.return_value.revoke_credential = async_mock.CoroutineMock( side_effect=test_module.StorageNotFoundError() ) diff --git a/aries_cloudagent/transport/pack_format.py b/aries_cloudagent/transport/pack_format.py index f982748098..76ac70b87f 100644 --- a/aries_cloudagent/transport/pack_format.py +++ b/aries_cloudagent/transport/pack_format.py @@ -69,7 +69,6 @@ async def parse_message( # packed messages are detected by the absence of @type if "@type" not in message_dict: - try: unpack = self.unpack(session, message_body, receipt) message_json = await ( diff --git a/aries_cloudagent/transport/tests/test_pack_format.py b/aries_cloudagent/transport/tests/test_pack_format.py index 02a7712e1c..764e4a54bf 100644 --- a/aries_cloudagent/transport/tests/test_pack_format.py +++ b/aries_cloudagent/transport/tests/test_pack_format.py @@ -8,7 +8,7 @@ from ...protocols.didcomm_prefix import DIDCommPrefix from ...protocols.routing.v1_0.message_types import FORWARD from ...wallet.base import BaseWallet -from ...wallet.did_method import SOV +from ...wallet.did_method import SOV, DIDMethods from ...wallet.error import WalletError from ...wallet.key_type import ED25519 from .. import pack_format as test_module @@ -33,6 +33,7 @@ class TestPackWireFormat(AsyncTestCase): def setUp(self): self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.wallet = self.session.inject(BaseWallet) async def test_errors(self): diff --git a/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py b/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py index 3c3643ec1a..c09f9345dc 100644 --- a/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py +++ b/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py @@ -259,7 +259,6 @@ async def test_verify_presentation_x_no_purpose_challenge(self): ) async def test_sign_presentation_x_no_purpose_challenge(self): - with self.assertRaises(LinkedDataProofException) as context: await sign_presentation( presentation=PRESENTATION_UNSIGNED, diff --git a/aries_cloudagent/wallet/askar.py b/aries_cloudagent/wallet/askar.py index 5d84df9f26..2a0093d276 100644 --- a/aries_cloudagent/wallet/askar.py +++ b/aries_cloudagent/wallet/askar.py @@ -15,9 +15,9 @@ SeedMethod, ) +from .did_parameters_validation import DIDParametersValidation from ..askar.didcomm.v1 import pack_message, unpack_message from ..askar.profile import AskarProfileSession -from ..did.did_key import DIDKey from ..ledger.base import BaseLedger from ..ledger.endpoint_type import EndpointType from ..ledger.error import LedgerConfigError @@ -30,7 +30,7 @@ validate_seed, verify_signed_message, ) -from .did_method import SOV, KEY, DIDMethod, DIDMethods +from .did_method import SOV, DIDMethod, DIDMethods from .error import WalletError, WalletDuplicateError, WalletNotFoundError from .key_type import BLS12381G2, ED25519, KeyType, KeyTypes from .util import b58_to_bytes, bytes_to_b58 @@ -171,29 +171,23 @@ async def create_local_did( WalletError: If there is another backend error """ - - # validate key_type - if not method.supports_key_type(key_type): - raise WalletError( - f"Invalid key type {key_type.key_type}" - f" for DID method {method.method_name}" - ) - - if method == KEY and did: - raise WalletError("Not allowed to set DID for DID method 'key'") + did_validation = DIDParametersValidation( + self._session.context.inject(DIDMethods) + ) + did_validation.validate_key_type(method, key_type) if not metadata: metadata = {} - if method not in [SOV, KEY]: - raise WalletError( - f"Unsupported DID method for askar storage: {method.method_name}" - ) try: keypair = _create_keypair(key_type, seed) verkey_bytes = keypair.get_public_bytes() verkey = bytes_to_b58(verkey_bytes) + did = did_validation.validate_or_derive_did( + method, key_type, verkey_bytes, did + ) + try: await self._session.handle.insert_key( verkey, keypair, metadata=json.dumps(metadata) @@ -205,11 +199,6 @@ async def create_local_did( else: raise WalletError("Error inserting key") from err - if method == KEY: - did = DIDKey.from_public_key(verkey_bytes, key_type).did - elif not did: - did = bytes_to_b58(verkey_bytes[:16]) - item = await self._session.handle.fetch(CATEGORY_DID, did, for_update=True) if item: did_info = item.value_json diff --git a/aries_cloudagent/wallet/bbs.py b/aries_cloudagent/wallet/bbs.py index fd80e24fb7..a204682a73 100644 --- a/aries_cloudagent/wallet/bbs.py +++ b/aries_cloudagent/wallet/bbs.py @@ -102,5 +102,5 @@ def create_bls12381g2_keypair(seed: bytes = None) -> Tuple[bytes, bytes]: try: key_pair = BlsKeyPair.generate_g2(seed) return key_pair.public_key, key_pair.secret_key - except (Exception) as error: + except Exception as error: raise BbsException("Unable to create keypair") from error diff --git a/aries_cloudagent/wallet/did_method.py b/aries_cloudagent/wallet/did_method.py index af971ea019..cbe1361b39 100644 --- a/aries_cloudagent/wallet/did_method.py +++ b/aries_cloudagent/wallet/did_method.py @@ -1,19 +1,35 @@ """did method.py contains registry for did methods.""" +from enum import Enum from typing import Dict, List, Mapping, Optional from .error import BaseError from .key_type import BLS12381G2, ED25519, KeyType +class HolderDefinedDid(Enum): + """Define if a holder can specify its own did for a given method.""" + + NO = "no" # holder CANNOT provide a DID + ALLOWED = "allowed" # holder CAN provide a DID + REQUIRED = "required" # holder MUST provide a DID + + class DIDMethod: """Class to represent a did method.""" - def __init__(self, name: str, key_types: List[KeyType], rotation: bool = False): + def __init__( + self, + name: str, + key_types: List[KeyType], + rotation: bool = False, + holder_defined_did: HolderDefinedDid = HolderDefinedDid.NO, + ): """Construct did method class.""" self._method_name: str = name self._supported_key_types: List[KeyType] = key_types self._supports_rotation: bool = rotation + self._holder_defined_did: HolderDefinedDid = holder_defined_did @property def method_name(self): @@ -34,8 +50,21 @@ def supports_key_type(self, key_type: KeyType) -> bool: """Check whether the current method supports the key type.""" return key_type in self.supported_key_types + def holder_defined_did(self) -> HolderDefinedDid: + """Return the did derivation policy. -SOV = DIDMethod(name="sov", key_types=[ED25519], rotation=True) + eg: did:key DIDs are derived from the verkey -> HolderDefinedDid.NO + eg: did:web DIDs cannot be derived from key material -> HolderDefinedDid.REQUIRED + """ + return self._holder_defined_did + + +SOV = DIDMethod( + name="sov", + key_types=[ED25519], + rotation=True, + holder_defined_did=HolderDefinedDid.ALLOWED, +) KEY = DIDMethod( name="key", key_types=[ED25519, BLS12381G2], @@ -55,7 +84,7 @@ def __init__(self) -> None: def registered(self, method: str) -> bool: """Check for a supported method.""" - return method in list(self._registry.items()) + return method in self._registry.keys() def register(self, method: DIDMethod): """Register a new did method.""" diff --git a/aries_cloudagent/wallet/did_parameters_validation.py b/aries_cloudagent/wallet/did_parameters_validation.py new file mode 100644 index 0000000000..04572c77bf --- /dev/null +++ b/aries_cloudagent/wallet/did_parameters_validation.py @@ -0,0 +1,65 @@ +"""Tooling to validate DID creation parameters.""" + +from typing import Optional + +from aries_cloudagent.did.did_key import DIDKey +from aries_cloudagent.wallet.did_method import ( + DIDMethods, + DIDMethod, + HolderDefinedDid, + KEY, + SOV, +) +from aries_cloudagent.wallet.error import WalletError +from aries_cloudagent.wallet.key_type import KeyType +from aries_cloudagent.wallet.util import bytes_to_b58 + + +class DIDParametersValidation: + """A utility class to check compatibility of provided DID creation parameters.""" + + def __init__(self, did_methods: DIDMethods): + """:param did_methods: DID method registry relevant for the validation.""" + self.did_methods = did_methods + + @staticmethod + def validate_key_type(method: DIDMethod, key_type: KeyType): + """Validate compatibility of the DID method with the desired key type.""" + # validate key_type + if not method.supports_key_type(key_type): + raise WalletError( + f"Invalid key type {key_type.key_type}" + f" for DID method {method.method_name}" + ) + + def validate_or_derive_did( + self, + method: DIDMethod, + key_type: KeyType, + verkey: bytes, + did: Optional[str], + ) -> str: + """ + Validate compatibility of the provided did (if any) with the given DID method. + + If no DID was provided, automatically derive one for methods that support it. + """ + if method.holder_defined_did() == HolderDefinedDid.NO and did: + raise WalletError( + f"Not allowed to set DID for DID method '{method.method_name}'" + ) + elif method.holder_defined_did() == HolderDefinedDid.REQUIRED and not did: + raise WalletError(f"Providing a DID is required {method.method_name}") + elif not self.did_methods.registered(method.method_name): + raise WalletError( + f"Unsupported DID method for current storage: {method.method_name}" + ) + + # We need some did method specific handling. If more did methods + # are added it is probably better create a did method specific handler + elif method == KEY: + return DIDKey.from_public_key(verkey, key_type).did + elif method == SOV: + return bytes_to_b58(verkey[:16]) if not did else did + + return did diff --git a/aries_cloudagent/wallet/in_memory.py b/aries_cloudagent/wallet/in_memory.py index f9bb03a37f..5023e8d6c0 100644 --- a/aries_cloudagent/wallet/in_memory.py +++ b/aries_cloudagent/wallet/in_memory.py @@ -3,8 +3,8 @@ import asyncio from typing import List, Sequence, Tuple, Union +from .did_parameters_validation import DIDParametersValidation from ..core.in_memory import InMemoryProfile -from ..did.did_key import DIDKey from .base import BaseWallet from .crypto import ( @@ -17,7 +17,7 @@ ) from .did_info import KeyInfo, DIDInfo from .did_posture import DIDPosture -from .did_method import SOV, KEY, DIDMethod, DIDMethods +from .did_method import SOV, DIDMethod, DIDMethods from .error import WalletError, WalletDuplicateError, WalletNotFoundError from .key_type import KeyType from .util import b58_to_bytes, bytes_to_b58, random_seed @@ -212,27 +212,15 @@ async def create_local_did( """ seed = validate_seed(seed) or random_seed() - # validate key_type - if not method.supports_key_type(key_type): - raise WalletError( - f"Invalid key type {key_type.key_type} for method {method.method_name}" - ) + did_methods: DIDMethods = self.profile.context.inject(DIDMethods) + did_validation = DIDParametersValidation(did_methods) + + did_validation.validate_key_type(method, key_type) verkey, secret = create_keypair(key_type, seed) verkey_enc = bytes_to_b58(verkey) - # We need some did method specific handling. If more did methods - # are added it is probably better create a did method specific handler - if method == KEY: - if did: - raise WalletError("Not allowed to set DID for DID method 'key'") - - did = DIDKey.from_public_key(verkey, key_type).did - elif method == SOV: - if not did: - did = bytes_to_b58(verkey[:16]) - else: - raise WalletError(f"Unsupported DID method: {method.method_name}") + did = did_validation.validate_or_derive_did(method, key_type, verkey, did) if ( did in self.profile.local_dids diff --git a/aries_cloudagent/wallet/indy.py b/aries_cloudagent/wallet/indy.py index ea27f3132b..78356946bb 100644 --- a/aries_cloudagent/wallet/indy.py +++ b/aries_cloudagent/wallet/indy.py @@ -199,9 +199,9 @@ async def __get_keypair_signing_key(self, verkey: str) -> KeyInfo: metadata=key_pair["metadata"], key_type=key_types.from_key_type(key_pair["key_type"]) or BLS12381G2, ) - except (StorageNotFoundError): + except StorageNotFoundError: raise WalletNotFoundError(f"Unknown key: {verkey}") - except (StorageDuplicateError): + except StorageDuplicateError: raise WalletDuplicateError(f"Multiple keys exist for verkey: {verkey}") async def get_signing_key(self, verkey: str) -> KeyInfo: diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 1c038957dc..462192d03e 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -23,8 +23,8 @@ ENDPOINT, ENDPOINT_TYPE, INDY_DID, - INDY_OR_KEY_DID, INDY_RAW_PUBLIC_KEY, + GENERIC_DID, ) from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager from ..protocols.endorse_transaction.v1_0.manager import ( @@ -38,7 +38,7 @@ from ..storage.error import StorageError, StorageNotFoundError from .base import BaseWallet from .did_info import DIDInfo -from .did_method import SOV, KEY, DIDMethod, DIDMethods +from .did_method import SOV, KEY, DIDMethod, DIDMethods, HolderDefinedDid from .did_posture import DIDPosture from .error import WalletError, WalletNotFoundError from .key_type import BLS12381G2, ED25519, KeyTypes @@ -54,7 +54,7 @@ class WalletModuleResponseSchema(OpenAPISchema): class DIDSchema(OpenAPISchema): """Result schema for a DID.""" - did = fields.Str(description="DID of interest", **INDY_OR_KEY_DID) + did = fields.Str(description="DID of interest", **GENERIC_DID) verkey = fields.Str(description="Public verification key", **INDY_RAW_PUBLIC_KEY) posture = fields.Str( description=( @@ -65,11 +65,7 @@ class DIDSchema(OpenAPISchema): **DID_POSTURE, ) method = fields.Str( - description="Did method associated with the DID", - example=SOV.method_name, - validate=validate.OneOf( - [method.method_name for method in [SOV, KEY]] - ), # TODO: support more methods + description="Did method associated with the DID", example=SOV.method_name ) key_type = fields.Str( description="Key type associated with the DID", @@ -119,7 +115,7 @@ class DIDEndpointSchema(OpenAPISchema): class DIDListQueryStringSchema(OpenAPISchema): """Parameters and validators for DID list request query string.""" - did = fields.Str(description="DID of interest", required=False, **INDY_OR_KEY_DID) + did = fields.Str(description="DID of interest", required=False, **GENERIC_DID) verkey = fields.Str( description="Verification key of interest", required=False, @@ -160,9 +156,18 @@ class DIDCreateOptionsSchema(OpenAPISchema): key_type = fields.Str( required=True, example=ED25519.key_type, + description="Key type to use for the DID keypair. " + + "Validated with the chosen DID method's supported key types.", validate=validate.OneOf([ED25519.key_type, BLS12381G2.key_type]), ) + did = fields.Str( + required=False, + description="Specify final value of the did (including did:: prefix)" + + "if the method supports or requires so.", + **GENERIC_DID, + ) + class DIDCreateSchema(OpenAPISchema): """Parameters and validators for create DID endpoint.""" @@ -171,13 +176,14 @@ class DIDCreateSchema(OpenAPISchema): required=False, default=SOV.method_name, example=SOV.method_name, - validate=validate.OneOf([KEY.method_name, SOV.method_name]), + description="Method for the requested DID." + + "Supported methods are 'key', 'sov', and any other registered method.", ) options = fields.Nested( DIDCreateOptionsSchema, required=False, - description="To define a key type for a did:key", + description="To define a key type and/or a did depending on chosen DID method.", ) seed = fields.Str( @@ -374,14 +380,26 @@ async def wallet_create_did(request: web.BaseRequest): f" support key type {key_type.key_type}" ) ) + + did = body.get("options", {}).get("did") + if method.holder_defined_did() == HolderDefinedDid.NO and did: + raise web.HTTPForbidden( + reason=( + f"method {method.method_name} does not" + f" support user-defined DIDs" + ) + ) + elif method.holder_defined_did() == HolderDefinedDid.REQUIRED and not did: + raise web.HTTPBadRequest( + reason=f"method {method.method_name} requires a user-defined DIDs" + ) + wallet = session.inject_or(BaseWallet) if not wallet: raise web.HTTPForbidden(reason="No wallet available") try: info = await wallet.create_local_did( - method=method, - key_type=key_type, - seed=seed, + method=method, key_type=key_type, seed=seed, did=did ) except WalletError as err: diff --git a/aries_cloudagent/wallet/tests/test_did_parameters_validation.py b/aries_cloudagent/wallet/tests/test_did_parameters_validation.py new file mode 100644 index 0000000000..73565f827f --- /dev/null +++ b/aries_cloudagent/wallet/tests/test_did_parameters_validation.py @@ -0,0 +1,83 @@ +import pytest + +from aries_cloudagent.wallet.did_method import DIDMethods, DIDMethod, HolderDefinedDid +from aries_cloudagent.wallet.did_parameters_validation import DIDParametersValidation +from aries_cloudagent.wallet.error import WalletError +from aries_cloudagent.wallet.key_type import ED25519, BLS12381G1 + + +@pytest.fixture +def did_methods_registry(): + return DIDMethods() + + +def test_validate_key_type_uses_didmethod_when_validating_key_type( + did_methods_registry, +): + # given + ed_method = DIDMethod("ed-method", [ED25519]) + did_methods_registry.register(ed_method) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + assert did_validation.validate_key_type(ed_method, ED25519) is None + with pytest.raises(WalletError): + did_validation.validate_key_type(ed_method, BLS12381G1) + + +def test_validate_key_type_raises_exception_when_validating_unknown_did_method( + did_methods_registry, +): + # given + unknown_method = DIDMethod("unknown", []) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + with pytest.raises(WalletError): + did_validation.validate_key_type(unknown_method, ED25519) + + +def test_set_did_raises_error_when_did_is_provided_and_method_doesnt_allow( + did_methods_registry, +): + # given + ed_method = DIDMethod( + "derived-did", [ED25519], holder_defined_did=HolderDefinedDid.NO + ) + did_methods_registry.register(ed_method) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + with pytest.raises(WalletError): + did_validation.validate_or_derive_did( + ed_method, ED25519, b"verkey", "did:edward:self-defined" + ) + + +def test_validate_or_derive_did_raises_error_when_no_did_is_provided_and_method_requires_one( + did_methods_registry, +): + # given + ed_method = DIDMethod( + "self-defined-did", [ED25519], holder_defined_did=HolderDefinedDid.REQUIRED + ) + did_methods_registry.register(ed_method) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + with pytest.raises(WalletError): + did_validation.validate_or_derive_did(ed_method, ED25519, b"verkey", did=None) + + +def test_validate_or_derive_did_raises_exception_when_validating_unknown_did_method( + did_methods_registry, +): + # given + unknown_method = DIDMethod("unknown", []) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + with pytest.raises(WalletError): + did_validation.validate_or_derive_did( + unknown_method, ED25519, b"verkey", did=None + ) diff --git a/aries_cloudagent/wallet/tests/test_in_memory_wallet.py b/aries_cloudagent/wallet/tests/test_in_memory_wallet.py index ecb3c97263..e9d81ffa27 100644 --- a/aries_cloudagent/wallet/tests/test_in_memory_wallet.py +++ b/aries_cloudagent/wallet/tests/test_in_memory_wallet.py @@ -13,6 +13,7 @@ @pytest.fixture() async def wallet(): profile = InMemoryProfile.test_profile() + profile.context.injector.bind_instance(DIDMethods, DIDMethods()) wallet = InMemoryWallet(profile) yield wallet diff --git a/aries_cloudagent/wallet/tests/test_indy_wallet.py b/aries_cloudagent/wallet/tests/test_indy_wallet.py index 8dfe821e58..47ae72cdec 100644 --- a/aries_cloudagent/wallet/tests/test_indy_wallet.py +++ b/aries_cloudagent/wallet/tests/test_indy_wallet.py @@ -17,7 +17,7 @@ from ...indy.sdk.wallet_setup import IndyWalletConfig from ...ledger.endpoint_type import EndpointType from ...ledger.indy import IndySdkLedgerPool -from ...wallet.did_method import SOV +from ...wallet.did_method import SOV, DIDMethods from ...wallet.key_type import ED25519 from .. import indy as test_module from ..base import BaseWallet @@ -28,7 +28,7 @@ @pytest.fixture() async def in_memory_wallet(): - profile = InMemoryProfile.test_profile() + profile = InMemoryProfile.test_profile(bind={DIDMethods: DIDMethods()}) wallet = InMemoryWallet(profile) yield wallet @@ -38,6 +38,7 @@ async def wallet(): key = await IndySdkWallet.generate_wallet_key() context = InjectionContext() context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) + context.injector.bind_instance(DIDMethods, DIDMethods()) with async_mock.patch.object(IndySdkProfile, "_make_finalizer"): profile = cast( IndySdkProfile, diff --git a/aries_cloudagent/wallet/tests/test_routes.py b/aries_cloudagent/wallet/tests/test_routes.py index 75abc4d483..051380c7f8 100644 --- a/aries_cloudagent/wallet/tests/test_routes.py +++ b/aries_cloudagent/wallet/tests/test_routes.py @@ -1,4 +1,5 @@ import mock as async_mock +import pytest from aiohttp.web import HTTPForbidden from async_case import IsolatedAsyncioTestCase @@ -6,7 +7,7 @@ from ...core.in_memory import InMemoryProfile from ...ledger.base import BaseLedger from ...protocols.coordinate_mediation.v1_0.route_manager import RouteManager -from ...wallet.did_method import SOV, DIDMethods +from ...wallet.did_method import SOV, DIDMethods, DIDMethod, HolderDefinedDid from ...wallet.key_type import ED25519, KeyTypes from .. import routes as test_module from ..base import BaseWallet @@ -38,7 +39,8 @@ def setUp(self): self.test_verkey = "verkey" self.test_posted_did = "posted-did" self.test_posted_verkey = "posted-verkey" - self.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.did_methods = DIDMethods() + self.context.injector.bind_instance(DIDMethods, self.did_methods) async def test_missing_wallet(self): self.session_inject[BaseWallet] = None @@ -134,6 +136,48 @@ async def test_create_did_unsupported_key_type(self): with self.assertRaises(test_module.web.HTTPForbidden): await test_module.wallet_create_did(self.request) + async def test_create_did_method_requires_user_defined_did(self): + # given + did_custom = DIDMethod( + name="custom", + key_types=[ED25519], + rotation=True, + holder_defined_did=HolderDefinedDid.REQUIRED, + ) + self.did_methods.register(did_custom) + + self.request.json = async_mock.AsyncMock( + return_value={"method": "custom", "options": {"key_type": "ed25519"}} + ) + + # when - then + with self.assertRaises(test_module.web.HTTPBadRequest): + await test_module.wallet_create_did(self.request) + + async def test_create_did_method_doesnt_support_user_defined_did(self): + did_custom = DIDMethod( + name="custom", + key_types=[ED25519], + rotation=True, + holder_defined_did=HolderDefinedDid.NO, + ) + self.did_methods.register(did_custom) + + # when + self.request.json = async_mock.AsyncMock( + return_value={ + "method": "custom", + "options": { + "key_type": ED25519.key_type, + "did": "did:custom:aCustomUserDefinedDID", + }, + } + ) + + # then + with self.assertRaises(test_module.web.HTTPForbidden): + await test_module.wallet_create_did(self.request) + async def test_create_did_x(self): self.wallet.create_local_did.side_effect = test_module.WalletError() with self.assertRaises(test_module.web.HTTPBadRequest): @@ -420,7 +464,6 @@ async def test_set_public_did_no_query_did(self): await test_module.wallet_set_public_did(self.request) async def test_set_public_did_no_ledger(self): - mock_route_manager = async_mock.MagicMock() mock_route_manager.mediation_record_if_id = async_mock.AsyncMock() mock_route_manager.__aenter__ = async_mock.AsyncMock( diff --git a/demo/docker-agent/Dockerfile.acapy b/demo/docker-agent/Dockerfile.acapy index aa9e5593de..a8eee30ae0 100644 --- a/demo/docker-agent/Dockerfile.acapy +++ b/demo/docker-agent/Dockerfile.acapy @@ -8,7 +8,3 @@ COPY ngrok-wait.sh ngrok-wait.sh RUN chmod +x ./ngrok-wait.sh USER $user - -# temporary until this PR gets merged/released -RUN pip uninstall -y aries-cloudagent -RUN pip install aries-cloudagent[indy,bbs,askar]@git+https://github.com/ianco/aries-cloudagent-python@endorser-write-did diff --git a/demo/docker-agent/docker-compose.yml b/demo/docker-agent/docker-compose.yml index 8dd07c428f..35b7b2ac57 100644 --- a/demo/docker-agent/docker-compose.yml +++ b/demo/docker-agent/docker-compose.yml @@ -32,12 +32,10 @@ services: - ./ngrok-wait.sh:/home/indy/ngrok-wait.sh wallet-db: - image: vcr-postgresql + image: postgres:12 environment: - - POSTGRESQL_USER=DB_USER - - POSTGRESQL_PASSWORD=DB_PASSWORD - - POSTGRESQL_DATABASE=DB_USER - - POSTGRESQL_ADMIN_PASSWORD=mysecretpassword + - POSTGRES_USER=DB_USER + - POSTGRES_PASSWORD=DB_PASSWORD ports: - 5433:5432 volumes: diff --git a/demo/docker-agent/ngrok-wait.sh b/demo/docker-agent/ngrok-wait.sh index 70c50ce6f3..4c7ccde9db 100755 --- a/demo/docker-agent/ngrok-wait.sh +++ b/demo/docker-agent/ngrok-wait.sh @@ -37,7 +37,7 @@ exec aca-py start \ --wallet-key "secret_key" \ --wallet-storage-type "postgres_storage" \ --wallet-storage-config "{\"url\":\"wallet-db:5432\",\"max_connections\":5}" \ - --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"postgres\",\"admin_password\":\"mysecretpassword\"}" \ + --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}" \ --admin '0.0.0.0' 8010 \ --label "test_author" \ --admin-insecure-mode \ diff --git a/demo/docker/docker-compose.yml b/demo/docker/docker-compose.yml index bcec777fd0..29facddf0f 100644 --- a/demo/docker/docker-compose.yml +++ b/demo/docker/docker-compose.yml @@ -3,6 +3,9 @@ # To shut down the services run `docker-compose rm` - this will retain the postgres database, so you can change aca-py startup parameters # and restart the docker containers without losing your wallet data # If you want to delete your wallet data just run `docker volume ls -q | xargs docker volume rm` + +# Note this requires von-network (https://github.com/bcgov/von-network) and indy-tails-server (https://github.com/bcgov/indy-tails-server) are already running + version: "3" services: vcr-agent: @@ -41,7 +44,7 @@ services: --wallet-key 'key' \ --wallet-storage-type 'postgres_storage' \ --wallet-storage-config '{\"url\":\"wallet-db:5432\",\"max_connections\":5}' \ - --wallet-storage-creds '{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"postgres\",\"admin_password\":\"mysecretpassword\"}' \ + --wallet-storage-creds '{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}' \ --admin '0.0.0.0' 8010 \ --admin-insecure-mode \ --label 'tester_agent' \ @@ -58,12 +61,10 @@ services: # --genesis-transactions-list 'ledgers.yaml' \ wallet-db: - image: vcr-postgresql + image: postgres:12 environment: - - POSTGRESQL_USER=DB_USER - - POSTGRESQL_PASSWORD=DB_PASSWORD - - POSTGRESQL_DATABASE=DB_USER - - POSTGRESQL_ADMIN_PASSWORD=mysecretpassword + - POSTGRES_USER=DB_USER + - POSTGRES_PASSWORD=DB_PASSWORD ports: - 5433:5432 volumes: diff --git a/demo/multi-demo/Dockerfile.acapy b/demo/multi-demo/Dockerfile.acapy new file mode 100644 index 0000000000..a8eee30ae0 --- /dev/null +++ b/demo/multi-demo/Dockerfile.acapy @@ -0,0 +1,10 @@ +FROM bcgovimages/aries-cloudagent:py36-1.16-1_1.0.0-rc0 + +USER root + +ADD https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 ./jq +RUN chmod +x ./jq +COPY ngrok-wait.sh ngrok-wait.sh +RUN chmod +x ./ngrok-wait.sh + +USER $user diff --git a/demo/multi-demo/README.md b/demo/multi-demo/README.md new file mode 100644 index 0000000000..90274cb259 --- /dev/null +++ b/demo/multi-demo/README.md @@ -0,0 +1,40 @@ +# Running an Aca-Py Agent in Multitenant Mode + +This directory contains scripts to run an aca-py agent in multitenancy mode. + +## Running the Agent + +The docker-compose script runs ngrok to expose the agent's port publicly, and stores wallet data in a postgres database. + +To run the agent in this repo, open a command shell in this directory and run: + +- to build the containers: + +```bash +docker-compose build +``` + +- to run the agent: + +```bash +docker-compose up +``` + +You can connect to the [agent's api service here](http://localhost:8010). + +Note that all the configuration settings are hard-coded in the docker-compose file and ngrok-wait.sh script, so if you change any configs you need to rebuild the docker images. + +- to shut down the agent: + +```bash +docker-compose stop +docker-compose rm -f +``` + +This will leave the agent's wallet data, so if you restart the agent it will maintain any created data. + +- to remove the agent's wallet: + +```bash +docker volume rm multi-demo_wallet-db-data +``` diff --git a/demo/multi-demo/docker-compose.yml b/demo/multi-demo/docker-compose.yml new file mode 100644 index 0000000000..80021ab211 --- /dev/null +++ b/demo/multi-demo/docker-compose.yml @@ -0,0 +1,45 @@ +# Sample docker-compose to start a local aca-py multitenancy agent +# To start aca-py and the postgres database, just run `docker-compose up` +# To shut down the services run `docker-compose rm` - this will retain the postgres database, so you can change aca-py startup parameters +# and restart the docker containers without losing your wallet data +# If you want to delete your wallet data just run `docker volume ls -q | xargs docker volume rm` +version: "3" +services: + ngrok-agent: + image: wernight/ngrok + ports: + - 4067:4040 + command: ngrok http multi-agent:8001 --log stdout + + multi-agent: + build: + context: . + dockerfile: Dockerfile.acapy + environment: + - NGROK_NAME=ngrok-agent + ports: + - 8010:8010 + - 8001:8001 + depends_on: + - wallet-db + entrypoint: /bin/bash + command: [ + "-c", + "sleep 5; \ + ./ngrok-wait.sh" + ] + volumes: + - ./ngrok-wait.sh:/home/indy/ngrok-wait.sh + + wallet-db: + image: postgres:12 + environment: + - POSTGRES_USER=DB_USER + - POSTGRES_PASSWORD=DB_PASSWORD + ports: + - 5433:5432 + volumes: + - wallet-db-data:/var/lib/pgsql/data + +volumes: + wallet-db-data: diff --git a/demo/multi-demo/ngrok-wait.sh b/demo/multi-demo/ngrok-wait.sh new file mode 100755 index 0000000000..6800ec5cd1 --- /dev/null +++ b/demo/multi-demo/ngrok-wait.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# based on code developed by Sovrin: https://github.com/hyperledger/aries-acapy-plugin-toolbox + +echo "using ngrok end point [$NGROK_NAME]" + +NGROK_ENDPOINT=null +while [ -z "$NGROK_ENDPOINT" ] || [ "$NGROK_ENDPOINT" = "null" ] +do + echo "Fetching end point from ngrok service" + NGROK_ENDPOINT=$(curl --silent $NGROK_NAME:4040/api/tunnels | ./jq -r '.tunnels[] | select(.proto=="https") | .public_url') + + if [ -z "$NGROK_ENDPOINT" ] || [ "$NGROK_ENDPOINT" = "null" ]; then + echo "ngrok not ready, sleeping 5 seconds...." + sleep 5 + fi +done + +export ACAPY_ENDPOINT=$NGROK_ENDPOINT + +echo "Starting aca-py agent with endpoint [$ACAPY_ENDPOINT]" + +# ... if you want to echo the aca-py startup command ... +set -x + +exec aca-py start \ + --auto-provision \ + --inbound-transport http '0.0.0.0' 8001 \ + --outbound-transport http \ + --genesis-url "http://test.bcovrin.vonx.io/genesis" \ + --endpoint "${ACAPY_ENDPOINT}" \ + --auto-ping-connection \ + --monitor-ping \ + --public-invites \ + --wallet-type "askar" \ + --wallet-name "test_multi" \ + --wallet-key "secret_key" \ + --wallet-storage-type "postgres_storage" \ + --wallet-storage-config "{\"url\":\"wallet-db:5432\",\"max_connections\":5,\"scheme\":\"MultiWalletSingleTable\"}" \ + --wallet-storage-creds "{\"account\":\"DB_USER\",\"password\":\"DB_PASSWORD\",\"admin_account\":\"DB_USER\",\"admin_password\":\"DB_PASSWORD\"}" \ + --admin '0.0.0.0' 8010 \ + --label "test_multi" \ + --admin-insecure-mode \ + --multitenant \ + --multitenant-admin \ + --jwt-secret "very_secret_secret" \ + --log-level "error" diff --git a/demo/runners/agent_container.py b/demo/runners/agent_container.py index c18c472ebc..bbcd11be40 100644 --- a/demo/runners/agent_container.py +++ b/demo/runners/agent_container.py @@ -622,7 +622,10 @@ async def create_schema_and_cred_def( random.randint(1, 101), ) ) - (_, cred_def_id,) = await self.register_schema_and_creddef( # schema id + ( + _, + cred_def_id, + ) = await self.register_schema_and_creddef( # schema id schema_name, version, schema_attrs, diff --git a/demo/runners/performance.py b/demo/runners/performance.py index b92125f3b2..02b6d3283e 100644 --- a/demo/runners/performance.py +++ b/demo/runners/performance.py @@ -275,7 +275,6 @@ async def main( wallet_type: str = None, arg_file: str = None, ): - if multi_ledger: genesis = None multi_ledger_config_path = "./demo/multi_ledger_config.yml" diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..ecbffe5b24 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,97 @@ +ARG python_version=3.9.16 +FROM python:${python_version}-slim-bullseye AS build + +WORKDIR /src + +ADD . . + +RUN pip install setuptools wheel +RUN python setup.py sdist bdist_wheel + +FROM python:${python_version}-slim-bullseye AS main + +ARG uid=1001 +ARG user=aries +ARG acapy_version +ARG acapy_reqs=[askar,bbs] + +ENV HOME="/home/$user" \ + APP_ROOT="$HOME" \ + LC_ALL=C.UTF-8 \ + LANG=C.UTF-8 \ + PIP_NO_CACHE_DIR=off \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=UTF-8 \ + RUST_LOG=warning \ + SHELL=/bin/bash \ + SUMMARY="aries-cloudagent image" \ + DESCRIPTION="aries-cloudagent provides a base image for running Hyperledger Aries agents in Docker. \ + This image layers the python implementation of aries-cloudagent $acapy_version. Based on Debian Buster." + +LABEL summary="$SUMMARY" \ + description="$DESCRIPTION" \ + io.k8s.description="$DESCRIPTION" \ + io.k8s.display-name="aries-cloudagent $acapy_version" \ + name="aries-cloudagent" \ + version="$acapy_version" \ + maintainer="" + +# Add aries user +RUN useradd -U -ms /bin/bash -u $uid $user + +# Install environment +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends \ + apt-transport-https \ + ca-certificates \ + build-essential \ + bzip2 \ + curl \ + git \ + less \ + libffi-dev \ + libgmp10 \ + liblzma5 \ + libncurses5 \ + libncursesw5 \ + libsecp256k1-0 \ + libzmq5 \ + net-tools \ + openssl \ + sqlite3 \ + vim-tiny \ + zlib1g && \ + rm -rf /var/lib/apt/lists/* /usr/share/doc/* + +WORKDIR $HOME + +# Add local binaries and aliases to path +ENV PATH="$HOME/.local/bin:$PATH" + +# - In order to drop the root user, we have to make some directories writable +# to the root group as OpenShift default security model is to run the container +# under random UID. +RUN usermod -a -G 0 $user + +# Create standard directories to allow volume mounting and set permissions +# Note: PIP_NO_CACHE_DIR environment variable should be cleared to allow caching +RUN mkdir -p \ + $HOME/.aries_cloudagent \ + $HOME/.cache/pip/http \ + $HOME/ledger/sandbox/data \ + $HOME/log + +# The root group needs access the directories under $HOME for the container to function in OpenShift. +# Also ensure the permissions on the python 'site-packages' folder are set correctly. +RUN chmod -R ug+rw $HOME/log $HOME/ledger $HOME/.aries_cloudagent $HOME/.cache + +COPY --from=build /src/dist/aries_cloudagent*.whl . + +RUN pip install --no-cache-dir --find-links=. aries_cloudagent${acapy_reqs} && rm aries_cloudagent*.whl + +# Clean-up unneccessary build dependencies and reduce final image size +RUN apt-get purge -y --auto-remove build-essential + +USER $user + +ENTRYPOINT ["aca-py"] diff --git a/docker/Dockerfile.indy b/docker/Dockerfile.indy index 1072998985..e4c4959cd4 100644 --- a/docker/Dockerfile.indy +++ b/docker/Dockerfile.indy @@ -1,9 +1,9 @@ -ARG python_version=3.6.13 +ARG python_version=3.9.16 ARG rust_version=1.46 # This image could be replaced with an "indy" image from another repo, # such as the indy-sdk -FROM rust:${rust_version}-slim-buster as indy-builder +FROM rust:${rust_version}-slim as indy-builder ARG user=indy ENV HOME="/home/$user" @@ -80,7 +80,7 @@ RUN rm -rf indy-sdk indy-postgres # Indy Base Image # This image could be replaced with an "indy-python" image from another repo, # such as the indy-sdk -FROM python:${python_version}-slim-buster as indy-base +FROM python:${python_version}-slim-bullseye as indy-base ARG uid=1001 ARG user=indy @@ -97,7 +97,7 @@ ENV HOME="/home/$user" \ SHELL=/bin/bash \ SUMMARY="indy-python base image" \ DESCRIPTION="aries-cloudagent provides a base image for running Hyperledger Aries agents in Docker. \ - This image provides all the necessary dependencies to use the indy-sdk in python. Based on Debian Buster." + This image provides all the necessary dependencies to use the indy-sdk in python. Based on Debian bullseye." LABEL summary="$SUMMARY" \ description="$DESCRIPTION" \ @@ -115,11 +115,12 @@ RUN apt-get update -y && \ apt-get install -y --no-install-recommends \ apt-transport-https \ ca-certificates \ + build-essential \ bzip2 \ curl \ git \ less \ - libffi6 \ + libffi-dev \ libgmp10 \ liblzma5 \ libncurses5 \ @@ -204,7 +205,7 @@ ENTRYPOINT ["/bin/bash", "-c", "pytest \"$@\"", "--"] # ACA-Py Builder # Build ACA-Py wheel using setuptools -FROM python:${python_version}-slim-buster AS acapy-builder +FROM python:${python_version}-slim-bullseye AS acapy-builder WORKDIR /src @@ -257,4 +258,7 @@ COPY --from=acapy-builder /src/dist/aries_cloudagent*.whl . RUN pip install --no-cache-dir --find-links=. aries_cloudagent${acapy_reqs} && rm aries_cloudagent*.whl +# Clean-up unneccessary build dependencies and reduce final image size +# RUN apt-get purge -y --auto-remove build-essential + ENTRYPOINT ["aca-py"] diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index e949e9274f..6a1b4df76a 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -1,11 +1,10 @@ -FROM python:3.6.13 +ARG python_version=3.6.13 +FROM python:${python_version}-slim-buster RUN apt-get update -y && \ apt-get install -y --no-install-recommends \ - python3 \ - python3-pip \ - python3-setuptools \ libsodium23 && \ + apt-get clean && \ rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app @@ -20,4 +19,4 @@ RUN pip3 install --no-cache-dir \ ADD . . -ENTRYPOINT ["/bin/bash", "-c", "pytest \"$@\"", "--"] \ No newline at end of file +ENTRYPOINT ["/bin/bash", "-c", "pytest \"$@\"", "--"] diff --git a/docker/Dockerfile.test-indy b/docker/Dockerfile.test-indy deleted file mode 100644 index 047b19187e..0000000000 --- a/docker/Dockerfile.test-indy +++ /dev/null @@ -1,21 +0,0 @@ -FROM bcgovimages/von-image:py36-1.15-1 - -USER indy - -RUN mkdir src test-reports - -WORKDIR /home/indy/src - -RUN mkdir -p test-reports && chown -R indy:indy test-reports && chmod -R ug+rw test-reports - -ADD requirements*.txt ./ - -RUN pip3 install --no-cache-dir \ - -r requirements.txt \ - -r requirements.askar.txt \ - -r requirements.bbs.txt \ - -r requirements.dev.txt - -ADD --chown=indy:root . . - -ENTRYPOINT ["/bin/bash", "-c", "pytest \"$@\"", "--"] diff --git a/docs/conf.py b/docs/conf.py index d3c2d570da..6ed81e5982 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -242,6 +242,7 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {"https://docs.python.org/": None} + # To supress cross-reference warnings # https://github.com/sphinx-doc/sphinx/issues/3866#issuecomment-768167824 class PatchedPythonDomain(PythonDomain): diff --git a/scripts/run_docker b/scripts/run_docker index 3dbfcd3f3b..13b65ee2c6 100755 --- a/scripts/run_docker +++ b/scripts/run_docker @@ -70,6 +70,23 @@ if [ -n "${NETWORK_NAME}" ]; then else echo "No Docker network specified." fi -echo "" -$CONTAINER_RUNTIME run --rm -ti $ARGS aries-cloudagent-run "$@" +if [ -n "${ARG_FILE}" ]; then + if [ -f "${ARG_FILE}" ]; then + ARG_FILE_IN_DOCKER="/home/indy/arg-file.yml" + ARGS="${ARGS} -v ${ARG_FILE}:${ARG_FILE_IN_DOCKER}" + echo "" + echo "Using acapy config file: ${ARG_FILE}" + else + echo "Config file not found: ${ARG_FILE}" || exit 1 + fi +fi + +ACAPY_ARGUMENTS=("$@") +if [ -n "${ARG_FILE_IN_DOCKER}" ]; then + ACAPY_ARGUMENTS=("${ACAPY_ARGUMENTS[@]}" "--arg-file" "${ARG_FILE_IN_DOCKER}") +fi + +echo "" +# shellcheck disable=SC2086,SC2090 +$CONTAINER_RUNTIME run --rm -ti $ARGS aries-cloudagent-run "${ACAPY_ARGUMENTS[@]}" diff --git a/scripts/run_tests_indy b/scripts/run_tests_indy index 6efa27b215..37fab8d5e7 100755 --- a/scripts/run_tests_indy +++ b/scripts/run_tests_indy @@ -3,7 +3,12 @@ cd "$(dirname "$0")" || exit CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}" -$CONTAINER_RUNTIME build -t aries-cloudagent-test -f ../docker/Dockerfile.test-indy .. || exit 1 +DOCKER_BUILDKIT=1 $CONTAINER_RUNTIME build \ + -t aries-cloudagent-test \ + -f ../docker/Dockerfile.indy \ + --target acapy-test .. \ + --build-arg indy_version=1.16.0 \ + || exit 1 if [ ! -d ../test-reports ]; then mkdir ../test-reports; fi