├── ci ├── run-did-test-suite │ ├── .gitignore │ ├── app │ │ ├── package.json │ │ ├── testserver-utils.js │ │ ├── local-files-utils.js │ │ ├── utils.js │ │ └── index.js │ ├── action.yml │ ├── entrypoint.sh │ ├── Dockerfile │ └── README.md ├── get-driver-status │ ├── .gitignore │ ├── app │ │ ├── requirements.txt │ │ └── get-driver-status.py │ ├── Dockerfile │ ├── action.yaml │ ├── entrypoint.sh │ └── README.md ├── docker-build-push │ ├── Dockerfile │ ├── README.md │ └── entrypoint.sh ├── did-lint-check │ └── run.sh ├── deploy-k8s-aws │ ├── Dockerfile │ ├── scripts │ │ ├── process-env.sh │ │ ├── parse-compose.sh │ │ ├── handle-btcr-secret.sh │ │ ├── deploy-frontend.sh │ │ └── deploy-ingress.sh │ └── action.yml └── setup-aws-route53 │ ├── set-alias-dev.uniresolver.io.sh │ ├── set-alias-resolver.identity.foundation.sh │ └── setup-route53-guide.md ├── examples ├── sovrin │ ├── lib │ │ └── .gitignore │ ├── localhost.txn │ ├── danube.txn │ └── mainnet.txn ├── .gitignore ├── src │ └── main │ │ ├── resources │ │ └── log4j2.properties │ │ └── java │ │ └── uniresolver │ │ └── examples │ │ ├── TestDriverDidBtcr.java │ │ ├── TestClientUniResolver.java │ │ ├── TestDriverDidSov.java │ │ ├── TestLocalUniResolver.java │ │ ├── TestLocalUniDereferencer.java │ │ └── w3ctestsuite │ │ ├── TestIdentifier.java │ │ ├── TestDIDResolution.java │ │ ├── TestDIDURLDereferencing.java │ │ └── TestSuiteUtil.java └── pom.xml ├── uni-resolver-core ├── openapi │ └── java-client-generated │ │ ├── .openapi-generator-ignore │ │ └── .gitignore ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── uniresolver │ │ ├── w3c │ │ ├── DIDResolver.java │ │ └── DIDURLDereferencer.java │ │ ├── UniDereferencer.java │ │ ├── UniResolver.java │ │ └── result │ │ ├── ResolveResult.java │ │ └── DereferenceResult.java └── pom.xml ├── driver ├── .gitignore ├── src │ └── main │ │ └── java │ │ └── uniresolver │ │ └── driver │ │ ├── AbstractDriver.java │ │ ├── Driver.java │ │ ├── util │ │ └── MediaTypeUtil.java │ │ └── servlet │ │ ├── InitServlet.java │ │ ├── PropertiesServlet.java │ │ └── ServletUtil.java └── pom.xml ├── docs ├── logo-dif.png ├── logo-ngi0pet.png ├── figures │ ├── overview.png │ ├── protocol.png │ ├── architecture.png │ └── aws-architecture.png ├── troubleshooting.md ├── design-goals.md ├── java-components.md ├── continuous-integration-and-delivery.md ├── dev-system.md └── branching-strategy.md ├── driver-http ├── .gitignore └── pom.xml ├── uni-resolver-client ├── .gitignore └── pom.xml ├── uni-resolver-local ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── uniresolver │ └── local │ ├── extensions │ ├── impl │ │ ├── DummyResolverExtension.java │ │ ├── DummyDereferencerExtension.java │ │ └── DIDDocumentExtension.java │ ├── ResolverExtension.java │ ├── DereferencerExtension.java │ ├── ExtensionStatus.java │ └── util │ │ └── ExecutionStateUtil.java │ └── configuration │ └── LocalUniResolverConfigurator.java ├── uni-resolver-web ├── .gitignore ├── src │ ├── test │ │ └── resources │ │ │ ├── jetty.xml │ │ │ ├── jetty-env.xml │ │ │ └── log4j2-test.xml │ └── main │ │ ├── resources │ │ ├── application-dev.yml │ │ └── log4j2.xml │ │ └── java │ │ └── uniresolver │ │ └── web │ │ ├── config │ │ ├── ServletMappings.java │ │ └── DriverConfigs.java │ │ ├── WebUniResolverApplication.java │ │ ├── WebUniResolverWebServerFactoryCustomizer.java │ │ ├── servlet │ │ ├── MethodsServlet.java │ │ ├── TraitsServlet.java │ │ ├── PropertiesServlet.java │ │ ├── TestIdentifiersServlet.java │ │ └── ServletUtil.java │ │ └── WebUniResolver.java ├── docker │ └── Dockerfile └── pom.xml ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── nightly-did-lint-check.yml │ ├── nightly-did-test-suite.yml │ ├── kubernetes-deploy-to-cluster.yml │ ├── archive_lint_reports.yml │ ├── release.yml │ ├── latest.yml │ └── docker-multi-arch.yml ├── .gitlab-ci.yml └── .env /ci/run-did-test-suite/.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules -------------------------------------------------------------------------------- /examples/sovrin/lib/.gitignore: -------------------------------------------------------------------------------- 1 | libindy.so 2 | 3 | /.idea/ 4 | *.iml 5 | -------------------------------------------------------------------------------- /uni-resolver-core/openapi/java-client-generated/.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /ci/get-driver-status/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | /app/config.json 3 | /app/driver-status-result-*.json 4 | -------------------------------------------------------------------------------- /driver/.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | /.settings/ 4 | /target 5 | /bin/ 6 | /.idea/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | /.settings/ 4 | /target 5 | /bin/ 6 | /.idea/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /uni-resolver-core/openapi/java-client-generated/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !.openapi-generator-ignore -------------------------------------------------------------------------------- /docs/logo-dif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decentralized-identity/universal-resolver/HEAD/docs/logo-dif.png -------------------------------------------------------------------------------- /driver-http/.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | /.settings/ 4 | /target 5 | /bin/ 6 | /.idea/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /uni-resolver-client/.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | /.settings/ 4 | /target 5 | /bin/ 6 | /.idea/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /uni-resolver-core/.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | /.settings/ 4 | /target 5 | /bin/ 6 | /.idea/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /uni-resolver-local/.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | /.settings/ 4 | /target 5 | /bin/ 6 | /.idea/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /uni-resolver-web/.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | /.settings/ 4 | /target 5 | /bin/ 6 | /.idea/ 7 | *.iml 8 | -------------------------------------------------------------------------------- /ci/get-driver-status/app/requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==0.6.0 2 | aiohttp==3.8.5 3 | async-timeout==3.0.1 4 | pyyaml==5.4 5 | -------------------------------------------------------------------------------- /docs/logo-ngi0pet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decentralized-identity/universal-resolver/HEAD/docs/logo-ngi0pet.png -------------------------------------------------------------------------------- /docs/figures/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decentralized-identity/universal-resolver/HEAD/docs/figures/overview.png -------------------------------------------------------------------------------- /docs/figures/protocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decentralized-identity/universal-resolver/HEAD/docs/figures/protocol.png -------------------------------------------------------------------------------- /docs/figures/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decentralized-identity/universal-resolver/HEAD/docs/figures/architecture.png -------------------------------------------------------------------------------- /docs/figures/aws-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decentralized-identity/universal-resolver/HEAD/docs/figures/aws-architecture.png -------------------------------------------------------------------------------- /driver/src/main/java/uniresolver/driver/AbstractDriver.java: -------------------------------------------------------------------------------- 1 | package uniresolver.driver; 2 | 3 | public abstract class AbstractDriver implements Driver { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | /.settings/ 4 | /target/ 5 | /bin/ 6 | docker-compose.override.yml 7 | /.idea/ 8 | *.iml 9 | .vscode/ 10 | .factorypath 11 | Makefile 12 | **/deploy/ -------------------------------------------------------------------------------- /uni-resolver-web/src/test/resources/jetty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /uni-resolver-web/src/test/resources/jetty-env.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /uni-resolver-core/src/main/java/uniresolver/w3c/DIDResolver.java: -------------------------------------------------------------------------------- 1 | package uniresolver.w3c; 2 | 3 | import uniresolver.ResolutionException; 4 | import uniresolver.result.ResolveResult; 5 | 6 | import java.util.Map; 7 | 8 | public interface DIDResolver { 9 | 10 | public ResolveResult resolve(String didString, Map resolutionOptions) throws ResolutionException; 11 | } 12 | -------------------------------------------------------------------------------- /uni-resolver-core/src/main/java/uniresolver/w3c/DIDURLDereferencer.java: -------------------------------------------------------------------------------- 1 | package uniresolver.w3c; 2 | 3 | import uniresolver.DereferencingException; 4 | import uniresolver.ResolutionException; 5 | import uniresolver.result.DereferenceResult; 6 | 7 | import java.util.Map; 8 | 9 | public interface DIDURLDereferencer { 10 | 11 | public DereferenceResult dereference(String didUrlString, Map dereferenceOptions) throws DereferencingException, ResolutionException; 12 | } 13 | -------------------------------------------------------------------------------- /uni-resolver-web/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | management: 2 | endpoints: 3 | web: 4 | exposure: 5 | include: '*' 6 | health: 7 | readinessState: 8 | enabled: 'true' 9 | livenessState: 10 | enabled: 'true' 11 | endpoint: 12 | health: 13 | probes: 14 | enabled: 'true' 15 | show-details: always 16 | status: 17 | http-mapping: 18 | down: '500' 19 | warning: '500' 20 | out_of_service: '503' 21 | -------------------------------------------------------------------------------- /ci/run-did-test-suite/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "run-did-test-suite", 3 | "version": "1.0.0", 4 | "description": "Run did-test-suite action", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Bernhard Fuchs", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@actions/core": "^1.2.7", 13 | "@actions/github": "^4.0.0", 14 | "axios": "^0.21.2", 15 | "dayjs": "^1.10.4", 16 | "minimist": "^1.2.6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Maintain dependencies for GitHub Actions 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /ci/docker-build-push/Dockerfile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | FROM docker:stable 3 | 4 | LABEL "name"="Docker build/push Action" 5 | LABEL "maintainer"="Phil " 6 | LABEL "version"="1.0.0" 7 | 8 | LABEL "com.github.actions.name"="Docker build/push Action" 9 | LABEL "com.github.actions.description"="GitHub Action for building and pushing a Docker container" 10 | LABEL "com.github.actions.icon"="package" 11 | LABEL "com.github.actions.color"="blue" 12 | 13 | ADD entrypoint.sh /entrypoint.sh 14 | RUN chmod +x /entrypoint.sh 15 | ENTRYPOINT ["/entrypoint.sh"] 16 | -------------------------------------------------------------------------------- /ci/run-did-test-suite/action.yml: -------------------------------------------------------------------------------- 1 | name: 'uni-resolver-did-test-suite' 2 | description: 'Run did-test-suite against Universal Resolver deployment' 3 | inputs: 4 | host: 5 | description: 'Host of Uni-Resolver deployment' 6 | required: false 7 | driver_status_report: 8 | description: 'File with testset of Uni-resolver response' 9 | required: false 10 | reports_folder: 11 | description: 'Destination folder of reports' 12 | required: true 13 | runs: 14 | using: 'docker' 15 | image: 'Dockerfile' 16 | args: 17 | - ${{ inputs.host }} 18 | - ${{ inputs.driver_status_report }} 19 | - ${{ inputs.reports_folder }} -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Universal Resolver — Troubleshooting 2 | 3 | If docker-compose complains about wrong versions then you probably have a too old docker-compose version. 4 | 5 | On Ubuntu 16.04 remove docker-compose and install a new version e.g. 6 | ``` 7 | sudo apt-get remove docker-compose 8 | curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 9 | chmod +x /usr/local/bin/docker-compose 10 | ``` 11 | You might want to adjust the version number 1.22.0 to the latest one. Please see: [Installing docker-compose](https://docs.docker.com/compose/install/#install-compose) 12 | 13 | -------------------------------------------------------------------------------- /ci/run-did-test-suite/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "#### Run did-test-suite ####" 4 | 5 | echo "Running with parameters:" 6 | sh -c "echo $*" 7 | 8 | echo "host: $INPUT_HOST" 9 | echo "driver_status_report: $INPUT_DRIVER_STATUS_REPORT" 10 | 11 | node /run-did-test-suite/index.js --HOST="$INPUT_HOST" --DRIVER_STATUS_REPORT="$INPUT_DRIVER_STATUS_REPORT" --OUTPUT_PATH="$INPUT_REPORTS_FOLDER" 12 | 13 | echo "Push report file to repo" 14 | git status 15 | git config --global user.email "admin@danubetech.com" 16 | git config --global user.name "Get driver status workflow" 17 | git fetch && git pull --ff-only 18 | git add . && git commit -m "did-test-suite report" && git push -------------------------------------------------------------------------------- /.github/workflows/nightly-did-lint-check.yml: -------------------------------------------------------------------------------- 1 | name: Nightly DID Lint check 2 | 3 | on: 4 | schedule: 5 | - cron: '0 6,12,18,0 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | did-lint-check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: Did Lint check 14 | run: ./ci/did-lint-check/run.sh 15 | 16 | - name: Slack notification 17 | uses: 8398a7/action-slack@v3 18 | with: 19 | status: ${{ job.status }} 20 | fields: repo,commit,action,eventName,ref,workflow 21 | env: 22 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 23 | if: failure() 24 | -------------------------------------------------------------------------------- /uni-resolver-core/src/main/java/uniresolver/UniDereferencer.java: -------------------------------------------------------------------------------- 1 | package uniresolver; 2 | 3 | import uniresolver.result.DereferenceResult; 4 | import uniresolver.w3c.DIDURLDereferencer; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public interface UniDereferencer extends DIDURLDereferencer { 10 | 11 | @Override public DereferenceResult dereference(String didUrlString, Map dereferenceOptions) throws DereferencingException, ResolutionException; 12 | 13 | default public DereferenceResult dereference(String didUrlString) throws DereferencingException, ResolutionException { 14 | return this.dereference(didUrlString, new HashMap<>()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ci/run-did-test-suite/app/testserver-utils.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const fs = require('fs'); 3 | 4 | const runTests = (resolvers, host, outputPath) => { 5 | axios.post(host, 6 | { 7 | suites: [ 8 | { 9 | suite_name: 'did-resolution', 10 | resolvers: resolvers 11 | } 12 | ] 13 | } 14 | ).then(res => { 15 | const timestamp = new Date().toISOString().split('.')[0]; 16 | fs.writeFileSync(`${outputPath}/did-test-suite-report-${timestamp}.json`, JSON.stringify(res.data.suitesReportJson, null, 2)) 17 | }).catch(err => console.log(err)); 18 | } 19 | 20 | module.exports = {runTests} 21 | -------------------------------------------------------------------------------- /uni-resolver-web/src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: docker:latest 2 | 3 | services: 4 | - docker:dind 5 | 6 | before_script: 7 | - echo -n $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY 8 | 9 | build-master: 10 | stage: build 11 | script: 12 | - cd resolver/java/uni-resolver-web/ 13 | - docker build -f ./docker/Dockerfile --pull -t "$CI_REGISTRY_IMAGE" . 14 | - docker push "$CI_REGISTRY_IMAGE" 15 | only: 16 | - master 17 | 18 | build: 19 | stage: build 20 | script: 21 | - cd resolver/java/uni-resolver-web/ 22 | - docker build -f ./docker/Dockerfile --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" . 23 | - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" 24 | except: 25 | - master 26 | -------------------------------------------------------------------------------- /examples/src/main/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | status=error 2 | dest=err 3 | name=testLoggerConfig 4 | 5 | filter.threshold.type=ThresholdFilter 6 | filter.threshold.level=debug 7 | 8 | appender.console.type=Console 9 | appender.console.name=STDOUT 10 | appender.console.layout.type=PatternLayout 11 | appender.console.layout.pattern=%highlight{[%t] %p %c{2} (%M) -} %highlight{%m%n%throwable}{STYLE=Logback} 12 | appender.console.filter.threshold.type=ThresholdFilter 13 | appender.console.filter.threshold.level=debug 14 | 15 | logger.uniresolver.name=uniresolver 16 | logger.uniresolver.level=debug 17 | 18 | logger.timeoutHandler.name=org.bitcoinj.core.PeerSocketHandler 19 | logger.timeoutHandler.level=error 20 | 21 | rootLogger.level=info 22 | rootLogger.additivity=false 23 | rootLogger.appenderRefs=stdout 24 | rootLogger.appenderRef.stdout.ref=STDOUT 25 | -------------------------------------------------------------------------------- /ci/get-driver-status/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-buster 2 | 3 | LABEL "com.github.actions.name"="Get driver status" 4 | LABEL "com.github.actions.description"="Get driver status for a Universal Resolver deployment" 5 | LABEL "com.github.actions.icon"="mic" 6 | LABEL "com.github.actions.color"="blue" 7 | LABEL "version"="1.0.0" 8 | LABEL "repository"="https://github.com/decentralized-identity/universal-resolver" 9 | LABEL "homepage"="https://uniresolver.io" 10 | LABEL "maintainer"="Bernhard Fuchs " 11 | 12 | RUN apt-get update && \ 13 | apt-get upgrade -y 14 | 15 | WORKDIR /get-driver-status/ 16 | 17 | COPY app/requirements.txt . 18 | RUN pip install --no-cache-dir -r requirements.txt 19 | 20 | COPY app/get-driver-status.py . 21 | 22 | COPY entrypoint.sh / 23 | RUN chmod +x /entrypoint.sh 24 | ENTRYPOINT ["/entrypoint.sh"] 25 | -------------------------------------------------------------------------------- /driver-http/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | uni-resolver-driver-http 5 | jar 6 | uni-resolver-driver-http 7 | 8 | 9 | decentralized-identity 10 | uni-resolver 11 | 0.48-SNAPSHOT 12 | 13 | 14 | 15 | 16 | decentralized-identity 17 | uni-resolver-driver 18 | 19 | 20 | org.apache.httpcomponents 21 | httpclient 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ci/did-lint-check/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Pulling did-status-generator image" 4 | docker pull oydeu/did-status-generator:latest 5 | 6 | echo "Running did-status-generator image" 7 | docker run --rm -e "DID_RESOLVER=https://resolver.identity.foundation/1.0/identifiers/" oydeu/did-status-generator:latest > result.json 8 | echo "Set result.json to result_var" 9 | result_var=$(cat result.json) 10 | 11 | echo "Checkout to did-lint-reports branch" 12 | git fetch 13 | git switch did-lint-reports --force 14 | 15 | echo "Replace old result.json with result_var" 16 | echo "$result_var" > result.json 17 | 18 | echo "Push result file to repo" 19 | git config --global user.email "admin@danubetech.com" 20 | git config --global user.name "DID Lint check workflow" 21 | git status 22 | git add result.json 23 | git commit -m "DID Lint check reports" 24 | git push origin did-lint-reports:did-lint-reports 25 | -------------------------------------------------------------------------------- /ci/run-did-test-suite/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine3.13 2 | 3 | LABEL "com.github.actions.name"="Did Test Suite" 4 | LABEL "com.github.actions.description"="Run the did-test-suite against the api of a Universal Resolver deployment" 5 | LABEL "com.github.actions.icon"="mic" 6 | LABEL "com.github.actions.color"="blue" 7 | LABEL "version"="1.0.0" 8 | LABEL "repository"="https://github.com/decentralized-identity/universal-resolver" 9 | LABEL "homepage"="https://uniresolver.io" 10 | LABEL "maintainer"="Bernhard Fuchs " 11 | 12 | RUN apk update && apk upgrade && \ 13 | apk add --no-cache git 14 | 15 | WORKDIR /run-did-test-suite/ 16 | 17 | COPY app/package.json . 18 | RUN npm install 19 | 20 | COPY app/index.js . 21 | COPY app/local-files-utils.js . 22 | COPY app/testserver-utils.js . 23 | COPY app/utils.js . 24 | 25 | COPY entrypoint.sh / 26 | RUN chmod +x /entrypoint.sh 27 | ENTRYPOINT ["/entrypoint.sh"] -------------------------------------------------------------------------------- /examples/src/main/java/uniresolver/examples/TestDriverDidBtcr.java: -------------------------------------------------------------------------------- 1 | package uniresolver.examples; 2 | import foundation.identity.did.DID; 3 | import info.weboftrust.btctxlookup.bitcoinconnection.BlockcypherAPIBitcoinConnection; 4 | import uniresolver.driver.did.btcr2.DidBtcrDriver; 5 | import uniresolver.result.ResolveDataModelResult; 6 | import uniresolver.result.ResolveResult; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class TestDriverDidBtcr { 12 | 13 | public static void main(String[] args) throws Exception { 14 | 15 | Map driverProperties = new HashMap<>(); 16 | driverProperties.put("bitcoinConnection", "blockcypherapi"); 17 | DidBtcrDriver driver = new DidBtcrDriver(driverProperties); 18 | 19 | Map resolveOptions = new HashMap<>(); 20 | ResolveResult resolveResult = driver.resolve(DID.fromString("did:btcr:x705-jznz-q3nl-srs"), resolveOptions); 21 | System.out.println(resolveResult.toJson()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ci/get-driver-status/action.yaml: -------------------------------------------------------------------------------- 1 | name: 'uni-resolver-get-driver-status' 2 | description: 'Get driver status for Universal Resolver deployment' 3 | inputs: 4 | host: 5 | description: 'Host of Uni-Resolver deployment' 6 | required: false 7 | default: https://dev.uniresolver.io 8 | config: 9 | description: 'Uni-Resolver configuration file' 10 | required: false 11 | default: /github/workspace/uni-resolver-web/src/main/resources/application.yml 12 | out: 13 | description: 'Folder location of driver-status-result- file' 14 | required: false 15 | default: /github/workspace/uni-resolver-web/driver-status-reports 16 | debug: 17 | description: 'Enhance logging' 18 | required: false 19 | default: false 20 | keep_result: 21 | description: 'Keep result file' 22 | required: false 23 | default: false 24 | runs: 25 | using: 'docker' 26 | image: 'Dockerfile' 27 | args: 28 | - ${{ inputs.host }} 29 | - ${{ inputs.config }} 30 | - ${{ inputs.out_folder }} 31 | -------------------------------------------------------------------------------- /uni-resolver-local/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | uni-resolver-local 5 | jar 6 | uni-resolver-local 7 | 8 | 9 | decentralized-identity 10 | uni-resolver 11 | 0.48-SNAPSHOT 12 | 13 | 14 | 15 | 16 | decentralized-identity 17 | uni-resolver-core 18 | 19 | 20 | decentralized-identity 21 | uni-resolver-driver 22 | 23 | 24 | decentralized-identity 25 | uni-resolver-driver-http 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/src/main/java/uniresolver/examples/TestClientUniResolver.java: -------------------------------------------------------------------------------- 1 | package uniresolver.examples; 2 | 3 | import uniresolver.client.ClientUniResolver; 4 | import uniresolver.result.ResolveDataModelResult; 5 | import uniresolver.result.ResolveRepresentationResult; 6 | import uniresolver.result.ResolveResult; 7 | 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public class TestClientUniResolver { 13 | 14 | public static void main(String[] args) throws Exception { 15 | 16 | Map resolveOptions = new HashMap<>(); 17 | resolveOptions.put("accept", "application/did"); 18 | 19 | ClientUniResolver uniResolver = new ClientUniResolver(); 20 | uniResolver.setResolveUri("http://localhost:8080/1.0/identifiers/"); 21 | 22 | ResolveRepresentationResult resolveRepresentationResult = uniResolver.resolveRepresentation("did:sov:WRfXPg8dantKVubE3HX8pw", resolveOptions); 23 | System.out.println(resolveRepresentationResult.toJson()); 24 | System.out.println(new String(resolveRepresentationResult.getDidDocumentStream(), StandardCharsets.UTF_8)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/nightly-did-test-suite.yml: -------------------------------------------------------------------------------- 1 | name: Nightly did-test-suite 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | driver-health-check: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | - name: Get driver status 12 | uses: ./ci/get-driver-status 13 | with: 14 | host: https://dev.uniresolver.io 15 | out: /home/runner/work/universal-resolver/universal-resolver/driver-status-reports 16 | keep_result: true 17 | # - name: Run did-test-suite 18 | # uses: ./ci/run-did-test-suite 19 | # with: 20 | # host: https://did-test-suite.uniresolver.io/test-suite-manager/generate-report 21 | # driver_status_report: ${{ env.driver_status_report }} 22 | # reports_folder: ${{ env.reports_folder }} 23 | - name: Slack notification 24 | uses: 8398a7/action-slack@v3 25 | with: 26 | status: ${{ job.status }} 27 | fields: repo,commit,action,eventName,ref,workflow 28 | env: 29 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 30 | if: failure() 31 | -------------------------------------------------------------------------------- /examples/src/main/java/uniresolver/examples/TestDriverDidSov.java: -------------------------------------------------------------------------------- 1 | package uniresolver.examples; 2 | 3 | import foundation.identity.did.DID; 4 | import uniresolver.driver.did.sov.DidSovDriver; 5 | import uniresolver.result.ResolveDataModelResult; 6 | import uniresolver.result.ResolveResult; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class TestDriverDidSov { 12 | 13 | public static void main(String[] args) throws Exception { 14 | 15 | DidSovDriver driver = new DidSovDriver(); 16 | driver.setLibIndyPath("./sovrin/lib/libindy.so"); 17 | driver.setPoolConfigs("_;./sovrin/mainnet.txn"); 18 | driver.setPoolVersions("_;2"); 19 | 20 | Map resolveOptions = new HashMap<>(); 21 | resolveOptions.put("accept", "application/did"); 22 | 23 | ResolveResult resolveResult; 24 | resolveResult = driver.resolve(DID.fromString("did:sov:WRfXPg8dantKVubE3HX8pw"), resolveOptions); 25 | System.out.println(resolveResult.toJson()); 26 | resolveResult = driver.resolveRepresentation(DID.fromString("did:sov:WRfXPg8dantKVubE3HX8pw"), resolveOptions); 27 | System.out.println(resolveResult.toJson()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /uni-resolver-web/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for universalresolver/uni-resolver-web 2 | 3 | FROM maven:3-eclipse-temurin-21 AS build 4 | MAINTAINER Markus Sabadello 5 | 6 | # build uni-resolver-web 7 | 8 | ADD . /opt/universal-resolver 9 | RUN cd /opt/universal-resolver && mvn clean install -N 10 | RUN cd /opt/universal-resolver/uni-resolver-core && mvn clean install -N 11 | RUN cd /opt/universal-resolver/driver && mvn clean install -N 12 | RUN cd /opt/universal-resolver/driver-http && mvn clean install -N 13 | RUN cd /opt/universal-resolver/uni-resolver-local && mvn clean install -N 14 | RUN cd /opt/universal-resolver/uni-resolver-web && mvn clean package -N 15 | 16 | # build image 17 | 18 | FROM eclipse-temurin:21-jre-alpine 19 | # For amd64 architecture use amd64/eclipse-temurin:17-jre-alpine 20 | 21 | MAINTAINER Markus Sabadello 22 | 23 | WORKDIR /opt/universal-resolver/uni-resolver-web/ 24 | 25 | COPY --from=build /opt/universal-resolver/uni-resolver-web/target/*-exec.jar ./ 26 | 27 | ENV uniresolver_web_spring_profiles_active=default 28 | 29 | # done 30 | 31 | EXPOSE 8080 32 | CMD java -jar *.jar 33 | -------------------------------------------------------------------------------- /uni-resolver-local/src/main/java/uniresolver/local/extensions/impl/DummyResolverExtension.java: -------------------------------------------------------------------------------- 1 | package uniresolver.local.extensions.impl; 2 | 3 | import foundation.identity.did.DID; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import uniresolver.ResolutionException; 7 | import uniresolver.local.LocalUniResolver; 8 | import uniresolver.local.extensions.ExtensionStatus; 9 | import uniresolver.local.extensions.ResolverExtension; 10 | import uniresolver.local.extensions.ResolverExtension.AbstractResolverExtension; 11 | import uniresolver.result.ResolveResult; 12 | 13 | import java.util.Map; 14 | 15 | public class DummyResolverExtension extends AbstractResolverExtension implements ResolverExtension { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(DummyResolverExtension.class); 18 | 19 | @Override 20 | public ExtensionStatus afterResolve(DID did, Map resolutionOptions, ResolveResult resolveResult, Map executionState, LocalUniResolver localUniResolver) throws ResolutionException { 21 | 22 | if (log.isDebugEnabled()) log.debug("Dummy extension called!"); 23 | return ExtensionStatus.DEFAULT; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/design-goals.md: -------------------------------------------------------------------------------- 1 | # Universal Resolver — Design Goals 2 | 3 | The main design objectives of the UR are: 4 | 5 | * **Generic:** The UR will be a neutral implementation that will be usable as broadly as possible, i.e. not limited towards a specific use case or higher-level protocol. 6 | * **Simple:** The most common uses of the UR should be very simple to execute (e.g. "give me the DDO or the Hub endpoint for this identifier"). 7 | * **Extensible:** The UR may ship by default with a few key implementations of common identifier systems, but must also be extensible to easily add new ones through a plugin mechanism. 8 | 9 | The following functionality is out of scope for the UR: 10 | 11 | * The UR will not address registration or modification of identifiers and associated data. 12 | * The UR will not involve authentication or authorization of the UR user, i.e. all its functionality will be publicly usable by anyone. [debatable?] 13 | * The UR will not implement any functionality that builds on top of resolution, such as the DIF Hub protocols, Blockstack storage layer, XDI, DID Auth, DKMS, etc. [debatable?], but it will be able to serve as a building block for such higher-level protocols. 14 | -------------------------------------------------------------------------------- /examples/src/main/java/uniresolver/examples/TestLocalUniResolver.java: -------------------------------------------------------------------------------- 1 | package uniresolver.examples; 2 | 3 | import uniresolver.driver.did.sov.DidSovDriver; 4 | import uniresolver.local.LocalUniResolver; 5 | import uniresolver.result.ResolveResult; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class TestLocalUniResolver { 11 | 12 | public static void main(String[] args) throws Exception { 13 | 14 | LocalUniResolver uniResolver = new LocalUniResolver(); 15 | uniResolver.getDrivers().add(new DidSovDriver()); 16 | uniResolver.getDriver(DidSovDriver.class).setLibIndyPath("./sovrin/lib/libindy.so"); 17 | uniResolver.getDriver(DidSovDriver.class).setPoolConfigs("_;./sovrin/mainnet.txn"); 18 | uniResolver.getDriver(DidSovDriver.class).setPoolVersions("_;2"); 19 | 20 | Map resolveOptions = new HashMap<>(); 21 | resolveOptions.put("accept", "application/did"); 22 | 23 | ResolveResult resolveResult; 24 | resolveResult = uniResolver.resolve("did:sov:WRfXPg8dantKVubE3HX8pw", resolveOptions); 25 | System.out.println(resolveResult.toJson()); 26 | resolveResult = uniResolver.resolveRepresentation("did:sov:WRfXPg8dantKVubE3HX8pw", resolveOptions); 27 | System.out.println(resolveResult.toJson()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ci/run-did-test-suite/app/local-files-utils.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | 3 | const generateLocalFile = (testData, methodName, path) => { 4 | fs.writeFileSync( 5 | `${path}/universal-resolver-did-${methodName}.json`, 6 | JSON.stringify(testData, null, 4) 7 | ); 8 | }; 9 | 10 | const generateDefaultFile = (path) => { 11 | fs.readdir(path, (err, files) => { 12 | 13 | const resolvers = [] 14 | 15 | files.forEach(file => { 16 | console.log(file); 17 | const fileContent = JSON.parse(fs.readFileSync(`${path}/${file}`)); 18 | console.log(fileContent); 19 | if (file.startsWith('universal-resolver') || file.startsWith('resolver')) { 20 | resolvers.push(`require('../implementations/${file}')`) 21 | } 22 | }); 23 | console.log(resolvers) 24 | 25 | fs.writeFileSync( 26 | '/Users/devfox/testsuites/did-test-suite/packages/did-core-test-server/suites/did-resolution/default.js', 27 | `module.exports = { 28 | name: '7.1 DID Resolution', 29 | resolvers: [${resolvers}] 30 | }` 31 | ); 32 | }); 33 | }; 34 | 35 | module.exports = { 36 | generateDefaultFile, 37 | generateLocalFile 38 | } 39 | -------------------------------------------------------------------------------- /examples/src/main/java/uniresolver/examples/TestLocalUniDereferencer.java: -------------------------------------------------------------------------------- 1 | package uniresolver.examples; 2 | 3 | import uniresolver.driver.did.sov.DidSovDriver; 4 | import uniresolver.local.LocalUniDereferencer; 5 | import uniresolver.local.LocalUniResolver; 6 | import uniresolver.result.DereferenceResult; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class TestLocalUniDereferencer { 12 | 13 | public static void main(String[] args) throws Exception { 14 | 15 | LocalUniResolver uniResolver = new LocalUniResolver(); 16 | uniResolver.getDrivers().add(new DidSovDriver()); 17 | uniResolver.getDriver(DidSovDriver.class).setLibIndyPath("./sovrin/lib/libindy.so"); 18 | uniResolver.getDriver(DidSovDriver.class).setPoolConfigs("_;./sovrin/mainnet.txn"); 19 | uniResolver.getDriver(DidSovDriver.class).setPoolVersions("_;2"); 20 | 21 | LocalUniDereferencer uniDereferencer = new LocalUniDereferencer(); 22 | uniDereferencer.setUniResolver(uniResolver); 23 | 24 | Map dereferenceOptions = new HashMap<>(); 25 | dereferenceOptions.put("accept", "application/did"); 26 | DereferenceResult dereferenceResult = uniDereferencer.dereference("did:sov:WRfXPg8dantKVubE3HX8pw#key-1", dereferenceOptions); 27 | System.out.println(dereferenceResult.toJson()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /uni-resolver-local/src/main/java/uniresolver/local/extensions/impl/DummyDereferencerExtension.java: -------------------------------------------------------------------------------- 1 | package uniresolver.local.extensions.impl; 2 | 3 | import foundation.identity.did.DIDURL; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import uniresolver.DereferencingException; 7 | import uniresolver.ResolutionException; 8 | import uniresolver.local.LocalUniDereferencer; 9 | import uniresolver.local.extensions.DereferencerExtension; 10 | import uniresolver.local.extensions.DereferencerExtension.AbstractDereferencerExtension; 11 | import uniresolver.local.extensions.ExtensionStatus; 12 | import uniresolver.result.DereferenceResult; 13 | 14 | import java.util.Map; 15 | 16 | public class DummyDereferencerExtension extends AbstractDereferencerExtension implements DereferencerExtension { 17 | 18 | private static final Logger log = LoggerFactory.getLogger(DummyDereferencerExtension.class); 19 | 20 | @Override 21 | public ExtensionStatus afterDereference(DIDURL didUrl, Map dereferenceOptions, DereferenceResult dereferenceResult, Map executionState, LocalUniDereferencer localUniDereferencer) throws ResolutionException, DereferencingException { 22 | 23 | if (log.isDebugEnabled()) log.debug("Dummy extension called!"); 24 | return ExtensionStatus.DEFAULT; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/sovrin/localhost.txn: -------------------------------------------------------------------------------- 1 | {"data":{"alias":"Node1","client_ip":"10.0.0.2","client_port":9702,"node_ip":"10.0.0.2","node_port":9701,"services":["VALIDATOR"]},"dest":"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv","identifier":"FYmoFw55GeQH7SRFa37dkx1d2dZ3zUF8ckg7wmL7ofN4","txnId":"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62","type":"0"} 2 | {"data":{"alias":"Node2","client_ip":"10.0.0.2","client_port":9704,"node_ip":"10.0.0.2","node_port":9703,"services":["VALIDATOR"]},"dest":"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb","identifier":"8QhFxKxyaFsJy4CyxeYX34dFH8oWqyBv1P4HLQCsoeLy","txnId":"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc","type":"0"} 3 | {"data":{"alias":"Node3","client_ip":"10.0.0.2","client_port":9706,"node_ip":"10.0.0.2","node_port":9705,"services":["VALIDATOR"]},"dest":"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya","identifier":"2yAeV5ftuasWNgQwVYzeHeTuM7LwwNtPR3Zg9N4JiDgF","txnId":"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4","type":"0"} 4 | {"data":{"alias":"Node4","client_ip":"10.0.0.2","client_port":9708,"node_ip":"10.0.0.2","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA","identifier":"FTE95CVthRtrBnK2PYCBbC9LghTcGwi9Zfi1Gz2dnyNx","txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008","type":"0"} 5 | -------------------------------------------------------------------------------- /ci/docker-build-push/README.md: -------------------------------------------------------------------------------- 1 | # Github Action 2 | 3 | GitHub Action for building and publishing a Docker container to Docker Hub. 4 | 5 | 6 | ## Secrets 7 | 8 | - `DOCKER_USERNAME` - *Required* Name of Docker Hub user which has **Write access** 9 | - `DOCKER_PASSWORD` - *Required* Password of the Docker Hub user 10 | 11 | Setup secrets in your GitHub project at "Settings > Secrets" 12 | 13 | ## Environment Variables 14 | 15 | 16 | - `CONTAINER_TAG` : **mandatory**, example: 'universalresolver/driver-did-btcr:latest' 17 | - `DOCKER_FILE` : **optional**, default is **Dockerfile** 18 | 19 | 20 | ## Example 21 | 22 | 23 | ```yaml 24 | name: CI/CD Workflow for driver-did-btcr 25 | 26 | on: 27 | push: 28 | branches: 29 | - master 30 | pull_request: 31 | branches: 32 | - master 33 | 34 | jobs: 35 | build: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@master 39 | - name: Docker Build and Push 40 | uses: .ci/docker-build-push 41 | env: 42 | DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}} 43 | DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}} 44 | DOCKER_FILE: docker/Dockerfile 45 | CONTAINER_TAG: universalresolver/driver-did-btcr:latest 46 | ``` 47 | 48 | ## LICENSE 49 | 50 | Copyright (c) 2020 51 | 52 | Licensed under the Apache2 License. -------------------------------------------------------------------------------- /uni-resolver-web/src/main/java/uniresolver/web/config/ServletMappings.java: -------------------------------------------------------------------------------- 1 | package uniresolver.web.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ConfigurationProperties("server.servlet.mappings") 8 | public class ServletMappings { 9 | 10 | private String properties; 11 | private String resolve; 12 | private String methods; 13 | private String testIdentifiers; 14 | private String traits; 15 | 16 | public String getProperties() { 17 | return properties; 18 | } 19 | 20 | public String getResolve() { 21 | return resolve; 22 | } 23 | 24 | public String getMethods() { 25 | return methods; 26 | } 27 | 28 | public String getTestIdentifiers() { 29 | return testIdentifiers; 30 | } 31 | 32 | public String getTraits() { 33 | return traits; 34 | } 35 | 36 | public void setProperties(String properties) { 37 | this.properties = properties; 38 | } 39 | 40 | public void setResolve(String resolve) { 41 | this.resolve = resolve; 42 | } 43 | 44 | public void setMethods(String methods) { 45 | this.methods = methods; 46 | } 47 | 48 | public void setTestIdentifiers(String testIdentifiers) { 49 | this.testIdentifiers = testIdentifiers; 50 | } 51 | 52 | public void setTraits(String traits) { 53 | this.traits = traits; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /uni-resolver-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | uni-resolver-client 5 | jar 6 | uni-resolver-client 7 | 8 | 9 | decentralized-identity 10 | uni-resolver 11 | 0.48-SNAPSHOT 12 | 13 | 14 | 15 | 1.20.0 16 | 17 | 18 | 19 | 20 | 21 | commons-codec 22 | commons-codec 23 | ${commons-codec.version} 24 | 25 | 26 | 27 | 28 | 29 | 30 | decentralized-identity 31 | uni-resolver-core 32 | 33 | 34 | org.apache.httpcomponents 35 | httpclient 36 | 37 | 38 | commons-codec 39 | commons-codec 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /uni-resolver-core/src/main/java/uniresolver/UniResolver.java: -------------------------------------------------------------------------------- 1 | package uniresolver; 2 | 3 | import foundation.identity.did.representations.Representations; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import uniresolver.result.ResolveResult; 7 | import uniresolver.w3c.DIDResolver; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | public interface UniResolver extends DIDResolver { 14 | 15 | public static final String PROPERTIES_MEDIA_TYPE = "application/json"; 16 | public static final String METHODS_MEDIA_TYPE = "application/json"; 17 | public static final String TEST_IDENTIFIER_MEDIA_TYPE = "application/json"; 18 | public static final String TRAITS_MEDIA_TYPE = "application/json"; 19 | 20 | static final Logger log = LoggerFactory.getLogger(UniResolver.class); 21 | 22 | @Override public ResolveResult resolve(String didString, Map resolutionOptions) throws ResolutionException; 23 | 24 | default public ResolveResult resolve(String didString) throws ResolutionException { 25 | return this.resolve(didString, Map.of("accept", Representations.DEFAULT_MEDIA_TYPE)); 26 | } 27 | 28 | public Map> properties() throws ResolutionException; 29 | public Set methods() throws ResolutionException; 30 | public Map> testIdentifiers() throws ResolutionException; 31 | public Map> traits() throws ResolutionException; 32 | } 33 | -------------------------------------------------------------------------------- /driver/src/main/java/uniresolver/driver/Driver.java: -------------------------------------------------------------------------------- 1 | package uniresolver.driver; 2 | 3 | import foundation.identity.did.DID; 4 | import foundation.identity.did.DIDURL; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import uniresolver.DereferencingException; 8 | import uniresolver.ResolutionException; 9 | import uniresolver.result.DereferenceResult; 10 | import uniresolver.result.ResolveResult; 11 | 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | public interface Driver { 17 | 18 | public static final String PROPERTIES_MEDIA_TYPE = "application/json"; 19 | public static final String TEST_IDENTIFIER_MEDIA_TYPE = "application/json"; 20 | public static final String TRAITS_MEDIA_TYPE = "application/json"; 21 | 22 | static final Logger log = LoggerFactory.getLogger(Driver.class); 23 | 24 | public ResolveResult resolve(DID did, Map resolutionOptions) throws ResolutionException; 25 | 26 | public DereferenceResult dereference(DIDURL didUrl, Map dereferenceOptions) throws DereferencingException, ResolutionException; 27 | 28 | default public Map properties() throws ResolutionException { 29 | return Collections.emptyMap(); 30 | } 31 | 32 | default public List testIdentifiers() throws ResolutionException { 33 | return Collections.emptyList(); 34 | } 35 | 36 | default public Map traits() throws ResolutionException { 37 | return Collections.emptyMap(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | uni-resolver-examples 6 | jar 7 | uni-resolver-examples 8 | 9 | decentralized-identity 10 | uni-resolver 11 | 0.4-SNAPSHOT 12 | 13 | 14 | 15 | 16 | decentralized-identity 17 | uni-resolver-local 18 | compile 19 | 20 | 21 | decentralized-identity 22 | uni-resolver-client 23 | compile 24 | 25 | 26 | decentralized-identity 27 | uni-resolver-driver-did-sov 28 | 0.4-SNAPSHOT 29 | compile 30 | 31 | 32 | decentralized-identity 33 | uni-resolver-driver-did-btcr 34 | 0.1.3-SNAPSHOT 35 | compile 36 | 37 | 38 | org.apache.logging.log4j 39 | log4j-slf4j2-impl 40 | 2.13.3 41 | compile 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ci/docker-build-push/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -l 2 | set -e 3 | 4 | echo "DOCKER BUILD AND PUSH" 5 | 6 | echo "Environment: " 7 | echo "- BUILD_PATH: ${BUILD_PATH}"; 8 | echo "- DOCKER_FILE: ${DOCKER_FILE}"; 9 | echo "- CONTAINER_TAG: ${CONTAINER_TAG}"; 10 | echo "- GITHUB_EVENT_NAME: ${GITHUB_EVENT_NAME}"; 11 | echo "- GITHUB_EVENT_PATH: ${GITHUB_EVENT_PATH}"; 12 | echo "- GITHUB_REF: ${GITHUB_REF}"; 13 | echo "- GITHUB_WORKSPACE: ${GITHUB_WORKSPACE}"; 14 | echo "- GITHUB_REPOSITORY: ${GITHUB_REPOSITORY}"; 15 | echo "- GITHUB_ACTOR: ${GITHUB_ACTOR}"; 16 | 17 | if [[ -z "$GITHUB_EVENT_NAME" ]]; then 18 | echo "Set the GITHUB_EVENT_NAME env variable." 19 | exit 1 20 | fi 21 | 22 | if [[ -z "$GITHUB_EVENT_PATH" ]]; then 23 | echo "Set the GITHUB_EVENT_PATH env variable." 24 | exit 1 25 | fi 26 | 27 | if [[ -z "$GITHUB_REF" ]]; then 28 | echo "Set the GITHUB_REF env variable." 29 | exit 1 30 | fi 31 | 32 | if [ -z "${DOCKER_FILE}" ] 33 | then 34 | echo "No Dockerfile specified. Using default file: Dockerfile" 35 | DOCKER_FILE=Dockerfile 36 | fi 37 | 38 | if [[ -z "$CONTAINER_TAG" ]]; then 39 | echo "Set the CONTAINER_TAG env variable." 40 | exit 1 41 | fi 42 | 43 | ls -al 44 | 45 | if [ -n "${BUILD_PATH}" ] 46 | then 47 | cd ${BUILD_PATH} 48 | fi 49 | 50 | ls -al 51 | 52 | echo "Building container ..." 53 | docker build . -f ${DOCKER_FILE} -t ${CONTAINER_TAG} 54 | 55 | if [ -n "${DOCKER_USERNAME}" ] 56 | then 57 | echo "Pushing container to DockerHub ..." 58 | 59 | docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD} 60 | 61 | docker push ${CONTAINER_TAG} 62 | fi 63 | 64 | -------------------------------------------------------------------------------- /driver/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | uni-resolver-driver 5 | jar 6 | uni-resolver-driver 7 | 8 | 9 | decentralized-identity 10 | uni-resolver 11 | 0.48-SNAPSHOT 12 | 13 | 14 | 15 | 6.1.0 16 | 7.0.2 17 | 18 | 19 | 20 | 21 | 22 | jakarta.servlet 23 | jakarta.servlet-api 24 | ${jakarta.servlet-api.version} 25 | 26 | 27 | org.springframework 28 | spring-web 29 | ${org.springframework-spring-web.version} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | jakarta.servlet 38 | jakarta.servlet-api 39 | provided 40 | 41 | 42 | decentralized-identity 43 | uni-resolver-core 44 | 45 | 46 | org.springframework 47 | spring-web 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /uni-resolver-web/src/main/java/uniresolver/web/WebUniResolverApplication.java: -------------------------------------------------------------------------------- 1 | package uniresolver.web; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.builder.SpringApplicationBuilder; 7 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.ApplicationContextAware; 10 | import org.springframework.context.annotation.Bean; 11 | import uniresolver.local.LocalUniDereferencer; 12 | import uniresolver.local.LocalUniResolver; 13 | 14 | @SpringBootApplication 15 | public class WebUniResolverApplication extends SpringBootServletInitializer implements ApplicationContextAware { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(WebUniResolverApplication.class, args); 19 | } 20 | 21 | private ApplicationContext applicationContext; 22 | 23 | @Override 24 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 25 | return application.sources(WebUniResolverApplication.class); 26 | } 27 | 28 | @Bean(name = "UniResolver") 29 | public LocalUniResolver localUniResolver() { 30 | return new LocalUniResolver(); 31 | } 32 | 33 | @Bean(name = "UniDereferencer") 34 | public LocalUniDereferencer localUniDereferencer() { 35 | return new LocalUniDereferencer(this.applicationContext.getBean("UniResolver", LocalUniResolver.class)); 36 | } 37 | 38 | @Override 39 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 40 | this.applicationContext = applicationContext; 41 | } 42 | } -------------------------------------------------------------------------------- /uni-resolver-web/src/main/java/uniresolver/web/WebUniResolverWebServerFactoryCustomizer.java: -------------------------------------------------------------------------------- 1 | package uniresolver.web; 2 | 3 | import org.eclipse.jetty.http.UriCompliance; 4 | import org.eclipse.jetty.server.Connector; 5 | import org.eclipse.jetty.server.HttpConfiguration; 6 | import org.eclipse.jetty.server.HttpConnectionFactory; 7 | import org.eclipse.jetty.server.Server; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.boot.jetty.servlet.JettyServletWebServerFactory; 11 | import org.springframework.boot.web.server.WebServerFactoryCustomizer; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Component 15 | public class WebUniResolverWebServerFactoryCustomizer implements WebServerFactoryCustomizer { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(WebUniResolverWebServerFactoryCustomizer.class); 18 | 19 | @Override 20 | public void customize(JettyServletWebServerFactory factory) { 21 | factory.addServerCustomizers(this::customizeUriCompliance); 22 | } 23 | 24 | private void customizeUriCompliance(Server server) { 25 | if (log.isInfoEnabled()) log.info("Customizing URI compliance: " + server); 26 | for (Connector connector : server.getConnectors()) { 27 | if (log.isInfoEnabled()) log.info("Customizing connector: " + connector); 28 | connector.getConnectionFactories().stream() 29 | .filter(factory -> factory instanceof HttpConnectionFactory) 30 | .forEach(factory -> { 31 | HttpConfiguration httpConfig = ((HttpConnectionFactory) factory).getHttpConfiguration(); 32 | httpConfig.setUriCompliance(UriCompliance.UNSAFE); 33 | if (log.isInfoEnabled()) log.info("Set URI compliance: " + httpConfig); 34 | }); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ci/deploy-k8s-aws/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim 2 | 3 | LABEL "name"="Deployment of the Universal Resolver to a Kubernetes Cluster" 4 | LABEL "maintainer"="Bernhard Fuchs " 5 | LABEL "version"="2.0.0" 6 | 7 | LABEL "com.github.actions.name"="GitHub Action for deploying the Universal Resolver" 8 | LABEL "com.github.actions.description"="Deploys the Universal Resolver to a Kubernetes cluster with smart deployment strategy." 9 | LABEL "com.github.actions.icon"="package" 10 | LABEL "com.github.actions.color"="blue" 11 | 12 | # Install base tools and AWS CLI 13 | RUN apt-get update -y && \ 14 | apt-get install -y curl gnupg openssh-client git unzip jq ca-certificates && \ 15 | # Install AWS CLI v2 16 | curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ 17 | unzip awscliv2.zip && \ 18 | ./aws/install && \ 19 | rm -rf awscliv2.zip aws && \ 20 | # Install AWS IAM Authenticator (latest version compatible with EKS 1.33) 21 | curl -Lso /bin/aws-iam-authenticator https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/v0.6.30/aws-iam-authenticator_0.6.30_linux_amd64 && \ 22 | chmod +x /bin/aws-iam-authenticator && \ 23 | # Note: kubectl will be installed dynamically in entrypoint.sh to match EKS cluster version 24 | # Install yq for YAML parsing 25 | curl -Lso /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && \ 26 | chmod +x /usr/local/bin/yq && \ 27 | # Cleanup 28 | apt-get -y clean && apt-get -y autoclean && apt-get -y autoremove && \ 29 | rm -rf /var/lib/apt/lists/* 30 | 31 | # Copy deployment scripts 32 | COPY scripts /scripts 33 | RUN chmod +x /scripts/*.sh 34 | 35 | ENTRYPOINT ["/scripts/entrypoint.sh"] 36 | -------------------------------------------------------------------------------- /ci/get-driver-status/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "#### Driver Status for a Universal Resolver Deployment ####" 4 | 5 | echo "Running with parameters:" 6 | sh -c "echo $*" 7 | 8 | echo "host: $INPUT_HOST" 9 | echo "config: $INPUT_CONFIG" 10 | echo "out folder: $INPUT_OUT" 11 | echo "debug: $INPUT_DEBUG" 12 | echo "keep result: $INPUT_KEEP_RESULT" 13 | 14 | if "$INPUT_DEBUG"; then 15 | echo "Current folder" 16 | pwd 17 | ls -al 18 | 19 | echo "Deployment folder" 20 | ls -al deploy 21 | 22 | echo "Root folder" 23 | ls -al / 24 | 25 | echo "#### Ingress file ####" 26 | cat /github/workspace/deploy/uni-resolver-ingress.yaml 27 | fi 28 | 29 | DATE_WITH_TIME=$(TZ=UTC date "+%Y-%m-%d_%H:%M:%S") 30 | REPORTS_FOLDER="$INPUT_OUT/nightly-run-$DATE_WITH_TIME" 31 | mkdir -p "$REPORTS_FOLDER" 32 | 33 | python --version 34 | python /get-driver-status/get-driver-status.py --host "$INPUT_HOST" --config "$INPUT_CONFIG" --out "$REPORTS_FOLDER" 35 | 36 | echo "Switch to drivers-status-reports branch" 37 | git config --global --add safe.directory /github/workspace 38 | git fetch 39 | git checkout -b driver-status-reports origin/driver-status-reports 40 | 41 | if "$INPUT_KEEP_RESULT"; 42 | then 43 | echo "Push result file to repo" 44 | git config --global user.email "admin@danubetech.com" 45 | git config --global user.name "Get driver status workflow" 46 | # Pass driver_status_report to next step in github action 47 | echo "driver_status_report=$(git diff --name-only --staged)" >> "$GITHUB_ENV" 48 | echo "reports_folder=$REPORTS_FOLDER" >> "$GITHUB_ENV" 49 | git add . 50 | git commit -m "Get driver status results" 51 | git push origin driver-status-reports:driver-status-reports 52 | else 53 | cat -b /driver-status-reports/driver-status-*.json 54 | fi 55 | -------------------------------------------------------------------------------- /driver/src/main/java/uniresolver/driver/util/MediaTypeUtil.java: -------------------------------------------------------------------------------- 1 | package uniresolver.driver.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.MediaType; 6 | 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | public class MediaTypeUtil { 11 | 12 | private static final Logger log = LoggerFactory.getLogger(MediaTypeUtil.class); 13 | 14 | public static boolean isMediaTypeAcceptable(MediaType acceptMediaType, String mediaTypeString) { 15 | if (mediaTypeString == null) throw new NullPointerException(); 16 | MediaType mediaType = MediaType.valueOf(mediaTypeString); 17 | boolean acceptable = false; 18 | if (acceptMediaType.includes(mediaType)) { 19 | acceptable = true; 20 | } 21 | if (acceptMediaType.getType().equals(mediaType.getType()) && mediaType.getSubtype().endsWith("+" + acceptMediaType.getSubtype())) { 22 | acceptable = true; 23 | } 24 | if (! MediaType.ALL.equals(acceptMediaType)) { 25 | if (acceptMediaType.getParameters() != null) { 26 | acceptable &= Objects.equals(acceptMediaType.getParameter("profile"), mediaType.getParameter("profile")); 27 | } 28 | } 29 | if (log.isDebugEnabled()) log.debug("Checking if media type " + mediaType + " is acceptable for " + acceptMediaType + ": " + acceptable); 30 | return acceptable; 31 | } 32 | 33 | public static boolean isMediaTypeAcceptable(List acceptMediaTypes, String mediaTypeString) { 34 | for (MediaType acceptMediaType : acceptMediaTypes) { 35 | if (isMediaTypeAcceptable(acceptMediaType, mediaTypeString)) { 36 | return true; 37 | } 38 | } 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /driver/src/main/java/uniresolver/driver/servlet/InitServlet.java: -------------------------------------------------------------------------------- 1 | package uniresolver.driver.servlet; 2 | 3 | import jakarta.servlet.Servlet; 4 | import jakarta.servlet.ServletConfig; 5 | import jakarta.servlet.ServletException; 6 | import jakarta.servlet.http.HttpServlet; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import uniresolver.driver.Driver; 10 | 11 | import java.lang.reflect.InvocationTargetException; 12 | 13 | public class InitServlet extends HttpServlet implements Servlet { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(InitServlet.class); 16 | 17 | private static Driver driver = null; 18 | 19 | public InitServlet() { 20 | 21 | super(); 22 | } 23 | 24 | @SuppressWarnings("unchecked") 25 | @Override 26 | public void init(ServletConfig config) throws ServletException { 27 | 28 | super.init(config); 29 | 30 | if (driver == null) { 31 | 32 | String driverClassName = config.getInitParameter("Driver"); 33 | Class driverClass; 34 | 35 | try { 36 | 37 | driverClass = driverClassName == null ? null : (Class) Class.forName(driverClassName); 38 | driver = driverClass == null ? null : driverClass.getConstructor().newInstance(); 39 | } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) { 40 | 41 | throw new ServletException(ex.getMessage(), ex); 42 | } 43 | 44 | if (driver == null) throw new ServletException("Unable to load driver: (no 'Driver' init parameter)"); 45 | 46 | if (log.isInfoEnabled()) log.info("Loaded driver: " + driverClass); 47 | } 48 | } 49 | 50 | public static Driver getDriver() { 51 | return InitServlet.driver; 52 | } 53 | 54 | public static void setDriver(Driver driver) { 55 | InitServlet.driver = driver; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ci/get-driver-status/README.md: -------------------------------------------------------------------------------- 1 | # universal-resolver-deployment-driver-status 2 | 3 | This tool can be used as a standalone script for testing an existing deployment of the Uni Resolver, it can be integrated as a [github action](https://github.com/features/actions) into a github workflow, or it can be run as docker container. 4 | 5 | ## Run get-driver-status script manually 6 | 7 | python get-driver-status.py --host --config --out --write200 false 8 | 9 | The script needs Python 3 and dependencies listed in the `requirements.txt`. 10 | 11 | Automatic installation of requirements: 12 | 13 | pip install --no-cache-dir -r requirements.txt 14 | 15 | All arguments are optional and default values are aligned with github actions workflow. 16 | 17 | Default values: 18 | 19 | host: https://dev.uniresolver.io 20 | config: /github/workspace/config.json 21 | out: ./ 22 | write200: True 23 | 24 | 25 | ## Use action in github workflow 26 | 27 | - name: Get Driver Status 28 | uses: ./ci/driver-status 29 | with: 30 | host: :// 31 | config: 32 | out folder: 33 | debug: 34 | write200: 35 | 36 | Example can be seen in the [uni-resolver workflow configuration](https://github.com/decentralized-identity/universal-resolver/blob/master/.github/workflows/universal-resolver-build-deploy.yml) 37 | 38 | ## Run as docker container 39 | ### Build container with 40 | 41 | docker build -f Dockerfile -t get-driver-status . 42 | 43 | ### Run container with 44 | 45 | docker run -it --rm -e HOST= -e CONFIG_FILE= --name get-driver-status . 46 | -------------------------------------------------------------------------------- /.github/workflows/kubernetes-deploy-to-cluster.yml: -------------------------------------------------------------------------------- 1 | name: AWS Kubernetes deployment 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout repository 11 | uses: actions/checkout@v4 12 | 13 | - name: Import Secrets 14 | uses: hashicorp/vault-action@v3 15 | with: 16 | url: ${{ secrets.VAULT_ADDR }} 17 | token: ${{ secrets.CI_SECRET_READER_PERIODIC_TOKEN }} 18 | caCertificate: ${{ secrets.VAULTCA }} 19 | secrets: | 20 | ci/data/gh-workflows/universal-resolver-cluster aws-access-key-id | AWS_ACCESS_KEY_ID ; 21 | ci/data/gh-workflows/universal-resolver-cluster aws-secret-access-key | AWS_SECRET_ACCESS_KEY ; 22 | ci/data/gh-workflows/universal-resolver-cluster rpc-url-testnet | RPC_URL_TESTNET ; 23 | ci/data/gh-workflows/universal-resolver-cluster rpc-cert-testnet | RPC_CERT_TESTNET 24 | 25 | - name: Deploy to AWS Kubernetes Cluster 26 | uses: ./ci/deploy-k8s-aws 27 | with: 28 | kube-config-data: ${{ secrets.KUBE_CONFIG_DATA_BASE64_UNI_RESOLVER_PROD }} 29 | aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} 30 | aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} 31 | rpc-url-testnet: ${{ env.RPC_URL_TESTNET }} 32 | rpc-cert-testnet: ${{ env.RPC_CERT_TESTNET }} 33 | slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} 34 | github-server-url: ${{ github.server_url }} 35 | github-repository: ${{ github.repository }} 36 | github-run-id: ${{ github.run_id }} 37 | 38 | - name: Slack notification 39 | if: failure() 40 | uses: 8398a7/action-slack@v3 41 | with: 42 | status: ${{ job.status }} 43 | fields: repo,commit,action,eventName,ref,workflow 44 | env: 45 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 46 | -------------------------------------------------------------------------------- /ci/deploy-k8s-aws/scripts/process-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################################################ 4 | # Process Environment Variables 5 | # 6 | # This script creates a Kubernetes ConfigMap from the .env file. 7 | # The ConfigMap is named 'app-config' and can be referenced by deployments 8 | # to inject environment variables into containers. 9 | # 10 | # Prerequisites: 11 | # - kubectl must be configured and authenticated 12 | # - NAMESPACE environment variable must be set 13 | # 14 | # Usage: 15 | # NAMESPACE=uni-resolver ./process-env.sh [path-to-env-file] 16 | # 17 | # Output: 18 | # ConfigMap 'app-config' created/updated in the specified namespace 19 | ################################################################################ 20 | 21 | set -e 22 | 23 | # Default .env file location 24 | ENV_FILE="${1:-.env}" 25 | 26 | # Validate namespace is set 27 | if [ -z "$NAMESPACE" ]; then 28 | echo "Error: NAMESPACE environment variable is not set" 29 | exit 1 30 | fi 31 | 32 | echo "Processing environment variables from $ENV_FILE..." 33 | 34 | # Check if .env file exists 35 | if [ -f "$ENV_FILE" ]; then 36 | # Create a ConfigMap from .env file 37 | # Using --dry-run=client to generate YAML without applying it first 38 | kubectl create configmap app-config \ 39 | --from-env-file="$ENV_FILE" \ 40 | --namespace="$NAMESPACE" \ 41 | --dry-run=client -o yaml > configmap.yaml 42 | 43 | # Apply the ConfigMap to the cluster 44 | kubectl apply -f configmap.yaml 45 | 46 | echo "✓ ConfigMap 'app-config' created/updated from $ENV_FILE" 47 | echo " Total environment variables: $(grep -c "=" "$ENV_FILE" 2>/dev/null || echo 0)" 48 | else 49 | echo "Warning: No $ENV_FILE file found, skipping ConfigMap creation" 50 | echo "Deployments will use default environment variables from their Docker images" 51 | fi 52 | -------------------------------------------------------------------------------- /.github/workflows/archive_lint_reports.yml: -------------------------------------------------------------------------------- 1 | name: Copy Result JSON on Commit 2 | 3 | on: 4 | push: 5 | branches: 6 | - did-lint-reports 7 | paths: 8 | - 'result.json' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | copy-result-json: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout source code 16 | uses: actions/checkout@v4 17 | 18 | - name: Get current timestamp 19 | id: timestamp 20 | uses: nanzm/get-time-action@master 21 | with: 22 | timeZone: UTC 23 | format: 'YYYY-MM-DD-HH-mm-ss' 24 | 25 | - name: Install dependencies 26 | run: sudo apt-get install jq -y 27 | 28 | - name: Configure git user 29 | run: | 30 | git config --global user.name 'Kim Duffy' 31 | git config --global user.email 'kimdhamilton@gmail.com' 32 | 33 | - name: Copy result.json with timestamp 34 | run: | 35 | cp result.json result_${{ steps.timestamp.outputs.time }}.json 36 | jq . result_${{ steps.timestamp.outputs.time }}.json > tmp.json && mv tmp.json result_${{ steps.timestamp.outputs.time }}.json 37 | 38 | - name: Push to target repository 39 | env: 40 | REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }} 41 | run: | 42 | git clone https://x-access-token:${REPO_ACCESS_TOKEN}@github.com/decentralized-identity/universal-resolver-lint-dashboard.git 43 | cd universal-resolver-lint-dashboard 44 | git checkout -b new-result || git checkout new-result 45 | git branch --set-upstream-to=origin/new-result new-result 46 | git pull 47 | mv ../result_${{ steps.timestamp.outputs.time }}.json . 48 | git add result_${{ steps.timestamp.outputs.time }}.json 49 | git commit -m "Add new result file" 50 | git push -u origin new-result 51 | 52 | 53 | -------------------------------------------------------------------------------- /ci/deploy-k8s-aws/scripts/parse-compose.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################################################ 4 | # Parse docker-compose.yml 5 | # 6 | # This script parses the docker-compose.yml file and extracts service 7 | # information into a JSON file (services.json) for further processing. 8 | # 9 | # Extracted information: 10 | # - Service name 11 | # - Container image (with tag) 12 | # - Exposed ports 13 | # - Environment variables 14 | # - Environment file references 15 | # - Volumes 16 | # - Command 17 | # - Restart policy 18 | # 19 | # Usage: 20 | # ./parse-compose.sh [path-to-docker-compose.yml] 21 | # 22 | # Output: 23 | # services.json - JSON array containing service definitions 24 | ################################################################################ 25 | 26 | set -e 27 | 28 | # Default docker-compose file location 29 | COMPOSE_FILE="${1:-docker-compose.yml}" 30 | 31 | # Validate that docker-compose.yml exists 32 | if [ ! -f "$COMPOSE_FILE" ]; then 33 | echo "Error: docker-compose.yml not found at $COMPOSE_FILE" 34 | exit 1 35 | fi 36 | 37 | echo "Parsing $COMPOSE_FILE..." 38 | 39 | # Extract services information using yq 40 | # For each service, create a JSON object with all relevant fields 41 | yq eval -o=json '.services | to_entries | .[] | { 42 | "name": .key, 43 | "image": .value.image, 44 | "ports": .value.ports, 45 | "environment": .value.environment, 46 | "env_file": .value.env_file, 47 | "volumes": .value.volumes, 48 | "command": .value.command, 49 | "restart": .value.restart 50 | }' "$COMPOSE_FILE" > services.json 51 | 52 | # Validate JSON output 53 | if [ ! -s services.json ]; then 54 | echo "Error: Failed to parse services from docker-compose.yml" 55 | exit 1 56 | fi 57 | 58 | echo "Services parsed successfully:" 59 | cat services.json 60 | 61 | echo "" 62 | echo "Total services found: $(cat services.json | jq -s 'length')" 63 | -------------------------------------------------------------------------------- /uni-resolver-web/src/main/java/uniresolver/web/servlet/MethodsServlet.java: -------------------------------------------------------------------------------- 1 | package uniresolver.web.servlet; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import uniresolver.UniResolver; 9 | import uniresolver.web.WebUniResolver; 10 | 11 | import java.io.IOException; 12 | import java.util.Set; 13 | 14 | public class MethodsServlet extends WebUniResolver { 15 | 16 | protected static final Logger log = LoggerFactory.getLogger(MethodsServlet.class); 17 | 18 | private static final ObjectMapper objectMapper = new ObjectMapper(); 19 | 20 | @Override 21 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 22 | 23 | // read request 24 | 25 | request.setCharacterEncoding("UTF-8"); 26 | response.setCharacterEncoding("UTF-8"); 27 | 28 | if (log.isInfoEnabled()) log.info("Incoming request."); 29 | 30 | // execute the request 31 | 32 | Set methods; 33 | String methodsString; 34 | 35 | try { 36 | 37 | methods = this.methods(); 38 | methodsString = methods == null ? null : objectMapper.writeValueAsString(methods); 39 | } catch (Exception ex) { 40 | 41 | if (log.isWarnEnabled()) log.warn("Resolver reported: " + ex.getMessage(), ex); 42 | ServletUtil.sendResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Resolver reported: " + ex.getMessage()); 43 | return; 44 | } 45 | 46 | if (log.isInfoEnabled()) log.info("Methods: " + methods); 47 | 48 | // no result? 49 | 50 | if (methods == null) { 51 | 52 | ServletUtil.sendResponse(response, HttpServletResponse.SC_NOT_FOUND, "No methods."); 53 | return; 54 | } 55 | 56 | // write result 57 | 58 | ServletUtil.sendResponse(response, HttpServletResponse.SC_OK, UniResolver.METHODS_MEDIA_TYPE, methodsString); 59 | } 60 | } -------------------------------------------------------------------------------- /uni-resolver-local/src/main/java/uniresolver/local/extensions/ResolverExtension.java: -------------------------------------------------------------------------------- 1 | package uniresolver.local.extensions; 2 | 3 | import foundation.identity.did.DID; 4 | import uniresolver.ResolutionException; 5 | import uniresolver.local.LocalUniResolver; 6 | import uniresolver.result.ResolveResult; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public interface ResolverExtension { 16 | 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target(ElementType.TYPE) 19 | @interface ExtensionStage { 20 | String value(); 21 | } 22 | 23 | @FunctionalInterface 24 | interface ExtensionFunction { 25 | ExtensionStatus apply(E extension) throws ResolutionException; 26 | } 27 | 28 | @ExtensionStage("beforeResolve") 29 | interface BeforeResolveResolverExtension extends ResolverExtension { 30 | default ExtensionStatus beforeResolve(DID did, Map resolutionOptions, ResolveResult resolveResult, Map executionState, LocalUniResolver localUniResolver) throws ResolutionException { 31 | return null; 32 | } 33 | } 34 | 35 | @ExtensionStage("afterResolve") 36 | interface AfterResolveResolverExtension extends ResolverExtension { 37 | default ExtensionStatus afterResolve(DID did, Map resolutionOptions, ResolveResult resolveResult, Map executionState, LocalUniResolver localUniResolver) throws ResolutionException { 38 | return null; 39 | } 40 | } 41 | 42 | abstract class AbstractResolverExtension implements BeforeResolveResolverExtension, AfterResolveResolverExtension { 43 | } 44 | 45 | static List extensionClassNames(List extensions) { 46 | return extensions.stream().map(e -> e.getClass().getSimpleName()).toList(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /uni-resolver-web/src/main/java/uniresolver/web/servlet/TraitsServlet.java: -------------------------------------------------------------------------------- 1 | package uniresolver.web.servlet; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import uniresolver.UniResolver; 9 | import uniresolver.web.WebUniResolver; 10 | 11 | import java.io.IOException; 12 | import java.util.Map; 13 | 14 | public class TraitsServlet extends WebUniResolver { 15 | 16 | protected static final Logger log = LoggerFactory.getLogger(TraitsServlet.class); 17 | 18 | private static final ObjectMapper objectMapper = new ObjectMapper(); 19 | 20 | @Override 21 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 22 | 23 | // read request 24 | 25 | request.setCharacterEncoding("UTF-8"); 26 | response.setCharacterEncoding("UTF-8"); 27 | 28 | if (log.isInfoEnabled()) log.info("Incoming request."); 29 | 30 | // execute the request 31 | 32 | Map> traits; 33 | String traitsString; 34 | 35 | try { 36 | 37 | traits = this.traits(); 38 | traitsString = traits == null ? null : objectMapper.writeValueAsString(traits); 39 | } catch (Exception ex) { 40 | 41 | if (log.isWarnEnabled()) log.warn("Resolver reported: " + ex.getMessage(), ex); 42 | ServletUtil.sendResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Resolver reported: " + ex.getMessage()); 43 | return; 44 | } 45 | 46 | if (log.isInfoEnabled()) log.info("Traits: " + traits); 47 | 48 | // no result? 49 | 50 | if (traits == null) { 51 | 52 | ServletUtil.sendResponse(response, HttpServletResponse.SC_NOT_FOUND, "No traits."); 53 | return; 54 | } 55 | 56 | // write result 57 | 58 | ServletUtil.sendResponse(response, HttpServletResponse.SC_OK, UniResolver.TRAITS_MEDIA_TYPE, traitsString); 59 | } 60 | } -------------------------------------------------------------------------------- /ci/setup-aws-route53/set-alias-dev.uniresolver.io.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # === inputs you can tweak === 5 | HZ_ID="Z07034832165V2JXMP0FS" # Hosted Zone ID for dev.uniresolver.io (from your CLI output) 6 | RECORD_NAME="dev.uniresolver.io." 7 | ALB_DNS="k8s-uniresol-uniresol-01e0b1f895-139924821.us-east-2.elb.amazonaws.com" 8 | AWS_REGION="us-east-2" # Region of the ALB 9 | 10 | # === derive the ALB's Canonical Hosted Zone ID (safer than hardcoding) === 11 | ALB_ZONE_ID="$(aws elbv2 describe-load-balancers \ 12 | --region "$AWS_REGION" \ 13 | --query "LoadBalancers[?DNSName=='${ALB_DNS}'].CanonicalHostedZoneId | [0]" \ 14 | --output text)" 15 | 16 | if [[ -z "$ALB_ZONE_ID" || "$ALB_ZONE_ID" == "None" ]]; then 17 | echo "ERROR: Could not resolve ALB CanonicalHostedZoneId for $ALB_DNS in $AWS_REGION" 18 | exit 1 19 | fi 20 | 21 | echo "Using ALB DNS: $ALB_DNS" 22 | echo "ALB Hosted Zone ID: $ALB_ZONE_ID" 23 | echo "Target record: $RECORD_NAME (zone $HZ_ID)" 24 | 25 | # === build the change batch payload === 26 | CHANGE_BATCH="$(cat <> properties; 33 | String propertiesString; 34 | 35 | try { 36 | 37 | properties = this.properties(); 38 | propertiesString = properties == null ? null : objectMapper.writeValueAsString(properties); 39 | } catch (Exception ex) { 40 | 41 | if (log.isWarnEnabled()) log.warn("Resolver reported: " + ex.getMessage(), ex); 42 | ServletUtil.sendResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Resolver reported: " + ex.getMessage()); 43 | return; 44 | } 45 | 46 | if (log.isInfoEnabled()) log.info("Properties: " + properties); 47 | 48 | // no result? 49 | 50 | if (properties == null) { 51 | 52 | ServletUtil.sendResponse(response, HttpServletResponse.SC_NOT_FOUND, "No properties."); 53 | return; 54 | } 55 | 56 | // write result 57 | 58 | ServletUtil.sendResponse(response, HttpServletResponse.SC_OK, UniResolver.PROPERTIES_MEDIA_TYPE, propertiesString); 59 | } 60 | } -------------------------------------------------------------------------------- /uni-resolver-web/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /uni-resolver-web/src/main/java/uniresolver/web/servlet/TestIdentifiersServlet.java: -------------------------------------------------------------------------------- 1 | package uniresolver.web.servlet; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import uniresolver.UniResolver; 9 | import uniresolver.web.WebUniResolver; 10 | 11 | import java.io.IOException; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public class TestIdentifiersServlet extends WebUniResolver { 16 | 17 | protected static final Logger log = LoggerFactory.getLogger(TestIdentifiersServlet.class); 18 | 19 | private static final ObjectMapper objectMapper = new ObjectMapper(); 20 | 21 | @Override 22 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 23 | 24 | // read request 25 | 26 | request.setCharacterEncoding("UTF-8"); 27 | response.setCharacterEncoding("UTF-8"); 28 | 29 | if (log.isInfoEnabled()) log.info("Incoming request."); 30 | 31 | // execute the request 32 | 33 | Map> testIdentifiers; 34 | String testIdentifiersString; 35 | 36 | try { 37 | 38 | testIdentifiers = this.testIdentifiers(); 39 | testIdentifiersString = testIdentifiers == null ? null : objectMapper.writeValueAsString(testIdentifiers); 40 | } catch (Exception ex) { 41 | 42 | if (log.isWarnEnabled()) log.warn("Resolver reported: " + ex.getMessage(), ex); 43 | ServletUtil.sendResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Resolver reported: " + ex.getMessage()); 44 | return; 45 | } 46 | 47 | if (log.isInfoEnabled()) log.info("Test identifiers: " + testIdentifiers); 48 | 49 | // no result? 50 | 51 | if (testIdentifiers == null) { 52 | 53 | ServletUtil.sendResponse(response, HttpServletResponse.SC_NOT_FOUND, "No test identifiers."); 54 | return; 55 | } 56 | 57 | // write result 58 | 59 | ServletUtil.sendResponse(response, HttpServletResponse.SC_OK, UniResolver.TEST_IDENTIFIER_MEDIA_TYPE, testIdentifiersString); 60 | } 61 | } -------------------------------------------------------------------------------- /docs/java-components.md: -------------------------------------------------------------------------------- 1 | # Universal Resolver — Java Components 2 | 3 | This is a Java implementation of a Universal Resolver. See [universal-resolver](https://github.com/decentralized-identity/universal-resolver/) for a general introduction to Universal Resolvers and drivers. 4 | 5 | ## Build (native Java) 6 | 7 | Maven build: 8 | 9 | mvn clean install 10 | 11 | ## Local Resolver 12 | 13 | You can use a [Local Resolver](https://github.com/decentralized-identity/universal-resolver/tree/main/uni-resolver-client) in your Java project that invokes drivers locally (either directly via their JAVA API or via a Docker REST API). 14 | 15 | Dependency: 16 | 17 | 18 | decentralized-identity 19 | uni-resolver-local 20 | 0.1-SNAPSHOT 21 | 22 | 23 | [Example Use](https://github.com/decentralized-identity/universal-resolver/blob/main/examples/src/main/java/uniresolver/examples/TestLocalUniResolver.java): 24 | 25 | ## Web Resolver 26 | 27 | You can deploy a [Web Resolver](https://github.com/decentralized-identity/universal-resolver/tree/main/uni-resolver-web) that can be called by clients and invokes drivers locally (either directly via their JAVA API or via a Docker REST API). 28 | 29 | See the [Example Configuration](https://github.com/decentralized-identity/universal-resolver/blob/main/uni-resolver-web/src/main/webapp/WEB-INF/applicationContext.xml). 30 | 31 | How to run: 32 | 33 | mvn jetty:run 34 | 35 | ## Client Resolver 36 | 37 | You can use a [Client Resolver](https://github.com/decentralized-identity/universal-resolver/tree/main/uni-resolver-client) in your Java project that calls a remote Web Resolver. 38 | 39 | Dependency: 40 | 41 | 42 | decentralized-identity 43 | uni-resolver-client 44 | 0.1-SNAPSHOT 45 | 46 | 47 | [Example Use](https://github.com/decentralized-identity/universal-resolver/blob/main/examples/src/main/java/uniresolver/examples/TestClientUniResolver.java): 48 | 49 | ## About 50 | 51 | Decentralized Identity Foundation - http://identity.foundation/ 52 | -------------------------------------------------------------------------------- /driver/src/main/java/uniresolver/driver/servlet/PropertiesServlet.java: -------------------------------------------------------------------------------- 1 | package uniresolver.driver.servlet; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import jakarta.servlet.Servlet; 5 | import jakarta.servlet.http.HttpServlet; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import uniresolver.driver.Driver; 11 | 12 | import java.io.IOException; 13 | import java.util.Map; 14 | 15 | public class PropertiesServlet extends HttpServlet implements Servlet { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(PropertiesServlet.class); 18 | 19 | private static final ObjectMapper objectMapper = new ObjectMapper(); 20 | 21 | public PropertiesServlet() { 22 | super(); 23 | } 24 | 25 | @Override 26 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 27 | 28 | // read request 29 | 30 | request.setCharacterEncoding("UTF-8"); 31 | response.setCharacterEncoding("UTF-8"); 32 | 33 | if (log.isInfoEnabled()) log.info("Incoming request."); 34 | 35 | // get properties 36 | 37 | Map properties; 38 | String propertiesString; 39 | 40 | try { 41 | properties = InitServlet.getDriver() == null ? null : InitServlet.getDriver().properties(); 42 | propertiesString = properties == null ? null : objectMapper.writeValueAsString(properties); 43 | } catch (Exception ex) { 44 | if (log.isWarnEnabled()) log.warn("Properties problem: " + ex.getMessage(), ex); 45 | ServletUtil.sendResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Properties problem: " + ex.getMessage()); 46 | return; 47 | } 48 | 49 | if (log.isInfoEnabled()) log.info("Properties: " + properties); 50 | 51 | // no properties? 52 | 53 | if (properties == null) { 54 | ServletUtil.sendResponse(response, HttpServletResponse.SC_NOT_FOUND, "No properties."); 55 | return; 56 | } 57 | 58 | // write properties 59 | 60 | ServletUtil.sendResponse(response, HttpServletResponse.SC_OK, Driver.PROPERTIES_MEDIA_TYPE, propertiesString); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/src/main/java/uniresolver/examples/w3ctestsuite/TestIdentifier.java: -------------------------------------------------------------------------------- 1 | package uniresolver.examples.w3ctestsuite; 2 | 3 | import foundation.identity.did.DID; 4 | import foundation.identity.did.DIDURL; 5 | import foundation.identity.did.parameters.Parameters; 6 | import uniresolver.ResolutionException; 7 | import uniresolver.client.ClientUniResolver; 8 | import uniresolver.result.ResolveResult; 9 | 10 | import java.net.URLEncoder; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.ArrayList; 13 | import java.util.LinkedHashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class TestIdentifier { 18 | 19 | static final List dids = new ArrayList<>(); 20 | 21 | static final Map didParameters = new LinkedHashMap<>(); 22 | 23 | public static void main(String[] args) throws Exception { 24 | 25 | DID did = DID.fromString("did:sov:WRfXPg8dantKVubE3HX8pw"); 26 | dids.add(did.toString()); 27 | 28 | DIDURL didUrl1 = DIDURL.fromString(did + "?" + Parameters.DID_URL_PARAMETER_SERVICE + "=" + "files" + "&" + Parameters.DID_URL_PARAMETER_RELATIVEREF + "=" + URLEncoder.encode("/myresume/doc?version=latest#intro", StandardCharsets.UTF_8)); 29 | DIDURL didUrl2 = DIDURL.fromString(did + "?" + Parameters.DID_URL_PARAMETER_HL + "=" + "zQmWvQxTqbG2Z9HPJgG57jjwR154cKhbtJenbyYTWkjgF3e"); 30 | DIDURL didUrl3 = DIDURL.fromString(did + "?" + Parameters.DID_URL_PARAMETER_VERSIONID + "=" + "4"); 31 | DIDURL didUrl4 = DIDURL.fromString(did + "?" + Parameters.DID_URL_PARAMETER_VERSIONTIME + "=" + "2016-10-17T02:41:00Z"); 32 | 33 | didParameters.put(Parameters.DID_URL_PARAMETER_SERVICE.toString(), didUrl1.toString()); 34 | didParameters.put(Parameters.DID_URL_PARAMETER_RELATIVEREF.toString(), didUrl1.toString()); 35 | didParameters.put(Parameters.DID_URL_PARAMETER_HL.toString(), didUrl2.toString()); 36 | didParameters.put(Parameters.DID_URL_PARAMETER_VERSIONID.toString(), didUrl3.toString()); 37 | didParameters.put(Parameters.DID_URL_PARAMETER_VERSIONTIME.toString(), didUrl4.toString()); 38 | 39 | System.out.println(TestSuiteUtil.makeIdentifierTestSuiteReport("sov", dids, didParameters)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Maven and Docker release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_type: 7 | description: 'Major, Minor or Patch release' 8 | type: choice 9 | required: true 10 | default: 'minor' 11 | options: 12 | - "major" 13 | - "minor" 14 | - "patch" 15 | 16 | jobs: 17 | maven-release: 18 | uses: danubetech/workflows/.github/workflows/maven-release.yml@main 19 | with: 20 | MAVEN_REPO_SERVER_ID: 'danubetech-maven-releases' 21 | RELEASE_TYPE: ${{ github.event.inputs.release_type }} 22 | secrets: 23 | VAULT_ADDR: ${{ secrets.VAULT_ADDR }} 24 | CI_SECRET_READER_PERIODIC_TOKEN: ${{ secrets.CI_SECRET_READER_PERIODIC_TOKEN }} 25 | VAULTCA: ${{ secrets.VAULTCA }} 26 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 27 | 28 | docker-release: 29 | needs: maven-release 30 | uses: danubetech/workflows/.github/workflows/maven-triggered-docker-release.yml@main 31 | with: 32 | GLOBAL_IMAGE_NAME: universalresolver/uni-resolver-web 33 | GLOBAL_REPO_NAME: docker.io 34 | GLOBAL_FRAMEWORK: triggered 35 | PATH_TO_DOCKERFILE: uni-resolver-web/docker/Dockerfile 36 | secrets: 37 | VAULT_ADDR: ${{ secrets.VAULT_ADDR }} 38 | CI_SECRET_READER_PERIODIC_TOKEN: ${{ secrets.CI_SECRET_READER_PERIODIC_TOKEN }} 39 | VAULTCA: ${{ secrets.VAULTCA }} 40 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 41 | 42 | docker-release-multi-arch: 43 | needs: docker-release 44 | if: github.ref == 'refs/heads/main' 45 | uses: ./.github/workflows/docker-multi-arch.yml 46 | with: 47 | GLOBAL_FRAMEWORK: maven 48 | GLOBAL_IMAGE_NAME: universalresolver/uni-resolver-web 49 | GLOBAL_REPO_NAME: docker.io 50 | IS_RELEASE: true 51 | PATH_TO_DOCKERFILE: uni-resolver-web/docker/Dockerfile 52 | RELEASE_TYPE: ${{ github.event.inputs.release_type }} 53 | secrets: 54 | CI_SECRET_READER_PERIODIC_TOKEN: ${{ secrets.CI_SECRET_READER_PERIODIC_TOKEN }} 55 | VAULT_ADDR: ${{ secrets.VAULT_ADDR }} 56 | VAULTCA: ${{ secrets.VAULTCA }} 57 | -------------------------------------------------------------------------------- /ci/run-did-test-suite/README.md: -------------------------------------------------------------------------------- 1 | # run-did-test-suite action 2 | 3 | Developed with Node 14.x and without guarantee of backwards compatibility. 4 | 5 | ## env variables 6 | `MODE`: 7 | - `server`(default) Run against a did-test-suite server 8 | - `local` Create local files 9 | 10 | `DRIVER_STATUS_REPORT`: 11 | - Path to file, generated by [get-driver-status](https://github.com/decentralized-identity/universal-resolver/tree/driver-status-reporting/ci/get-driver-status) action 12 | 13 | `OUTPUT_PATH`: 14 | - In `server` mode: Path to write the did-test-suite report 15 | - In `local` mode: Path to write the created files 16 | 17 | `HOST`: 18 | - Only for `server` mode and required 19 | - Full url of deployed did-test-suite endpoint e.g. `https://did-test-suite.uniresolver.io/test-suite-manager/generate-report` 20 | 21 | `GENERATE_DEFAULT_FILE`: 22 | - Only for `local` mode and optional 23 | - If `true` script automatically creates a `default.js` file with all the resolver related files in the `OUTPUT_PATH` 24 | 25 | ### Server mode to run against hosted did-test-suite example 26 | 27 | ```bash 28 | node index.js --DRIVER_STATUS_REPORT=/driver-status-2021-05-19_15-08-15-UTC.json --HOST=https://did-test-suite.uniresolver.io/test-suite-manager/generate-report 29 | ``` 30 | 31 | ### Local mode to create manual files example 32 | 33 | ```bash 34 | node index.js --MODE=local --DRIVER_STATUS_REPORT=/driver-status-2021-05-19_15-08-15-UTC.json --OUTPUT_PATH= --GENERATE_DEFAULT_FILE=true 35 | ``` 36 | 37 | ### Github action example 38 | 39 | **Note:** Env variables for `driver_status_report` and `reports_folder` are set by previous step in this case. See [example](https://github.com/decentralized-identity/universal-resolver/blob/ccbbbf17946ff62b53f647734c382dc460661659/ci/get-driver-status/entrypoint.sh#L43) in `get-driver-status` action. 40 | 41 | ```yaml 42 | - name: Run did-test-suite 43 | uses: ./ci/run-did-test-suite 44 | with: 45 | host: https://did-test-suite.uniresolver.io/test-suite-manager/generate-report 46 | driver_status_report: ${{ env.driver_status_report }} 47 | reports_folder: ${{ env.reports_folder }} 48 | ``` 49 | -------------------------------------------------------------------------------- /uni-resolver-web/src/main/java/uniresolver/web/servlet/ServletUtil.java: -------------------------------------------------------------------------------- 1 | package uniresolver.web.servlet; 2 | 3 | import jakarta.servlet.http.HttpServletResponse; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.Map; 11 | 12 | public class ServletUtil { 13 | 14 | private static final Logger log = LoggerFactory.getLogger(ServletUtil.class); 15 | 16 | public static void sendResponse(HttpServletResponse response, int status, Map headers, byte[] body) throws IOException { 17 | 18 | if (log.isInfoEnabled()) log.info("Sending response with status " + status + " and headers " + headers + " and body (" + (body == null ? null : body.length) + ")"); 19 | 20 | response.setStatus(status); 21 | if (headers != null) for (Map.Entry header : headers.entrySet()) response.setHeader(header.getKey(), header.getValue()); 22 | response.setHeader("Access-Control-Allow-Origin", "*"); 23 | 24 | if (body != null) { 25 | OutputStream outputStream = response.getOutputStream(); 26 | outputStream.write(body); 27 | outputStream.flush(); 28 | outputStream.close(); 29 | } 30 | 31 | response.flushBuffer(); 32 | } 33 | 34 | public static void sendResponse(HttpServletResponse response, int status, String contentType, byte[] body) throws IOException { 35 | sendResponse(response, status, Map.of("Content-Type", contentType), body); 36 | } 37 | 38 | public static void sendResponse(HttpServletResponse response, int status, String contentType, String body) throws IOException { 39 | sendResponse(response, status, Map.of("Content-Type", contentType), body == null ? null : body.getBytes(StandardCharsets.UTF_8)); 40 | } 41 | 42 | public static void sendResponse(HttpServletResponse response, int status, byte[] body) throws IOException { 43 | sendResponse(response, status, (Map) null, body); 44 | } 45 | 46 | public static void sendResponse(HttpServletResponse response, int status, String body) throws IOException { 47 | sendResponse(response, status, (Map) null, body == null ? null : body.getBytes(StandardCharsets.UTF_8)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ci/deploy-k8s-aws/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Deploy Universal Resolver to AWS Kubernetes' 2 | description: 'Deploy the Universal Resolver to AWS EKS cluster using smart deployment strategy' 3 | author: 'Bernhard Fuchs ' 4 | 5 | branding: 6 | icon: 'package' 7 | color: 'blue' 8 | 9 | inputs: 10 | kube-config-data: 11 | description: 'Base64-encoded Kubernetes configuration data' 12 | required: true 13 | aws-access-key-id: 14 | description: 'AWS Access Key ID for authentication' 15 | required: true 16 | aws-secret-access-key: 17 | description: 'AWS Secret Access Key for authentication' 18 | required: true 19 | namespace: 20 | description: 'Kubernetes namespace to deploy to' 21 | required: false 22 | default: 'uni-resolver' 23 | rpc-url-testnet: 24 | description: 'RPC URL for driver-did-btcr (optional)' 25 | required: false 26 | default: '' 27 | rpc-cert-testnet: 28 | description: 'RPC certificate for driver-did-btcr (optional)' 29 | required: false 30 | default: '' 31 | slack-webhook-url: 32 | description: 'Slack webhook URL for notifications (optional)' 33 | required: false 34 | default: '' 35 | github-server-url: 36 | description: 'GitHub server URL (automatically set by GitHub Actions)' 37 | required: false 38 | default: 'https://github.com' 39 | github-repository: 40 | description: 'GitHub repository (automatically set by GitHub Actions)' 41 | required: false 42 | default: '' 43 | github-run-id: 44 | description: 'GitHub run ID (automatically set by GitHub Actions)' 45 | required: false 46 | default: '' 47 | 48 | runs: 49 | using: 'docker' 50 | image: 'Dockerfile' 51 | env: 52 | KUBE_CONFIG_DATA: ${{ inputs.kube-config-data }} 53 | AWS_ACCESS_KEY_ID: ${{ inputs.aws-access-key-id }} 54 | AWS_SECRET_ACCESS_KEY: ${{ inputs.aws-secret-access-key }} 55 | NAMESPACE: ${{ inputs.namespace }} 56 | RPC_URL_TESTNET: ${{ inputs.rpc-url-testnet }} 57 | RPC_CERT_TESTNET: ${{ inputs.rpc-cert-testnet }} 58 | SLACK_WEBHOOK_URL: ${{ inputs.slack-webhook-url }} 59 | GITHUB_SERVER_URL: ${{ inputs.github-server-url }} 60 | GITHUB_REPOSITORY: ${{ inputs.github-repository }} 61 | GITHUB_RUN_ID: ${{ inputs.github-run-id }} 62 | -------------------------------------------------------------------------------- /driver/src/main/java/uniresolver/driver/servlet/ServletUtil.java: -------------------------------------------------------------------------------- 1 | package uniresolver.driver.servlet; 2 | 3 | import jakarta.servlet.http.HttpServletResponse; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.io.PrintWriter; 10 | import java.util.Map; 11 | 12 | public class ServletUtil { 13 | 14 | private static final Logger log = LoggerFactory.getLogger(ServletUtil.class); 15 | 16 | static void sendResponse(HttpServletResponse response, int status, Map headers, Object body) throws IOException { 17 | 18 | if (log.isDebugEnabled()) log.debug("Sending response with status " + status + " and headers " + headers + " and body (" + (body == null ? null : body.getClass()) + ")"); 19 | 20 | response.setStatus(status); 21 | if (headers != null) for (Map.Entry header : headers.entrySet()) response.setHeader(header.getKey(), header.getValue()); 22 | response.setHeader("Access-Control-Allow-Origin", "*"); 23 | 24 | if (body instanceof String) { 25 | 26 | PrintWriter printWriter = response.getWriter(); 27 | printWriter.write((String) body); 28 | printWriter.flush(); 29 | printWriter.close(); 30 | } else if (body instanceof byte[]) { 31 | 32 | OutputStream outputStream = response.getOutputStream(); 33 | outputStream.write((byte[]) body); 34 | outputStream.flush(); 35 | outputStream.close(); 36 | } else if (body != null) { 37 | 38 | PrintWriter printWriter = response.getWriter(); 39 | printWriter.write(body.toString()); 40 | printWriter.flush(); 41 | printWriter.close(); 42 | } 43 | 44 | response.flushBuffer(); 45 | } 46 | 47 | static void sendResponse(HttpServletResponse response, int status, Map headers) throws IOException { 48 | 49 | sendResponse(response, status, headers, null); 50 | } 51 | 52 | static void sendResponse(HttpServletResponse response, int status, String contentType, Object body) throws IOException { 53 | 54 | sendResponse(response, status, Map.of("Content-Type", contentType), body); 55 | } 56 | 57 | static void sendResponse(HttpServletResponse response, int status, Object body) throws IOException { 58 | 59 | sendResponse(response, status, (Map) null, body); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /examples/src/main/java/uniresolver/examples/w3ctestsuite/TestDIDResolution.java: -------------------------------------------------------------------------------- 1 | package uniresolver.examples.w3ctestsuite; 2 | 3 | import uniresolver.ResolutionException; 4 | import uniresolver.client.ClientUniResolver; 5 | import uniresolver.result.ResolveResult; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | public class TestDIDResolution { 12 | 13 | static final Map> expectedOutcomes = Map.of( 14 | "defaultOutcome", List.of(0, 1), 15 | "notFoundErrorOutcome", List.of(2), 16 | "invalidDidErrorOutcome", List.of(3) 17 | ); 18 | 19 | static final List function = List.of( 20 | "resolve", 21 | "resolveRepresentation", 22 | "resolve", 23 | "resolve" 24 | ); 25 | 26 | static final List didString = List.of( 27 | "did:sov:WRfXPg8dantKVubE3HX8pw", 28 | "did:sov:WRfXPg8dantKVubE3HX8pw", 29 | "did:sov:0000000000000000000000", 30 | "did:sov:danube:_$::" 31 | ); 32 | 33 | static final List> resolutionOptions = List.of( 34 | Map.of(), 35 | Map.of( 36 | "accept", "application/did" 37 | ), 38 | Map.of(), 39 | Map.of() 40 | ); 41 | 42 | public static void main(String[] args) throws Exception { 43 | 44 | ClientUniResolver uniResolver = new ClientUniResolver(); 45 | uniResolver.setResolveUri("http://localhost:8080/1.0/identifiers/"); 46 | 47 | List resolveResults = new ArrayList<>(); 48 | 49 | for (int i=0; i> expectedOutcomes = Map.of( 16 | "defaultOutcome", List.of(0, 1), 17 | "invalidDidUrlErrorOutcome", List.of(2), 18 | "notFoundErrorOutcome", List.of(3, 4) 19 | ); 20 | 21 | static final List function = List.of( 22 | "dereference", 23 | "dereference", 24 | "dereference", 25 | "dereference", 26 | "dereference" 27 | ); 28 | 29 | static final List didUrlString = List.of( 30 | "did:sov:WRfXPg8dantKVubE3HX8pw", 31 | "did:sov:WRfXPg8dantKVubE3HX8pw#key-1", 32 | "did:sov:WRfXPg8dantKVubE3HX8pw#key-1#key-2", 33 | "did:sov:WRfXPg8dantKVubE3HX8pw#key-3", 34 | "did:sov:0000000000000000000000" 35 | ); 36 | 37 | public static void main(String[] args) throws Exception { 38 | 39 | Map dereferenceOptions = new HashMap<>(); 40 | dereferenceOptions.put("accept", "application/did"); 41 | 42 | ClientUniResolver uniResolver = new ClientUniResolver(); 43 | uniResolver.setResolveUri("http://localhost:8080/1.0/identifiers/"); 44 | 45 | LocalUniDereferencer uniDereferencer = new LocalUniDereferencer(); 46 | uniDereferencer.setUniResolver(uniResolver); 47 | 48 | List dereferenceResults = new ArrayList<>(); 49 | 50 | for (int i=0; i { 2 | const didWithParams = url.split('/'); 3 | return didWithParams[didWithParams.length - 1].split('?')[0]; 4 | } 5 | 6 | const extractMethodName = (url) => { 7 | return extractDid(url).split(':')[1]; 8 | } 9 | 10 | const getWorkingMethods = (resolutionResults) => { 11 | const workingMethods = []; 12 | const urls = Object.keys( resolutionResults ); 13 | urls.forEach(url => { 14 | if (resolutionResults[url].status === 200 && resolutionResults[url].resolutionResponse["application/did+ld+json"].didDocument.id !== undefined) { 15 | workingMethods.push(extractMethodName(url)); 16 | } 17 | }) 18 | return Array.from(new Set(workingMethods)) 19 | } 20 | 21 | const getWorkingUrls = (resolutionResults) => { 22 | const workingUrls = []; 23 | const urls = Object.keys( resolutionResults ); 24 | urls.forEach(url => { 25 | if (resolutionResults[url].status === 200 && resolutionResults[url].resolutionResponse["application/did+ld+json"].didDocument.id !== undefined) { 26 | workingUrls.push(url); 27 | } 28 | }) 29 | return Array.from(new Set(workingUrls)) 30 | } 31 | 32 | const createExpectedOutcomes = (testData, resolutionResult, index) => { 33 | 34 | if (resolutionResult.resolutionResponse["application/did+ld+json"].didDocumentMetadata.deactivated === true) { 35 | testData.expectedOutcomes.deactivatedOutcome[0] === undefined ? 36 | testData.expectedOutcomes.deactivatedOutcome[0] = index : 37 | testData.expectedOutcomes.deactivatedOutcome.push(index) 38 | } else { 39 | testData.expectedOutcomes.defaultOutcomes[0] === undefined ? 40 | testData.expectedOutcomes.defaultOutcomes[0] = index : 41 | testData.expectedOutcomes.defaultOutcomes.push(index) 42 | } 43 | } 44 | 45 | const resetTestData = (testData) => { 46 | testData.executions = []; 47 | testData.expectedOutcomes.defaultOutcomes = []; 48 | testData.expectedOutcomes.invalidDidErrorOutcome = []; 49 | testData.expectedOutcomes.notFoundErrorOutcome = []; 50 | testData.expectedOutcomes.representationNotSupportedErrorOutcome = []; 51 | testData.expectedOutcomes.deactivatedOutcome = []; 52 | } 53 | 54 | module.exports = { 55 | extractDid, 56 | extractMethodName, 57 | getWorkingMethods, 58 | getWorkingUrls, 59 | createExpectedOutcomes, 60 | resetTestData 61 | } -------------------------------------------------------------------------------- /ci/setup-aws-route53/setup-route53-guide.md: -------------------------------------------------------------------------------- 1 | # DNS ALIAS Setup for ALB (Route53) 2 | 3 | ## Why this is needed (concise) 4 | 5 | Kubernetes Ingress on AWS provisions an **Application Load Balancer (ALB)** with its own DNS name (e.g. `…elb.amazonaws.com`). 6 | To make public domains like `dev.uniresolver.io` and `resolver.identity.foundation` resolve to that ALB, create **Route53 ALIAS A records** that point those names to the ALB. Without these ALIAS records, DNS lookups return nothing and browsers show **“Server Not Found.”** 7 | 8 | --- 9 | 10 | ## Get Hosted Zone IDs (Route53) 11 | 12 | Use these commands to locate the correct public hosted zones (copy the `HostedZones[0].Id` value and strip the `/hostedzone/` prefix): 13 | 14 | ```bash 15 | aws route53 list-hosted-zones-by-name --dns-name dev.uniresolver.io 16 | aws route53 list-hosted-zones-by-name --dns-name resolver.identity.foundation 17 | ``` 18 | 19 | > If a subdomain has its **own** hosted zone, ensure the parent zone delegates to it (NS records present in the parent). Otherwise, public DNS will remain blank even after updates. 20 | 21 | --- 22 | 23 | ## Verify DNS with `dig` 24 | 25 | After creating or updating ALIAS records, verify resolution to the ALB: 26 | 27 | ```bash 28 | # A/ALIAS resolution 29 | dig +short dev.uniresolver.io 30 | dig +short resolver.identity.foundation 31 | 32 | # Optional: trace—useful to debug delegation problems 33 | dig +trace dev.uniresolver.io 34 | dig +trace resolver.identity.foundation 35 | 36 | # Optional: IPv6 if an AAAA ALIAS to dualstack ALB was created 37 | dig AAAA +short dev.uniresolver.io 38 | dig AAAA +short resolver.identity.foundation 39 | ``` 40 | 41 | --- 42 | 43 | ## Run the scripts 44 | 45 | Two executable scripts exist—one per domain. They **UPSERT** an ALIAS A record to the ALB and wait for Route53 **INSYNC**: 46 | 47 | ```bash 48 | # From the repository directory where the scripts live 49 | ./set-alias-dev.uniresolver.io.sh 50 | ./set-alias-resolver.identity.foundation.sh 51 | ``` 52 | 53 | Re‑check DNS after they finish: 54 | 55 | ```bash 56 | dig +short dev.uniresolver.io 57 | dig +short resolver.identity.foundation 58 | ``` 59 | 60 | > If `dig` returns blank after INSYNC, re-check **delegation** (parent zone must contain `NS` records for the child zone). 61 | 62 | --- 63 | 64 | ## Notes 65 | 66 | * Current setup listens on **HTTP (80)**. HTTPS requires adding an ACM certificate and a 443 listener via Ingress annotations. 67 | * The scripts auto-detect the ALB’s **CanonicalHostedZoneId** via `elbv2` to build a correct ALIAS record. 68 | -------------------------------------------------------------------------------- /docs/continuous-integration-and-delivery.md: -------------------------------------------------------------------------------- 1 | # Universal Resolver — Continuous Integration and Delivery 2 | 3 | This section describes the building-blocks and ideas of the implemented CI/CD pipeline. In case of issues or requests in the scope of CI/CD, please directly consult the maintainers of the repository. 4 | 5 | ## Intro 6 | 7 | The CI/CD pipeline helps achieving the following goals: 8 | * Detection of problems asap 9 | * Short and robust release cycles 10 | * Avoidance of repetitive, manual tasks 11 | * Increase of Software Quality 12 | 13 | After every code change the CI/CD pipeline builds all software packages/Docker containers automatically. Once the containers are built, they are automatically deployed to the dev-system. By these measures building as well as deployment issues are immediately discovered. Once the freshly built containers are deployed, automatic tests are run in order to verify the software and to detect functional issues. 14 | 15 | ## Building Blocks 16 | 17 | The CI/CD pipeline is constructed by using GitHub Actions. The workflows are run after every push to the `main` branch and on ever PR against the `main` branch. 18 | The workflows consist of several steps. Currently, the two main steps are: 19 | 20 | 1. Building the resolver (workflow file https://github.com/decentralized-identity/universal-resolver/blob/main/.github/workflows/docker-build-and-deploy-image.yml) 21 | 2. Deploying the resolver (workflow file https://github.com/decentralized-identity/universal-resolver/blob/main/.github/workflows/kubernetes-deploy-to-cluster.yml) 22 | 23 | The first step builds a Docker image and pushes it to Docker Hub at https://hub.docker.com/u/universalresolver. 24 | The second step takes the image and deploys it (create or update) to the configured Kubernetes cluster. 25 | 26 | ## Steps of the CI/CD Workflow 27 | 28 | ![](https://user-images.githubusercontent.com/55081379/68245944-2a78db00-0018-11ea-8ebe-22c19d5ad096.PNG) 29 | 30 | 1. Dev pushes code to GitHub 31 | 2. GitHub Actions (GHA) is triggered by the „push“ event, clones the repo and runs the workflow for every container: 32 | * (a) Docker build 33 | * (b) Docker push (Docker image goes to DockerHub) 34 | * (c) Deploy to Kubernetes 35 | * (d) Runs a test-container 36 | 3. Manual and automated feedback 37 | 38 | ## Open Issues regarding CI/CD 39 | 40 | * Make all drivers accessible via sub-domains eg. elem.dev.uniresolver.io 41 | * Bundle new release for resolver.identity.foundation (only working drivers) 42 | * Render Smoke Test results as HTML-page and host it via gh-pages 43 | * Update documentation 44 | -------------------------------------------------------------------------------- /docs/dev-system.md: -------------------------------------------------------------------------------- 1 | # Universal Resolver — Development System 2 | 3 | The development instance, which runs the latest code-base of the Universal Resolver project, is hosted at: 4 | 5 | https://dev.uniresolver.io 6 | 7 | DIDs can be resolved by calling the resolver: 8 | 9 | https://dev.uniresolver.io/1.0/identifiers/did:btcr:xz35-jznz-q6mr-7q6 10 | 11 | 12 | The software is automatically updated on every commit and PR on the master branch. See [CI-CD](/docs/continuous-integration-and-delivery.md) for more details 13 | 14 | Currently the system is deployed in the AWS cloud by the use of the Elastic Kubernetes Service (EKS). Please be aware that the use of AWS is not mandatory for hosting the resolver. Any environment that supports Docker Compose or Kubernetes will be capable of running an instance of the Universal Resolver. 15 | 16 | We are using two `m5.large` instances (2 vCPU / 8GB RAM) due to the limitations of 29 pods per instance of this type on AWS EKS. This should not be treated as a recommendation, just an information. 17 | 18 | ## AWS Architecture 19 | 20 | This picture illustrates the AWS architecture for hosting the Universal resolver as well as the traffic-flow through the system. 21 | 22 |

23 | 24 | The entry-point to the system is the public Internet facing Application Load Balancer (ALB), that sits at the edge of the AWS cloud and is bound to the DNS name “dev.uniresolver.io”. When resolving DIDs the traffic flows through the ALB to the resolver. Based on the configuration of each DID-method the resolver calls the corresponding DID-driver (typical scenario) or may call another HTTP endpoint (another resolver or directly the DLT if HTTP is supported). In order to gain performance, blockchain nodes may also be added to the deployment, as sketched at Driver C. 25 | 26 | The Kubernetes cluster is spanned across multiple Availability Zones (AZ), which are essential parts for providing fault-tolerance and achieving a high-availability HA of the system. This means that no downtime is to be expected in case of failing parts of the system, as the healthy parts will take over operations reliably. 27 | 28 | If containers, like Universal Resolver drivers, are added or removed, the ALB ingress controller https://kubernetes-sigs.github.io/aws-alb-ingress-controller/ takes care of notifying the ALB. Due to this mechanism the ALB stays aware of the system state and is able to keep traffic-routes healthy. 29 | 30 | Further details regarding the automated system-update are described at [CI-CD](/docs/continuous-integration-and-delivery.md). 31 | -------------------------------------------------------------------------------- /.github/workflows/latest.yml: -------------------------------------------------------------------------------- 1 | name: Maven snapshot and Docker latest image 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '.gitignore' 7 | - 'README.md' 8 | - 'LICENSE' 9 | - 'docs' 10 | branches: [ main, 'test-**' ] 11 | workflow_dispatch: 12 | 13 | jobs: 14 | maven-snapshot: 15 | uses: danubetech/workflows/.github/workflows/maven-snapshot.yml@main 16 | with: 17 | MAVEN_REPO_SERVER_ID: 'danubetech-maven-snapshots' 18 | secrets: 19 | VAULT_ADDR: ${{ secrets.VAULT_ADDR }} 20 | CI_SECRET_READER_PERIODIC_TOKEN: ${{ secrets.CI_SECRET_READER_PERIODIC_TOKEN }} 21 | VAULTCA: ${{ secrets.VAULTCA }} 22 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 23 | 24 | docker-latest: 25 | needs: [ maven-snapshot ] 26 | uses: danubetech/workflows/.github/workflows/docker-latest.yml@main 27 | with: 28 | GLOBAL_IMAGE_NAME: universalresolver/uni-resolver-web 29 | GLOBAL_REPO_NAME: docker.io 30 | PATH_TO_DOCKERFILE: uni-resolver-web/docker/Dockerfile 31 | secrets: 32 | VAULT_ADDR: ${{ secrets.VAULT_ADDR }} 33 | CI_SECRET_READER_PERIODIC_TOKEN: ${{ secrets.CI_SECRET_READER_PERIODIC_TOKEN }} 34 | VAULTCA: ${{ secrets.VAULTCA }} 35 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 36 | 37 | docker-latest-multi-arch: 38 | needs: docker-latest 39 | if: github.ref == 'refs/heads/main' 40 | uses: ./.github/workflows/docker-multi-arch.yml 41 | with: 42 | GLOBAL_IMAGE_NAME: universalresolver/uni-resolver-web 43 | GLOBAL_REPO_NAME: docker.io 44 | IMAGE_TAG: latest 45 | PATH_TO_DOCKERFILE: uni-resolver-web/docker/Dockerfile 46 | secrets: 47 | CI_SECRET_READER_PERIODIC_TOKEN: ${{ secrets.CI_SECRET_READER_PERIODIC_TOKEN }} 48 | VAULT_ADDR: ${{ secrets.VAULT_ADDR }} 49 | VAULTCA: ${{ secrets.VAULTCA }} 50 | 51 | trigger-deployment: 52 | needs: [ docker-latest ] 53 | runs-on: ubuntu-latest 54 | steps: 55 | 56 | - name: Get current branch 57 | id: branch 58 | run: | 59 | echo "branch_name=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT 60 | 61 | - name: Trigger AWS Kubernetes deployment 62 | uses: actions/github-script@v6 63 | env: 64 | BRANCH: ${{ steps.branch.outputs.branch_name }} 65 | with: 66 | github-token: ${{ secrets.GITHUB_TOKEN }} 67 | script: | 68 | await github.rest.actions.createWorkflowDispatch({ 69 | owner: context.repo.owner, 70 | repo: context.repo.repo, 71 | workflow_id: 'kubernetes-deploy-to-cluster.yml', 72 | ref: process.env.BRANCH 73 | }) 74 | -------------------------------------------------------------------------------- /examples/sovrin/danube.txn: -------------------------------------------------------------------------------- 1 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blskey":"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba","blskey_pop":"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1","client_ip":"92.42.139.24","client_port":9702,"node_ip":"92.42.139.24","node_port":9701,"services":["VALIDATOR"]},"dest":"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv"},"metadata":{"from":"Th7MpTaRZVRYnPiabds81Y"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62"},"ver":"1"} 2 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"Node2","blskey":"37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk","blskey_pop":"Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5","client_ip":"92.42.139.24","client_port":9704,"node_ip":"92.42.139.24","node_port":9703,"services":["VALIDATOR"]},"dest":"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb"},"metadata":{"from":"EbP4aYNeTHL6q385GuVpRV"},"type":"0"},"txnMetadata":{"seqNo":2,"txnId":"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc"},"ver":"1"} 3 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"Node3","blskey":"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5","blskey_pop":"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh","client_ip":"92.42.139.24","client_port":9706,"node_ip":"92.42.139.24","node_port":9705,"services":["VALIDATOR"]},"dest":"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"},"metadata":{"from":"4cU41vWW82ArfxJxHkzXPG"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"},"ver":"1"} 4 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"Node4","blskey":"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw","blskey_pop":"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP","client_ip":"92.42.139.24","client_port":9708,"node_ip":"92.42.139.24","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"},"metadata":{"from":"TWwCRQRZ2ZHMJFn9TzLp7W"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"},"ver":"1"} 5 | -------------------------------------------------------------------------------- /uni-resolver-local/src/main/java/uniresolver/local/extensions/DereferencerExtension.java: -------------------------------------------------------------------------------- 1 | package uniresolver.local.extensions; 2 | 3 | import foundation.identity.did.DIDURL; 4 | import uniresolver.DereferencingException; 5 | import uniresolver.ResolutionException; 6 | import uniresolver.local.LocalUniDereferencer; 7 | import uniresolver.result.DereferenceResult; 8 | import uniresolver.result.ResolveResult; 9 | 10 | import java.lang.annotation.ElementType; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.RetentionPolicy; 13 | import java.lang.annotation.Target; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public interface DereferencerExtension { 18 | 19 | @Retention(RetentionPolicy.RUNTIME) 20 | @Target(ElementType.TYPE) 21 | @interface ExtensionStage { 22 | String value(); 23 | } 24 | 25 | @FunctionalInterface 26 | interface ExtensionFunction { 27 | ExtensionStatus apply(E extension) throws ResolutionException, DereferencingException; 28 | } 29 | 30 | @ExtensionStage("beforeDereference") 31 | interface BeforeDereferenceDereferencerExtension extends DereferencerExtension { 32 | default ExtensionStatus beforeDereference(DIDURL didUrl, Map dereferenceOptions, DereferenceResult dereferenceResult, Map executionState, LocalUniDereferencer localUniDereferencer) throws ResolutionException, DereferencingException { 33 | return null; 34 | } 35 | } 36 | 37 | @ExtensionStage("dereferencePrimary") 38 | interface DereferencePrimaryDereferencerExtension extends DereferencerExtension { 39 | default ExtensionStatus dereferencePrimary(DIDURL didUrlWithoutFragment, Map dereferenceOptions, ResolveResult resolveResult, DereferenceResult dereferenceResult, Map executionState, LocalUniDereferencer localUniDereferencer) throws ResolutionException, DereferencingException { 40 | return null; 41 | } 42 | } 43 | 44 | @ExtensionStage("dereferenceSecondary") 45 | interface DereferenceSecondaryDereferencerExtension extends DereferencerExtension { 46 | default ExtensionStatus dereferenceSecondary(DIDURL didUrlWithoutFragment, String didUrlFragment, Map dereferenceOptions, DereferenceResult dereferenceResult, Map executionState, LocalUniDereferencer localUniDereferencer) throws ResolutionException, DereferencingException { 47 | return null; 48 | } 49 | } 50 | 51 | @ExtensionStage("afterDereference") 52 | interface AfterDereferenceDereferencerExtension extends DereferencerExtension { 53 | default ExtensionStatus afterDereference(DIDURL didUrl, Map dereferenceOptions, DereferenceResult dereferenceResult, Map executionState, LocalUniDereferencer localUniDereferencer) throws ResolutionException, DereferencingException { 54 | return null; 55 | } 56 | } 57 | 58 | abstract class AbstractDereferencerExtension implements BeforeDereferenceDereferencerExtension, DereferencePrimaryDereferencerExtension, DereferenceSecondaryDereferencerExtension, AfterDereferenceDereferencerExtension { 59 | } 60 | 61 | static List extensionClassNames(List extensions) { 62 | return extensions.stream().map(e -> e.getClass().getSimpleName()).toList(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/branching-strategy.md: -------------------------------------------------------------------------------- 1 | # Universal Resolver — Branching Strategy 2 | 3 | The goals for our branching strategy are: 4 | * The `main` branch should be deployable at any time. This implies a stable build and the assurance that the core-functionality is provided at any time when cloning the `main` branch. 5 | * The `main` branch should stay active. As collaboratively working with multiple developers, we encourage merging the code as frequently as possible. This will potentially disclose issues at an early stage and facilitate the repair. Furthermore, it makes clear that the `main` branch is the preferred choice that newcomers want to clone. 6 | * In order not to waste time the branching strategy should stay as simple as possible. 7 | 8 | Among a bunch of various strategies we have chosen *GitHub Flow*, which is a lightweight branching-strategy encouraged by the GitHub dev-teams. Details can be found here: https://guides.github.com/introduction/flow/ 9 | The following recipe shortly describes the typical steps that developers need to be take into account when updating the code base: 10 | 11 | ![](https://hackernoon.com/hn-images/1*iHPPa72N11sBI_JSDEGxEA.png) 12 | 13 | 1. ***Create a branch***: When implementing a new feature, some improvement or a bugfix, a new branch should be created of the `main` branch. In order to preserve an organized git-repo, we agreed on following prefixes for branches: 14 | 15 | - feature-\ -> adding/changing functionality, whereas the branch-name should reflect the intention 16 | - bugfix-\ -> fixing Github-issues 17 | - refactor-\ -> cleanup, maintenance, improving code quality, adding Unit tests 18 | - ci-\ -> anything related to continuous integration 19 | - docs-\ -> updating/extending documentation, no code changes allowed 20 | - release-\ -> release of a new major version 21 | 22 | Feature branches are the most inclusive, which can contain refactoring and documentation. 23 | Always use the GitHub-issue name as part of the branch-name, if there is a corresponding issue available. 24 | 25 | 2. ***Commit some code***: Add your changes to the new branch and commit regularly with a descriptive commit-message. This builds up a transparent history of work and makes a roll back of changes easier. 26 | 3. ***Open a Pull Request (PR)***: Once your changes are complete (or you want some feedback at an early stage of development) open a PR against the `main` branch. A PR initiates a discussion with the maintainer, which will review your code at this point. Furthermore, the [[CI/CD process|Continuous-Integration-and-Delivery]] will be kicked off and your code will be deployed to the dev-system. 27 | 4. ***Discuss, review code and deployment***: Wait for feedback of the maintainer and check the deployment at the dev-system. In case of contributing a new driver the maintainer will also add the deployment-scripts in the scope of this PR. You may also be requested to make some changes to your code. Finally, the new changes should be safely incorporated to the `main` branch and the updated Universal Resolver is running smoothly in dev-environment. 28 | 5. ***Merge to the `main` branch***: If all parties involved in the discussion are satisfied, the maintainer will merge the PR into the `main` branch and will close the PR itself. You are free to delete your branch as all changes have already been incorporated in the `main` branch. 29 | -------------------------------------------------------------------------------- /uni-resolver-web/src/main/java/uniresolver/web/config/DriverConfigs.java: -------------------------------------------------------------------------------- 1 | package uniresolver.web.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | @Configuration 10 | @ConfigurationProperties("uniresolver") 11 | public class DriverConfigs { 12 | 13 | private List drivers; 14 | 15 | public List getDrivers() { 16 | return drivers; 17 | } 18 | 19 | public void setDrivers(List drivers) { 20 | this.drivers = drivers; 21 | } 22 | 23 | public static class DriverConfig { 24 | 25 | private String pattern; 26 | private String url; 27 | private String propertiesEndpoint; 28 | private String supportsOptions; 29 | private String supportsDereference; 30 | private String acceptHeaderValue; 31 | private String acceptHeaderValueDereference; 32 | private List testIdentifiers; 33 | private Map traits; 34 | 35 | public String getPattern() { 36 | return pattern; 37 | } 38 | 39 | public void setPattern(String value) { 40 | this.pattern = value; 41 | } 42 | 43 | public String getUrl() { 44 | return url; 45 | } 46 | 47 | public void setUrl(String value) { 48 | this.url = value; 49 | } 50 | 51 | public String getPropertiesEndpoint() { 52 | return propertiesEndpoint; 53 | } 54 | 55 | public void setPropertiesEndpoint(String value) { 56 | this.propertiesEndpoint = value; 57 | } 58 | 59 | public String getSupportsOptions() { 60 | return supportsOptions; 61 | } 62 | 63 | public void setSupportsOptions(String supportsOptions) { 64 | this.supportsOptions = supportsOptions; 65 | } 66 | 67 | public String getSupportsDereference() { 68 | return supportsDereference; 69 | } 70 | 71 | public void setSupportsDereference(String supportsDereference) { 72 | this.supportsDereference = supportsDereference; 73 | } 74 | 75 | public String getAcceptHeaderValue() { 76 | return acceptHeaderValue; 77 | } 78 | 79 | public void setAcceptHeaderValue(String acceptHeaderValue) { 80 | this.acceptHeaderValue = acceptHeaderValue; 81 | } 82 | 83 | public String getAcceptHeaderValueDereference() { 84 | return this.acceptHeaderValueDereference; 85 | } 86 | 87 | public void setAcceptHeaderValueDereference(String acceptHeaderValueDereference) { 88 | this.acceptHeaderValueDereference = acceptHeaderValueDereference; 89 | } 90 | 91 | public List getTestIdentifiers() { 92 | return testIdentifiers; 93 | } 94 | 95 | public void setTestIdentifiers(List value) { 96 | this.testIdentifiers = value; 97 | } 98 | 99 | public Map getTraits() { 100 | return traits; 101 | } 102 | 103 | public void setTraits(Map traits) { 104 | this.traits = traits; 105 | } 106 | 107 | @Override 108 | public String toString() { 109 | return "DriverConfig{" + 110 | "pattern='" + pattern + '\'' + 111 | ", url='" + url + '\'' + 112 | ", propertiesEndpoint='" + propertiesEndpoint + '\'' + 113 | ", supportsOptions='" + supportsOptions + '\'' + 114 | ", supportsDereference='" + supportsDereference + '\'' + 115 | ", acceptHeaderValue='" + acceptHeaderValue + '\'' + 116 | ", acceptHeaderValueDereference='" + acceptHeaderValueDereference + '\'' + 117 | ", testIdentifiers=" + testIdentifiers + 118 | ", traits=" + traits + 119 | '}'; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /examples/src/main/java/uniresolver/examples/w3ctestsuite/TestSuiteUtil.java: -------------------------------------------------------------------------------- 1 | package uniresolver.examples.w3ctestsuite; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import foundation.identity.did.DID; 5 | import uniresolver.result.DereferenceResult; 6 | import uniresolver.result.ResolveResult; 7 | 8 | import java.util.*; 9 | 10 | public class TestSuiteUtil { 11 | 12 | private static final ObjectMapper objectMapper = new ObjectMapper(); 13 | 14 | static String makeIdentifierTestSuiteReport(String didMethod, List dids, Map didParameters) throws Exception { 15 | 16 | Map json = new LinkedHashMap<>(); 17 | json.put("implementation", "Universal Resolver"); 18 | json.put("implementer", "Decentralized Identity Foundation and Contributors"); 19 | json.put("didMethod", didMethod); 20 | json.put("dids", dids); 21 | json.put("didParameters", didParameters); 22 | 23 | return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json); 24 | } 25 | 26 | static String makeDidResolutionTestSuiteReport(Map> expectedOutcomes, List function, List didString, String didMethod, List> resolutionOptions, List resolveResults) throws Exception { 27 | 28 | List executions = new ArrayList<>(); 29 | 30 | for (int i=0; i input = new LinkedHashMap<>(); 33 | input.put("did", didString.get(i)); 34 | input.put("resolutionOptions", resolutionOptions.get(i)); 35 | 36 | Map output = resolveResults.get(i).toMap(); 37 | 38 | Map execution = new LinkedHashMap<>(); 39 | execution.put("function", function.get(i)); 40 | execution.put("input", input); 41 | execution.put("output", output); 42 | 43 | executions.add(execution); 44 | } 45 | 46 | Map json = new LinkedHashMap<>(); 47 | json.put("implementation", "Universal Resolver"); 48 | json.put("implementer", "Decentralized Identity Foundation and Contributors"); 49 | json.put("didMethod", didMethod); 50 | json.put("expectedOutcomes", expectedOutcomes); 51 | json.put("executions", executions); 52 | 53 | return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json); 54 | } 55 | 56 | static String makeDidUrlDereferencingTestSuiteReport(Map> expectedOutcomes, List function, List didUrlString, String didMethod, Map dereferenceOptions, List dereferenceResults) throws Exception { 57 | 58 | List executions = new ArrayList<>(); 59 | 60 | for (int i=0; i input = new LinkedHashMap<>(); 63 | input.put("didUrl", didUrlString.get(i)); 64 | input.put("dereferenceOptions", dereferenceOptions); 65 | 66 | Map output = dereferenceResults.get(i).toMap(); 67 | 68 | Map execution = new LinkedHashMap<>(); 69 | execution.put("function", function.get(i)); 70 | execution.put("input", input); 71 | execution.put("output", output); 72 | 73 | executions.add(execution); 74 | } 75 | 76 | Map json = new LinkedHashMap<>(); 77 | json.put("implementation", "Universal Resolver"); 78 | json.put("implementer", "Decentralized Identity Foundation and Contributors"); 79 | json.put("didMethod", didMethod); 80 | json.put("expectedOutcomes", expectedOutcomes); 81 | json.put("executions", executions); 82 | 83 | return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(json); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /examples/sovrin/mainnet.txn: -------------------------------------------------------------------------------- 1 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"ev1","client_ip":"54.207.36.81","client_port":"9702","node_ip":"18.231.96.215","node_port":"9701","services":["VALIDATOR"]},"dest":"GWgp6huggos5HrzHVDy5xeBkYHxPvrRZzjPNAyJAqpjA"},"metadata":{"from":"J4N1K1SEB8uY2muwmecY5q"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"b0c82a3ade3497964cb8034be915da179459287823d92b5717e6d642784c50e6"},"ver":"1"} 2 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"zaValidator","client_ip":"154.0.164.39","client_port":"9702","node_ip":"154.0.164.39","node_port":"9701","services":["VALIDATOR"]},"dest":"BnubzSjE3dDVakR77yuJAuDdNajBdsh71ZtWePKhZTWe"},"metadata":{"from":"UoFyxT8BAqotbkhiehxHCn"},"type":"0"},"txnMetadata":{"seqNo":2,"txnId":"d5f775f65e44af60ff69cfbcf4f081cd31a218bf16a941d949339dadd55024d0"},"ver":"1"} 3 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"danube","client_ip":"128.130.204.35","client_port":"9722","node_ip":"128.130.204.35","node_port":"9721","services":["VALIDATOR"]},"dest":"476kwEjDj5rxH5ZcmTtgnWqDbAnYJAGGMgX7Sq183VED"},"metadata":{"from":"BrYDA5NubejDVHkCYBbpY5"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"ebf340b317c044d970fcd0ca018d8903726fa70c8d8854752cd65e29d443686c"},"ver":"1"} 4 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"royal_sovrin","client_ip":"35.167.133.255","client_port":"9702","node_ip":"35.167.133.255","node_port":"9701","services":["VALIDATOR"]},"dest":"Et6M1U7zXQksf7QM6Y61TtmXF1JU23nsHCwcp1M9S8Ly"},"metadata":{"from":"4ohadAwtb2kfqvXynfmfbq"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"24d391604c62e0e142ea51c6527481ae114722102e27f7878144d405d40df88d"},"ver":"1"} 5 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"digitalbazaar","client_ip":"34.226.105.29","client_port":"9701","node_ip":"34.226.105.29","node_port":"9700","services":["VALIDATOR"]},"dest":"D9oXgXC3b6ms3bXxrUu6KqR65TGhmC1eu7SUUanPoF71"},"metadata":{"from":"rckdVhnC5R5WvdtC83NQp"},"type":"0"},"txnMetadata":{"seqNo":5,"txnId":"56e1af48ef806615659304b1e5cf3ebf87050ad48e6310c5e8a8d9332ac5c0d8"},"ver":"1"} 6 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"OASFCU","client_ip":"38.70.17.248","client_port":"9702","node_ip":"38.70.17.248","node_port":"9701","services":["VALIDATOR"]},"dest":"8gM8NHpq2cE13rJYF33iDroEGiyU6wWLiU1jd2J4jSBz"},"metadata":{"from":"BFAeui85mkcuNeQQhZfqQY"},"type":"0"},"txnMetadata":{"seqNo":6,"txnId":"825aeaa33bc238449ec9bd58374b2b747a0b4859c5418da0ad201e928c3049ad"},"ver":"1"} 7 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"BIGAWSUSEAST1-001","client_ip":"34.224.255.108","client_port":"9796","node_ip":"34.224.255.108","node_port":"9769","services":["VALIDATOR"]},"dest":"HMJedzRbFkkuijvijASW2HZvQ93ooEVprxvNhqhCJUti"},"metadata":{"from":"L851TgZcjr6xqh4w6vYa34"},"type":"0"},"txnMetadata":{"seqNo":7,"txnId":"40fceb5fea4dbcadbd270be6d5752980e89692151baf77a6bb64c8ade42ac148"},"ver":"1"} 8 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"DustStorm","client_ip":"207.224.246.57","client_port":"9712","node_ip":"207.224.246.57","node_port":"9711","services":["VALIDATOR"]},"dest":"8gGDjbrn6wdq6CEjwoVStjQCEj3r7FCxKrA5d3qqXxjm"},"metadata":{"from":"FjuHvTjq76Pr9kdZiDadqq"},"type":"0"},"txnMetadata":{"seqNo":8,"txnId":"6d1ee3eb2057b8435333b23f271ab5c255a598193090452e9767f1edf1b4c72b"},"ver":"1"} 9 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"prosovitor","client_ip":"138.68.240.143","client_port":"9711","node_ip":"138.68.240.143","node_port":"9710","services":["VALIDATOR"]},"dest":"C8W35r9D2eubcrnAjyb4F3PC3vWQS1BHDg7UvDkvdV6Q"},"metadata":{"from":"Y1ENo59jsXYvTeP378hKWG"},"type":"0"},"txnMetadata":{"seqNo":9,"txnId":"15f22de8c95ef194f6448cfc03e93aeef199b9b1b7075c5ea13cfef71985bd83"},"ver":"1"} 10 | {"reqSignature":{},"txn":{"data":{"data":{"alias":"iRespond","client_ip":"52.187.10.28","client_port":"9702","node_ip":"52.187.10.28","node_port":"9701","services":["VALIDATOR"]},"dest":"3SD8yyJsK7iKYdesQjwuYbBGCPSs1Y9kYJizdwp2Q1zp"},"metadata":{"from":"JdJi97RRDH7Bx7khr1znAq"},"type":"0"},"txnMetadata":{"seqNo":10,"txnId":"b65ce086b631ed75722a4e1f28fc9cf6119b8bc695bbb77b7bdff53cfe0fc2e2"},"ver":"1"} 11 | -------------------------------------------------------------------------------- /uni-resolver-local/src/main/java/uniresolver/local/extensions/ExtensionStatus.java: -------------------------------------------------------------------------------- 1 | package uniresolver.local.extensions; 2 | 3 | public class ExtensionStatus { 4 | 5 | public static final ExtensionStatus DEFAULT = new ExtensionStatus(); 6 | 7 | public static final ExtensionStatus SKIP_BEFORE_RESOLVE = new ExtensionStatus(true, false, false, false, false, false, false); 8 | public static final ExtensionStatus SKIP_RESOLVE = new ExtensionStatus(false, true, false, false, false, false, false); 9 | public static final ExtensionStatus SKIP_AFTER_RESOLVE = new ExtensionStatus(false, false, true, false, false, false, false); 10 | public static final ExtensionStatus SKIP_BEFORE_DEREFERENCE = new ExtensionStatus(false, false, false, true, false, false, false); 11 | public static final ExtensionStatus SKIP_DEREFERENCE_PRIMARY = new ExtensionStatus(false, false, false, false, true, false, false); 12 | public static final ExtensionStatus SKIP_DEREFERENCE_SECONDARY = new ExtensionStatus(false, false, false, false, false, true, false); 13 | public static final ExtensionStatus SKIP_AFTER_DEREFERENCE = new ExtensionStatus(false, false, false, false, false, false, true); 14 | 15 | private boolean skipBeforeResolve; 16 | private boolean skipResolve; 17 | private boolean skipAfterResolve; 18 | private boolean skipBeforeDereference; 19 | private boolean skipDereferencePrimary; 20 | private boolean skipDereferenceSecondary; 21 | private boolean skipAfterDereference; 22 | 23 | public ExtensionStatus(boolean skipBeforeResolve, boolean skipResolve, boolean skipAfterResolve, boolean skipBeforeDereference, boolean skipDereferencePrimary, boolean skipDereferenceSecondary, boolean skipAfterDereference) { 24 | 25 | this.skipBeforeResolve = skipBeforeResolve; 26 | this.skipResolve = skipResolve; 27 | this.skipAfterResolve = skipAfterResolve; 28 | this.skipBeforeDereference = skipBeforeDereference; 29 | this.skipDereferencePrimary = skipDereferencePrimary; 30 | this.skipDereferenceSecondary = skipDereferenceSecondary; 31 | this.skipAfterDereference = skipAfterDereference; 32 | } 33 | 34 | public ExtensionStatus() { 35 | 36 | this(false, false, false, false, false, false, false); 37 | } 38 | 39 | public void or(ExtensionStatus extensionStatus) { 40 | 41 | if (extensionStatus == null) return; 42 | 43 | this.skipBeforeResolve |= extensionStatus.skipBeforeResolve; 44 | this.skipResolve |= extensionStatus.skipResolve; 45 | this.skipAfterResolve |= extensionStatus.skipAfterResolve; 46 | this.skipBeforeDereference |= extensionStatus.skipBeforeDereference; 47 | this.skipDereferencePrimary |= extensionStatus.skipDereferencePrimary; 48 | this.skipDereferenceSecondary |= extensionStatus.skipDereferenceSecondary; 49 | this.skipAfterDereference |= extensionStatus.skipAfterDereference; 50 | } 51 | 52 | public boolean skip(String extensionStage) { 53 | return switch (extensionStage) { 54 | case "beforeResolve" -> this.skipBeforeResolve; 55 | case "resolve" -> this.skipResolve; 56 | case "afterResolve" -> this.skipAfterResolve; 57 | case "beforeDereference" -> this.skipBeforeDereference; 58 | case "dereferencePrimary" -> this.skipDereferencePrimary; 59 | case "dereferenceSecondary" -> this.skipDereferenceSecondary; 60 | case "afterDereference" -> this.skipAfterDereference; 61 | default -> throw new IllegalStateException("Unexpected extension stage: " + extensionStage); 62 | }; 63 | } 64 | 65 | public boolean skipBeforeResolve() { 66 | return this.skipBeforeResolve; 67 | } 68 | 69 | public boolean skipResolve() { 70 | return this.skipResolve; 71 | } 72 | 73 | public boolean skipAfterResolve() { 74 | return this.skipAfterResolve; 75 | } 76 | 77 | public boolean skipBeforeDereference() { 78 | return skipBeforeDereference; 79 | } 80 | 81 | public boolean skipDereferencePrimary() { 82 | return skipDereferencePrimary; 83 | } 84 | 85 | public boolean skipDereferenceSecondary() { 86 | return skipDereferenceSecondary; 87 | } 88 | 89 | public boolean skipAfterDereference() { 90 | return skipAfterDereference; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /uni-resolver-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | uni-resolver-core 5 | jar 6 | uni-resolver-core 7 | 8 | 9 | decentralized-identity 10 | uni-resolver 11 | 0.48-SNAPSHOT 12 | 13 | 14 | 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-clean-plugin 19 | 20 | 21 | 22 | ${project.basedir}/openapi/java-client-generated/ 23 | 24 | **/* 25 | 26 | 27 | .gitignore 28 | .openapi-generator-ignore 29 | 30 | false 31 | 32 | 33 | 34 | 35 | 36 | org.openapitools 37 | openapi-generator-maven-plugin 38 | 39 | 40 | execution-universal-resolver 41 | 42 | generate 43 | 44 | 45 | java 46 | ${project.parent.basedir}/openapi/openapi.yaml 47 | openapi/java-client-generated/ 48 | uniresolver.openapi.api 49 | uniresolver.openapi.model 50 | uniresolver.openapi 51 | REF_AS_PARENT_IN_ALLOF=true 52 | 53 | uniresolver 54 | src/main/java/ 55 | decentralized-identity 56 | uniresolver-openapi 57 | ${project.version} 58 | uniresolver.openapi.config 59 | false 60 | false 61 | native 62 | true 63 | java8 64 | true 65 | false 66 | true 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | decentralized-identity 78 | did-common-java 79 | 80 | 81 | com.fasterxml.jackson.core 82 | jackson-core 83 | 84 | 85 | com.fasterxml.jackson.core 86 | jackson-databind 87 | 88 | 89 | com.fasterxml.jackson.datatype 90 | jackson-datatype-jsr310 91 | 92 | 93 | jakarta.annotation 94 | jakarta.annotation-api 95 | 96 | 97 | jakarta.validation 98 | jakarta.validation-api 99 | 100 | 101 | org.apache.httpcomponents 102 | httpcore 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /uni-resolver-local/src/main/java/uniresolver/local/extensions/impl/DIDDocumentExtension.java: -------------------------------------------------------------------------------- 1 | package uniresolver.local.extensions.impl; 2 | 3 | import foundation.identity.did.DIDURL; 4 | import foundation.identity.did.representations.Representations; 5 | import foundation.identity.did.representations.production.RepresentationProducer; 6 | import foundation.identity.did.representations.production.RepresentationProducerDIDCBOR; 7 | import foundation.identity.did.representations.production.RepresentationProducerDIDJSON; 8 | import foundation.identity.did.representations.production.RepresentationProducerDIDJSONLD; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import uniresolver.DereferencingException; 12 | import uniresolver.ResolutionException; 13 | import uniresolver.local.LocalUniDereferencer; 14 | import uniresolver.local.extensions.DereferencerExtension; 15 | import uniresolver.local.extensions.ExtensionStatus; 16 | import uniresolver.result.DereferenceResult; 17 | import uniresolver.result.ResolveResult; 18 | 19 | import java.io.IOException; 20 | import java.util.Map; 21 | 22 | public class DIDDocumentExtension implements DereferencerExtension.DereferencePrimaryDereferencerExtension { 23 | 24 | private static final Logger log = LoggerFactory.getLogger(DIDDocumentExtension.class); 25 | 26 | @Override 27 | public ExtensionStatus dereferencePrimary(DIDURL didUrlWithoutFragment, Map dereferenceOptions, ResolveResult resolveResult, DereferenceResult dereferenceResult, Map executionState, LocalUniDereferencer localUniDereferencer) throws ResolutionException, DereferencingException { 28 | 29 | // check inputs 30 | 31 | String accept = (String) dereferenceOptions.get("accept"); 32 | 33 | if (didUrlWithoutFragment.getPath() != null) { 34 | if (log.isDebugEnabled()) log.debug("Skipping (DID URL has path)."); 35 | return null; 36 | } 37 | 38 | if (resolveResult == null) { 39 | if (log.isDebugEnabled()) log.debug("Skipping (no resolve result)."); 40 | return null; 41 | } 42 | 43 | if (log.isInfoEnabled()) log.info("Executing dereferencePrimary() with extension " + this.getClass().getName()); 44 | 45 | // dereference 46 | 47 | if ("*/*".equals(accept)) accept = Representations.DEFAULT_MEDIA_TYPE; 48 | else if ("application/ld+json".equals(accept)) accept = RepresentationProducerDIDJSONLD.MEDIA_TYPE; 49 | else if ("application/json".equals(accept)) accept = RepresentationProducerDIDJSON.MEDIA_TYPE; 50 | else if ("application/cbor".equals(accept)) accept = RepresentationProducerDIDCBOR.MEDIA_TYPE; 51 | if (! Representations.isProducibleMediaType(accept)) { 52 | throw new DereferencingException(DereferencingException.ERROR_REPRESENTATION_NOT_SUPPORTED, "Content type not supported: " + accept); 53 | } 54 | 55 | if (log.isDebugEnabled()) log.debug("Dereferencing DID URL that has no path (assuming DID document): " + didUrlWithoutFragment); 56 | 57 | RepresentationProducer representationProducer; 58 | byte[] content; 59 | try { 60 | representationProducer = Representations.getProducer(accept); 61 | content = representationProducer.produce(resolveResult.getDidDocument()); 62 | } catch (IOException ex) { 63 | throw new DereferencingException(DereferencingException.ERROR_REPRESENTATION_NOT_SUPPORTED, "Cannot produce DID document: " + ex.getMessage(), ex); 64 | } 65 | 66 | if (log.isDebugEnabled()) log.debug("Dereferenced DID document with content type " + resolveResult.getContentType() + " using producer for " + representationProducer.getMediaType()); 67 | 68 | // set dereference result 69 | 70 | dereferenceResult.setContentType(representationProducer.getMediaType()); 71 | dereferenceResult.setContent(content); 72 | dereferenceResult.setContentMetadata(resolveResult.getDidDocumentMetadata()); 73 | 74 | // done 75 | 76 | return ExtensionStatus.SKIP_DEREFERENCE_PRIMARY; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /uni-resolver-web/src/main/java/uniresolver/web/WebUniResolver.java: -------------------------------------------------------------------------------- 1 | package uniresolver.web; 2 | 3 | import jakarta.servlet.ServletException; 4 | import jakarta.servlet.annotation.WebServlet; 5 | import jakarta.servlet.http.HttpServlet; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Qualifier; 10 | import org.springframework.lang.NonNull; 11 | import org.springframework.web.HttpRequestHandler; 12 | import uniresolver.DereferencingException; 13 | import uniresolver.ResolutionException; 14 | import uniresolver.UniDereferencer; 15 | import uniresolver.UniResolver; 16 | import uniresolver.result.DereferenceResult; 17 | import uniresolver.result.ResolveResult; 18 | 19 | import java.io.IOException; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Set; 23 | 24 | @WebServlet 25 | public abstract class WebUniResolver extends HttpServlet implements HttpRequestHandler, UniDereferencer, UniResolver { 26 | 27 | @Autowired 28 | @Qualifier("UniDereferencer") 29 | private UniDereferencer uniDereferencer; 30 | 31 | @Autowired 32 | @Qualifier("UniResolver") 33 | private UniResolver uniResolver; 34 | 35 | protected WebUniResolver() { 36 | 37 | super(); 38 | } 39 | 40 | @Override 41 | public void handleRequest(HttpServletRequest request, @NonNull HttpServletResponse response) throws ServletException, IOException { 42 | if ("GET".equals(request.getMethod())) this.doGet(request, response); 43 | if ("POST".equals(request.getMethod())) this.doPost(request, response); 44 | if ("PUT".equals(request.getMethod())) this.doPut(request, response); 45 | if ("DELETE".equals(request.getMethod())) this.doDelete(request, response); 46 | if ("OPTIONS".equals(request.getMethod())) this.doOptions(request, response); 47 | } 48 | 49 | @Override 50 | protected void doOptions(HttpServletRequest request, HttpServletResponse response) { 51 | response.setHeader("Access-Control-Allow-Origin", "*"); 52 | response.setHeader("Access-Control-Allow-Headers", "Accept, Content-Type"); 53 | response.setStatus(HttpServletResponse.SC_OK); 54 | } 55 | 56 | @Override 57 | public ResolveResult resolve(String didString, Map resolutionOptions) throws ResolutionException { 58 | return this.getUniResolver() == null ? null : this.getUniResolver().resolve(didString, resolutionOptions); 59 | } 60 | 61 | @Override 62 | public Map> properties() throws ResolutionException { 63 | return this.getUniResolver() == null ? null : this.getUniResolver().properties(); 64 | } 65 | 66 | @Override 67 | public Set methods() throws ResolutionException { 68 | return this.getUniResolver() == null ? null : this.getUniResolver().methods(); 69 | } 70 | 71 | @Override 72 | public Map> testIdentifiers() throws ResolutionException { 73 | return this.getUniResolver() == null ? null : this.getUniResolver().testIdentifiers(); 74 | } 75 | 76 | @Override 77 | public Map> traits() throws ResolutionException { 78 | return this.getUniResolver() == null ? null : this.getUniResolver().traits(); 79 | } 80 | 81 | @Override 82 | public DereferenceResult dereference(String didUrlString, Map dereferenceOptions) throws ResolutionException, DereferencingException { 83 | return this.getUniDereferencer() == null ? null : this.getUniDereferencer().dereference(didUrlString, dereferenceOptions); 84 | } 85 | 86 | /* 87 | * Getters and setters 88 | */ 89 | 90 | public UniDereferencer getUniDereferencer() { 91 | return this.uniDereferencer; 92 | } 93 | 94 | public void setUniDereferencer(UniDereferencer uniDereferencer) { 95 | this.uniDereferencer = uniDereferencer; 96 | } 97 | 98 | public UniResolver getUniResolver() { 99 | return this.uniResolver; 100 | } 101 | 102 | public void setUniResolver(UniResolver uniResolver) { 103 | this.uniResolver = uniResolver; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /uni-resolver-local/src/main/java/uniresolver/local/configuration/LocalUniResolverConfigurator.java: -------------------------------------------------------------------------------- 1 | package uniresolver.local.configuration; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import uniresolver.driver.Driver; 7 | import uniresolver.driver.http.HttpDriver; 8 | import uniresolver.local.LocalUniResolver; 9 | 10 | import java.io.FileReader; 11 | import java.io.IOException; 12 | import java.io.Reader; 13 | import java.net.URI; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | public class LocalUniResolverConfigurator { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(LocalUniResolverConfigurator.class); 21 | 22 | private final static ObjectMapper objectMapper = new ObjectMapper(); 23 | 24 | public static void configureLocalUniResolver(String filePath, LocalUniResolver localUniResolver) throws IOException { 25 | 26 | List drivers = new ArrayList<>(); 27 | 28 | try (Reader reader = new FileReader(filePath)) { 29 | 30 | Map jsonRoot = objectMapper.readValue(reader, Map.class); 31 | List> jsonDrivers = (List>) jsonRoot.get("drivers"); 32 | 33 | for (Map jsonDriver : jsonDrivers) { 34 | 35 | String pattern = jsonDriver.containsKey("pattern") ? (String) jsonDriver.get("pattern") : null; 36 | String url = jsonDriver.containsKey("url") ? (String) jsonDriver.get("url") : null; 37 | String propertiesEndpoint = jsonDriver.containsKey("propertiesEndpoint") ? (String) jsonDriver.get("propertiesEndpoint") : null; 38 | String supportsOptions = jsonDriver.containsKey("supportsOptions") ? (String) jsonDriver.get("supportsOptions") : null; 39 | String supportsDereference = jsonDriver.containsKey("supportsDereference") ? (String) jsonDriver.get("supportsDereference") : null; 40 | String acceptHeaderValue = jsonDriver.containsKey("acceptHeaderValue") ? (String) jsonDriver.get("acceptHeaderValue") : null; 41 | String acceptHeaderValueDereference = jsonDriver.containsKey("acceptHeaderValueDereference") ? (String) jsonDriver.get("acceptHeaderValueDereference") : null; 42 | List testIdentifiers = jsonDriver.containsKey("testIdentifiers") ? (List) jsonDriver.get("testIdentifiers") : null; 43 | Map traits = jsonDriver.containsKey("traits") ? (Map) jsonDriver.get("traits") : null; 44 | 45 | if (pattern == null) throw new IllegalArgumentException("Missing 'pattern' entry in driver configuration."); 46 | if (url == null) throw new IllegalArgumentException("Missing 'url' entry in driver configuration."); 47 | 48 | // construct HTTP driver 49 | 50 | HttpDriver driver = new HttpDriver(); 51 | driver.setPattern(pattern); 52 | 53 | if (url.contains("$1") || url.contains("$2")) { 54 | driver.setResolveUri(url); 55 | driver.setPropertiesUri((URI) null); 56 | } else { 57 | if (! url.endsWith("/")) url = url + "/"; 58 | driver.setResolveUri(url + "1.0/identifiers/"); 59 | if ("true".equals(propertiesEndpoint)) driver.setPropertiesUri(url + "1.0/properties"); 60 | } 61 | 62 | if (supportsOptions != null) driver.setSupportsOptions(Boolean.parseBoolean(supportsOptions)); 63 | if (supportsDereference != null) driver.setSupportsDereference(Boolean.parseBoolean(supportsDereference)); 64 | if (acceptHeaderValue != null) driver.setAcceptHeaderValue(acceptHeaderValue); 65 | if (acceptHeaderValueDereference != null) driver.setAcceptHeaderValueDereference(acceptHeaderValueDereference); 66 | if (testIdentifiers != null) driver.setTestIdentifiers(testIdentifiers); 67 | if (traits != null) driver.setTraits(traits); 68 | 69 | // done 70 | 71 | drivers.add(driver); 72 | if (log.isInfoEnabled()) log.info("Added driver for pattern '" + pattern + "' at " + driver.getResolveUri() + " (" + driver.getPropertiesUri() + ")"); 73 | } 74 | } 75 | 76 | // done 77 | 78 | localUniResolver.setDrivers(drivers); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /uni-resolver-local/src/main/java/uniresolver/local/extensions/util/ExecutionStateUtil.java: -------------------------------------------------------------------------------- 1 | package uniresolver.local.extensions.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import uniresolver.local.extensions.DereferencerExtension; 6 | import uniresolver.local.extensions.ResolverExtension; 7 | 8 | import java.util.ArrayList; 9 | import java.util.LinkedHashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class ExecutionStateUtil { 14 | 15 | private static final String RESOLVER_EXTENSION_STAGES = "resolverExtensionStages"; 16 | private static final String DEREFERENCER_EXTENSION_STAGES = "dereferencerExtensionStages"; 17 | 18 | private static final Logger log = LoggerFactory.getLogger(ExecutionStateUtil.class); 19 | 20 | public static void addResolverExtensionStage(Map executionState, Class extensionClass, ResolverExtension resolverExtension) { 21 | 22 | String extensionStage = extensionClass.getAnnotation(ResolverExtension.ExtensionStage.class).value(); 23 | String extensionName = resolverExtension.getClass().getSimpleName(); 24 | if (log.isDebugEnabled()) log.debug("Add resolver extension stage: " + extensionStage + " / " + extensionName); 25 | 26 | LinkedHashMap> executionStateStages = (LinkedHashMap>) executionState.computeIfAbsent(RESOLVER_EXTENSION_STAGES, f -> new LinkedHashMap>()); 27 | List executionStateStagesExtensions = executionStateStages.computeIfAbsent(extensionStage, f -> new ArrayList<>()); 28 | executionStateStagesExtensions.add(extensionName); 29 | } 30 | 31 | public static boolean checkResolverExtensionStage(Map executionState, Class extensionClass, ResolverExtension resolverExtension) { 32 | 33 | String extensionStage = extensionClass.getAnnotation(ResolverExtension.ExtensionStage.class).value(); 34 | String extensionName = resolverExtension.getClass().getSimpleName(); 35 | if (log.isDebugEnabled()) log.debug("Check resolver extension stage: " + extensionStage + " / " + extensionName); 36 | 37 | LinkedHashMap> executionStateStages = (LinkedHashMap>) executionState.get(RESOLVER_EXTENSION_STAGES); 38 | if (executionStateStages == null) return false; 39 | 40 | List executionStateStagesExtensions = executionStateStages.get(extensionStage); 41 | if (executionStateStagesExtensions == null) return false; 42 | 43 | return executionStateStagesExtensions.contains(extensionName); 44 | } 45 | 46 | public static void addDereferencerExtensionStage(Map executionState, Class extensionClass, DereferencerExtension dereferencerExtension) { 47 | 48 | String extensionStage = extensionClass.getAnnotation(DereferencerExtension.ExtensionStage.class).value(); 49 | String extensionName = dereferencerExtension.getClass().getSimpleName(); 50 | if (log.isDebugEnabled()) log.debug("Add dereferencer extension stage: " + extensionStage + " / " + extensionName); 51 | 52 | LinkedHashMap> executionStateStages = (LinkedHashMap>) executionState.computeIfAbsent(DEREFERENCER_EXTENSION_STAGES, f -> new LinkedHashMap>()); 53 | List executionStateStagesExtensions = executionStateStages.computeIfAbsent(extensionStage, f -> new ArrayList<>()); 54 | executionStateStagesExtensions.add(extensionName); 55 | } 56 | 57 | public static boolean checkDereferencerExtensionStage(Map executionState, Class extensionClass, DereferencerExtension dereferencerExtension) { 58 | 59 | String extensionStage = extensionClass.getAnnotation(DereferencerExtension.ExtensionStage.class).value(); 60 | String extensionName = dereferencerExtension.getClass().getSimpleName(); 61 | if (log.isDebugEnabled()) log.debug("Check dereferencer extension stage: " + extensionStage + " / " + extensionName); 62 | 63 | LinkedHashMap> executionStateStages = (LinkedHashMap>) executionState.get(DEREFERENCER_EXTENSION_STAGES); 64 | if (executionStateStages == null) return false; 65 | 66 | List executionStateStagesExtensions = executionStateStages.get(extensionStage); 67 | if (executionStateStagesExtensions == null) return false; 68 | 69 | return executionStateStagesExtensions.contains(extensionName); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | uniresolver_driver_did_btcr_bitcoinConnection=blockcypherapi 2 | uniresolver_driver_did_btcr_rpcUrlMainnet=http://user:pass@localhost:8332/ 3 | uniresolver_driver_did_btcr_rpcUrlTestnet=http://user:pass@localhost:18332/ 4 | uniresolver_driver_did_btcr_rpcCertMainnet= 5 | uniresolver_driver_did_btcr_rpcCertTestnet= 6 | 7 | uniresolver_driver_did_sov_libIndyPath= 8 | uniresolver_driver_did_sov_poolConfigs=_;./sovrin/_.txn;test;./sovrin/test.txn;builder;./sovrin/builder.txn;danube;./sovrin/danube.txn;indicio;./sovrin/indicio.txn;indicio:test;./sovrin/indicio-test.txn;indicio:demo;./sovrin/indicio-demo.txn;nxd;./sovrin/nxd.txn;findy:test;./sovrin/findy-test.txn 9 | uniresolver_driver_did_sov_poolVersions=_;2;test;2;builder;2;danube;2;2;2;indicio;2;indicio:test;2;indicio:demo;2;nxd;2;findy:test;2 10 | uniresolver_driver_did_sov_walletNames=_;w1;test;w2;builder;w3;danube;w4;w5;w6;indicio;w7;indicio:test;w8;indicio:demo;w9;nxd;w11;findy:test;w12 11 | uniresolver_driver_did_sov_submitterDidSeeds=_;_;test;_;builder;_;danube;_;_;_;indicio;_;indicio:test;_;indicio:demo;_;nxd;_;findy:test;_ 12 | 13 | uniresolver_driver_did_erc725_ethereumConnections=mainnet;hybrid;ropsten;hybrid;rinkeby;hybrid;kovan;hybrid 14 | uniresolver_driver_did_erc725_rpcUrls=mainnet;https://mainnet.infura.io/v3/fd9e225bc1234f49b48b295c611078eb;ropsten;https://ropsten.infura.io/v3/fd9e225bc1234f49b48b295c611078eb;rinkeby;https://rinkeby.infura.io/v3/fd9e225bc1234f49b48b295c611078eb;kovan;https://kovan.infura.io/v3/fd9e225bc1234f49b48b295c611078eb 15 | uniresolver_driver_did_erc725_etherscanApis=mainnet;http://api.etherscan.io/api;ropsten;http://api-ropsten.etherscan.io/api;rinkeby;http://api-rinkeby.etherscan.io/api;kovan;http://api-kovan.etherscan.io/api 16 | 17 | uniresolver_driver_did_work_apikey=sxVQUoDE015VhAs5ep4b57DFA5vT3zqvf1Dm1sGe 18 | uniresolver_driver_did_work_domain=https://credentials.id.workday.com 19 | 20 | uniresolver_driver_kilt_blockchain_node=wss://spiritnet.kilt.io 21 | 22 | uniresolver_driver_dns_dnsServers= 23 | 24 | uniresolver_driver_did_indy_libIndyPath= 25 | uniresolver_driver_did_indy_poolConfigs=sovrin;./sovrin/sovrin.txn;sovrin:test;./sovrin/sovrin-test.txn;sovrin:builder;./sovrin/sovrin-builder.txn;danube;./sovrin/danube.txn;indicio;./sovrin/indicio.txn;indicio:test;./sovrin/indicio-test.txn;indicio:demo;./sovrin/indicio-demo.txn;nxd;./sovrin/nxd.txn;findy:test;./sovrin/findy-test.txn 26 | uniresolver_driver_did_indy_poolVersions=sovrin;2;sovrin:test;2;sovrin:builder;2;danube;2;2;2;indicio;2;indicio:test;2;indicio:demo;2;nxd;2;findy:test;2 27 | uniresolver_driver_did_indy_walletNames=sovrin;w1;sovrin:test;w2;sovrin:builder;w3;danube;w4;w5;w6;indicio;w7;indicio:test;w8;indicio:demo;w9;nxd;w11;findy:test;w12 28 | uniresolver_driver_did_indy_submitterDidSeeds=sovrin;_;sovrin:test;_;sovrin:builder;_;danube;_;_;_;indicio;_;indicio:test;_;indicio:demo;_;nxd;_;findy:test;_ 29 | 30 | uniresolver_driver_did_echo_mainnet_rpc_url=http://127.0.0.1:8090/rpc 31 | uniresolver_driver_did_echo_testnet_rpc_url=http://testnet.echo-dev.io 32 | uniresolver_driver_did_echo_devnet_rpc_url=http://127.0.0.1:8090/rpc 33 | 34 | DID_METHOD_HOST_URL=0.0.0.0:8102 35 | DID_METHOD_TLS_SYSTEMCERTPOOL=true 36 | DID_METHOD_MODE=resolver 37 | 38 | uniresolver_driver_did_icon_node_url=https://sejong.net.solidwallet.io/api/v3 39 | uniresolver_driver_did_icon_score_addr=cxc7c8b0bb85eca64aecc8cc38628c4bc3c449f1fd 40 | uniresolver_driver_did_icon_network_id=83 41 | 42 | LEDGIS_LIT_ENDPOINT=https://lit.ledgis.io 43 | LEDGIS_LIT_CODE=lit 44 | 45 | ORB_DRIVER_HOST_URL=0.0.0.0:8121 46 | ORB_DRIVER_TLS_SYSTEMCERTPOOL=true 47 | ORB_DRIVER_VERIFY_RESOLUTION_RESULT_TYPE=all 48 | 49 | uniresolver_driver_did_dns_dnsServers= 50 | uniresolver_driver_did_dns_didKeyResolver=https://dev.uniresolver.io/1.0/ 51 | 52 | uniresolver_driver_did_com_network=https://lcd-mainnet.commercio.network 53 | 54 | uniresolver_driver_did_ev_node_url=https://polygon-mumbai.g.alchemy.com/v2/jLMUummm16stzMQjW1OB79IwuDjsJqS7 55 | uniresolver_driver_did_ev_address_im=0x4E4f55190185f2694D331E5c9Fd70a2B75Eb4Bd2 56 | uniresolver_driver_did_ev_base_blocks=2700000 57 | 58 | uniresolver_driver_did_itn_resolverUrl=https://resolver.itn.mobi 59 | 60 | uniresolver_driver_did_iota_network=testnet,mainnet 61 | 62 | 63 | uniresolver_driver_did_quarkid_node_url=https://lbquarkid2.extrimian.com/ 64 | uniresolver_driver_did_quarkid_node_pattern=did:quarkid 65 | uniresolver_driver_did_quarkid_node_behavior=1 66 | uniresolver_driver_did_quarkid_node_threadpool_size=240000 67 | 68 | uniresolver_driver_did_near_contract_id=neardti.testnet 69 | uniresolver_driver_did_near_rpc_url=https://rpc.testnet.near.org 70 | uniresolver_driver_did_near_network_id=testnet 71 | 72 | uniresolver_driver_did_empe_did_methods=[{"method":"empe","network":"testnet","url":"https://rpc-testnet.empe.io"},{"method":"empe","url":"https://rpc.empe.io"}] -------------------------------------------------------------------------------- /uni-resolver-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | uni-resolver-web 5 | jar 6 | uni-resolver-web 7 | 8 | 9 | decentralized-identity 10 | uni-resolver 11 | 0.48-SNAPSHOT 12 | 13 | 14 | 15 | 4.0.0 16 | 1.16.1 17 | 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-maven-plugin 24 | ${spring.boot.version} 25 | 26 | uniresolver.web.WebUniResolverApplication 27 | 28 | 29 | 30 | 31 | repackage 32 | 33 | 34 | exec 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-dependencies 47 | ${spring.boot.version} 48 | pom 49 | import 50 | 51 | 52 | io.micrometer 53 | micrometer-registry-prometheus 54 | ${io.micrometer.micrometer-registry-prometheus.version} 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-web 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-logging 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter-tomcat 71 | 72 | 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-starter-jetty 77 | 78 | 79 | ch.qos.logback 80 | logback-core 81 | 82 | 83 | ch.qos.logback 84 | logback-classic 85 | 86 | 87 | org.apache.logging.log4j 88 | log4j-to-slf4j 89 | 90 | 91 | 92 | 93 | decentralized-identity 94 | uni-resolver-local 95 | 96 | 97 | org.springframework.boot 98 | spring-boot-starter-log4j2 99 | 100 | 101 | org.apache.logging.log4j 102 | log4j-layout-template-json 103 | runtime 104 | 105 | 106 | io.micrometer 107 | micrometer-registry-prometheus 108 | runtime 109 | 110 | 111 | org.springframework.boot 112 | spring-boot-starter-actuator 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /ci/get-driver-status/app/get-driver-status.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import datetime 4 | import logging 5 | import re 6 | import json 7 | import getopt 8 | import asyncio 9 | import yaml 10 | from aiohttp import ClientSession 11 | 12 | 13 | WRITE_SUCCESS: bool = True 14 | 15 | logging.basicConfig( 16 | format="%(asctime)s %(levelname)s:%(name)s: %(message)s", 17 | level=logging.DEBUG, 18 | datefmt="%H:%M:%S", 19 | stream=sys.stderr, 20 | ) 21 | logger = logging.getLogger("areq") 22 | logging.getLogger("chardet.charsetprober").disabled = True 23 | 24 | 25 | # Create Test Data START 26 | def parse_json_to_dict(path): 27 | with open(path) as file: 28 | raw_config = yaml.safe_load(file) 29 | return raw_config 30 | 31 | 32 | def extract_did_method(did): 33 | return re.findall("(?<=:)(.*?)(?=:)", did)[0] 34 | 35 | 36 | def create_test_data(drivers_config, host): 37 | test_data = [] 38 | for driver in drivers_config: 39 | for testIdentifier in driver["testIdentifiers"]: 40 | if testIdentifier.startswith("did:"): 41 | driver_test_data = { 42 | "method": extract_did_method(testIdentifier), 43 | "url": host + testIdentifier 44 | } 45 | test_data.append(driver_test_data) 46 | 47 | return test_data 48 | 49 | 50 | # Create Test Data END 51 | 52 | # Run tests START 53 | async def fetch_html(url: str, session: ClientSession): 54 | resp = await session.request(method="GET", url=url) 55 | plain_html = await resp.text() 56 | logger.info("Got response [%s] for URL: %s", resp.status, url) 57 | logger.info("With body:\n %s", plain_html) 58 | 59 | if resp.status == 200: 60 | did_document = json.loads(plain_html) 61 | logger.info("With didDocument:\n %s", did_document) 62 | result = {"status": resp.status, "resolutionResponse": {}} 63 | result["resolutionResponse"]["application/did+ld+json"] = did_document 64 | return result 65 | else: 66 | return {"status": resp.status, "error": plain_html} 67 | 68 | 69 | async def write_one(results, data, session): 70 | url = data['url'] 71 | try: 72 | res = await fetch_html(url=url, session=session) 73 | if WRITE_SUCCESS | res['status'] != 200: 74 | results.update({url: res}) 75 | except asyncio.TimeoutError: 76 | results.update({ 77 | url: { 78 | "status": 504, 79 | "body": "Gateway Timeout error" 80 | } 81 | }) 82 | logger.info("Gateway Timeout error for %s", url) 83 | print("\n-----------------------------------------------------------------------------------------------\n") 84 | 85 | 86 | async def run_tests(test_data): 87 | async with ClientSession() as session: 88 | tasks = [] 89 | results = {} 90 | for data in test_data: 91 | tasks.append( 92 | write_one(results, data=data, session=session) 93 | ) 94 | await asyncio.gather(*tasks) 95 | return results 96 | 97 | 98 | # Run tests END 99 | 100 | def main(argv): 101 | help_text = './get-driver-status.py -host -config -out ' \ 102 | '--write200 ' 103 | host = 'https://dev.uniresolver.io' 104 | config = '/github/workspace/uni-resolver-web/src/main/resources/application.yml' 105 | out = './' 106 | try: 107 | opts, args = getopt.getopt(argv, "h:c:o:w", ["host=", "config=", "out=", "write200="]) 108 | except getopt.GetoptError: 109 | print(help_text) 110 | sys.exit(2) 111 | for opt, arg in opts: 112 | if opt == '--help': 113 | print(help_text) 114 | sys.exit() 115 | elif opt in ("-h", "--host"): 116 | host = arg 117 | elif opt in ("-c", "--config"): 118 | config = arg 119 | elif opt in ("-o", "--out"): 120 | print("ARG:" + arg) 121 | out = arg + '/' 122 | elif opt in ("-w", "--write200"): 123 | global WRITE_SUCCESS 124 | if arg.lower() == 'false': 125 | WRITE_SUCCESS = False 126 | 127 | uni_resolver_path = host + "/1.0/identifiers/" 128 | print('Resolving for: ' + uni_resolver_path) 129 | 130 | # build test data 131 | config_dict = parse_json_to_dict(config) 132 | test_data = create_test_data(config_dict["uniresolver"]["drivers"], uni_resolver_path) 133 | 134 | # run tests 135 | results = asyncio.run(run_tests(test_data=test_data)) 136 | 137 | results_timestamp = datetime.datetime.utcnow().replace(microsecond=0).isoformat() 138 | filename = "driver-status-" + results_timestamp + ".json" 139 | print('Out folder: ' + out) 140 | out_path = out + filename 141 | print('Writing to path: ' + out_path) 142 | with open(out_path, "a") as f: 143 | f.write(json.dumps(results, indent=4, sort_keys=True)) 144 | 145 | 146 | if __name__ == "__main__": 147 | main(sys.argv[1:]) 148 | print('Script finished') 149 | -------------------------------------------------------------------------------- /ci/deploy-k8s-aws/scripts/handle-btcr-secret.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################################################ 4 | # Handle driver-did-btcr Secret 5 | # 6 | # This script handles the special case for the driver-did-btcr service, which 7 | # requires specific RPC configuration via environment variables. 8 | # 9 | # The script: 10 | # - Checks if driver-did-btcr is deployed 11 | # - Creates/updates environment variables for RPC URL and certificate 12 | # - Patches the deployment to include these environment variables 13 | # 14 | # Prerequisites: 15 | # - kubectl must be configured and authenticated 16 | # - NAMESPACE environment variable must be set 17 | # - RPC_URL_TESTNET and RPC_CERT_TESTNET environment variables should be set 18 | # - processed_services.txt must exist (created by deploy-services.sh) 19 | # 20 | # Usage: 21 | # NAMESPACE=uni-resolver \ 22 | # RPC_URL_TESTNET="https://..." \ 23 | # RPC_CERT_TESTNET="cert-data" \ 24 | # ./handle-btcr-secret.sh 25 | ################################################################################ 26 | 27 | set -e 28 | 29 | # Validate prerequisites 30 | if [ -z "$NAMESPACE" ]; then 31 | echo "Error: NAMESPACE environment variable is not set" 32 | exit 1 33 | fi 34 | 35 | if [ ! -f "processed_services.txt" ]; then 36 | echo "Warning: processed_services.txt not found, skipping btcr secret handling" 37 | exit 0 38 | fi 39 | 40 | # Check if driver-did-btcr is in the processed services 41 | if ! grep -q "driver-did-btcr" processed_services.txt 2>/dev/null; then 42 | echo "driver-did-btcr not found in processed services, skipping secret handling" 43 | exit 0 44 | fi 45 | 46 | echo "====================================================================" 47 | echo "Handling special configuration for driver-did-btcr" 48 | echo "====================================================================" 49 | 50 | # Validate that required environment variables are set 51 | if [ -z "$RPC_URL_TESTNET" ]; then 52 | echo "Warning: RPC_URL_TESTNET environment variable is not set" 53 | echo "driver-did-btcr may not function correctly without RPC configuration" 54 | fi 55 | 56 | if [ -z "$RPC_CERT_TESTNET" ]; then 57 | echo "Warning: RPC_CERT_TESTNET environment variable is not set" 58 | echo "driver-did-btcr may not function correctly without RPC certificate" 59 | fi 60 | 61 | # Check if deployment exists 62 | if ! kubectl get deployment driver-did-btcr -n "$NAMESPACE" &>/dev/null; then 63 | echo "Error: driver-did-btcr deployment not found in namespace $NAMESPACE" 64 | exit 1 65 | fi 66 | 67 | echo "✓ driver-did-btcr deployment found" 68 | 69 | # Create a secret for the BTCR driver configuration 70 | # This secret will contain the RPC URL and certificate 71 | echo "Creating/updating secret for driver-did-btcr..." 72 | 73 | # Create secret YAML 74 | cat << EOF > driver-did-btcr-secret.yaml 75 | apiVersion: v1 76 | kind: Secret 77 | metadata: 78 | name: driver-did-btcr-secret 79 | namespace: ${NAMESPACE} 80 | labels: 81 | app: driver-did-btcr 82 | managed-by: github-action 83 | type: Opaque 84 | stringData: 85 | rpc-url: "${RPC_URL_TESTNET}" 86 | rpc-cert: "${RPC_CERT_TESTNET}" 87 | EOF 88 | 89 | # Apply the secret 90 | kubectl apply -f driver-did-btcr-secret.yaml 91 | echo "✓ Secret 'driver-did-btcr-secret' created/updated" 92 | 93 | # Patch the deployment to add environment variables from the secret 94 | echo "Patching driver-did-btcr deployment to use secret..." 95 | 96 | # Create a patch to add environment variables from the secret 97 | # This will add RPC_URL_TESTNET and RPC_CERT_TESTNET to the container 98 | cat << 'EOF' > btcr-env-patch.yaml 99 | spec: 100 | template: 101 | spec: 102 | containers: 103 | - name: driver-did-btcr 104 | env: 105 | - name: RPC_URL_TESTNET 106 | valueFrom: 107 | secretKeyRef: 108 | name: driver-did-btcr-secret 109 | key: rpc-url 110 | - name: RPC_CERT_TESTNET 111 | valueFrom: 112 | secretKeyRef: 113 | name: driver-did-btcr-secret 114 | key: rpc-cert 115 | EOF 116 | 117 | # Apply the patch using strategic merge 118 | if kubectl patch deployment driver-did-btcr -n "$NAMESPACE" --patch-file btcr-env-patch.yaml; then 119 | echo "✓ Deployment patched successfully" 120 | 121 | # Wait for the rollout to complete 122 | echo "Waiting for deployment to restart with new configuration..." 123 | if kubectl rollout status deployment/driver-did-btcr -n "$NAMESPACE" --timeout=300s; then 124 | echo "✓ driver-did-btcr is ready with updated configuration" 125 | else 126 | echo "⚠ Warning: Rollout status check timed out, but deployment may still succeed" 127 | fi 128 | else 129 | echo "⚠ Warning: Failed to patch deployment, secret was created but not mounted" 130 | echo "You may need to manually update the deployment to use the secret" 131 | fi 132 | 133 | echo "====================================================================" 134 | echo "driver-did-btcr secret handling completed" 135 | echo "====================================================================" 136 | -------------------------------------------------------------------------------- /ci/run-did-test-suite/app/index.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | const github = require('@actions/github'); 3 | const fs = require('fs'); 4 | const argv = require('minimist')(process.argv.slice(2)); 5 | 6 | const { runTests } = require("./testserver-utils"); 7 | const { generateLocalFile, generateDefaultFile } = require("./local-files-utils"); 8 | const { resetTestData, createExpectedOutcomes, extractDid, extractMethodName, getWorkingMethods, getWorkingUrls } = require("./utils"); 9 | 10 | const testDataSkeleton = { 11 | implementation: 'Universal Resolver', 12 | implementer: 'Decentralized Identity Foundation and Contributors', 13 | didMethod: '', 14 | expectedOutcomes: { 15 | defaultOutcomes: [], 16 | invalidDidErrorOutcome: [], 17 | notFoundErrorOutcome: [], 18 | representationNotSupportedErrorOutcome: [], 19 | deactivatedOutcome: [] 20 | }, 21 | executions: [] 22 | } 23 | 24 | const getOutputPath = () => { 25 | return argv["OUTPUT_PATH"] !== undefined ? argv["OUTPUT_PATH"] : './' 26 | } 27 | 28 | /* Set mode of test results 29 | * LOCAL => local files are created for manual upload 30 | * SERVER => tests are run against a hosted testserver 31 | * */ 32 | const getMode = () => { 33 | if (argv["MODE"] !== undefined) { 34 | return argv["MODE"].toUpperCase(); 35 | } else { 36 | return "SERVER"; 37 | } 38 | } 39 | 40 | const getTestset = () => { 41 | if (argv["DRIVER_STATUS_REPORT"] !== undefined) { 42 | return argv["DRIVER_STATUS_REPORT"]; 43 | } else { 44 | return core.getInput('DRIVER_STATUS_REPORT'); 45 | } 46 | } 47 | 48 | const getHost = () => { 49 | if (argv["HOST"] !== undefined) { 50 | return argv["HOST"]; 51 | } else { 52 | return core.getInput('host'); 53 | } 54 | } 55 | 56 | const getShouldGenerateDefaultFile = () => { 57 | if (argv["GENERATE_DEFAULT_FILE"] !== undefined) { 58 | return JSON.parse(argv["GENERATE_DEFAULT_FILE"].toLowerCase()) 59 | } else { 60 | return false; 61 | } 62 | } 63 | 64 | try { 65 | // Get the JSON webhook payload for the event that triggered the workflow 66 | const payload = JSON.stringify(github.context.payload, undefined, 2) 67 | console.log(`The event payload: ${payload}`); 68 | 69 | // Input variables 70 | const mode = getMode(); 71 | console.log(`Running in ${mode} mode`); 72 | 73 | // LOCAL mode env variables 74 | const outputPath = getOutputPath(); 75 | console.log(`Output path for testfiles ${outputPath}`); 76 | const shouldGenerateDefaultFile = getShouldGenerateDefaultFile(); 77 | 78 | // SERVER mode env variables 79 | const host = getHost(); 80 | console.log(`Testserver host ${host}`); 81 | 82 | // Common testset 83 | const testSet = getTestset(); 84 | console.log(`Running with testSet: ${testSet}`); 85 | 86 | const rawData = fs.readFileSync(testSet); 87 | const resolutionResults = JSON.parse(rawData); 88 | 89 | const workingMethods = getWorkingMethods(resolutionResults); 90 | console.log('Working methods', workingMethods); 91 | 92 | const workingUrls = getWorkingUrls(resolutionResults); 93 | console.log('Working Urls', workingUrls); 94 | 95 | const resolvers = []; 96 | 97 | workingMethods.forEach(workingMethodName => { 98 | const testData = {...testDataSkeleton}; 99 | resetTestData(testData); 100 | testData.didMethod = `did:${workingMethodName}`; 101 | 102 | let index = 0; 103 | workingUrls.forEach(url => { 104 | if (workingMethodName === extractMethodName(extractDid(url))) { 105 | createExpectedOutcomes(testData, resolutionResults[url], index) 106 | 107 | testData.executions.push({ 108 | function: 'resolveRepresentation', 109 | input: { 110 | did: resolutionResults[url].resolutionResponse["application/did+ld+json"].didDocument.id, 111 | resolutionOptions: { 112 | accept: "application/did+ld+json" 113 | } 114 | }, 115 | output: { 116 | didResolutionMetadata: resolutionResults[url].resolutionResponse["application/did+ld+json"].didResolutionMetadata, 117 | didDocumentStream: JSON.stringify(resolutionResults[url].resolutionResponse["application/did+ld+json"].didDocument), 118 | didDocumentMetadata: resolutionResults[url].resolutionResponse["application/did+ld+json"].didDocumentMetadata 119 | } 120 | }) 121 | index++; 122 | } 123 | }); 124 | 125 | if (mode === "LOCAL") generateLocalFile(testData, workingMethodName, outputPath); 126 | if (mode === "SERVER") resolvers.push(testData); 127 | }) 128 | 129 | if (mode === "LOCAL" && shouldGenerateDefaultFile === true) { 130 | generateDefaultFile(outputPath) 131 | } 132 | 133 | if (mode === "SERVER") { 134 | runTests(resolvers, host, outputPath); 135 | } 136 | } catch (error) { 137 | core.setFailed(error.message); 138 | } -------------------------------------------------------------------------------- /ci/deploy-k8s-aws/scripts/deploy-frontend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################################################ 4 | # Deploy Universal Resolver Frontend 5 | # 6 | # This script deploys the uni-resolver-frontend application which provides 7 | # the web UI for the Universal Resolver. This is separate from the services 8 | # defined in docker-compose.yml. 9 | # 10 | # Components deployed: 11 | # - ConfigMap: uni-resolver-frontend (contains backend URL) 12 | # - Deployment: uni-resolver-frontend (web UI container) 13 | # - Service: uni-resolver-frontend (NodePort service) 14 | # 15 | # Prerequisites: 16 | # - kubectl must be configured and authenticated 17 | # - NAMESPACE environment variable must be set 18 | # 19 | # Usage: 20 | # NAMESPACE=uni-resolver ./deploy-frontend.sh 21 | ################################################################################ 22 | 23 | set -e 24 | 25 | # Validate prerequisites 26 | if [ -z "$NAMESPACE" ]; then 27 | echo "Error: NAMESPACE environment variable is not set" 28 | exit 1 29 | fi 30 | 31 | # Configuration 32 | BACKEND_URL="https://dev.uniresolver.io/" 33 | 34 | echo "====================================================================" 35 | echo "Deploying Universal Resolver Frontend" 36 | echo "====================================================================" 37 | echo "" 38 | 39 | ################################################################################ 40 | # Step 1: Deploy ConfigMap 41 | ################################################################################ 42 | 43 | echo "Step 1: Creating/updating ConfigMap..." 44 | 45 | cat > configmap-uni-resolver-frontend.yaml << EOF 46 | apiVersion: v1 47 | kind: ConfigMap 48 | metadata: 49 | name: uni-resolver-frontend 50 | namespace: ${NAMESPACE} 51 | labels: 52 | app: uni-resolver-frontend 53 | managed-by: github-action 54 | data: 55 | backend_url: ${BACKEND_URL} 56 | EOF 57 | 58 | if kubectl apply -f configmap-uni-resolver-frontend.yaml; then 59 | echo "✓ ConfigMap created/updated" 60 | else 61 | echo "✗ Failed to create ConfigMap" 62 | exit 1 63 | fi 64 | 65 | echo "" 66 | 67 | ################################################################################ 68 | # Step 2: Deploy Frontend Application 69 | ################################################################################ 70 | 71 | echo "Step 2: Creating/updating Deployment and Service..." 72 | 73 | cat > deployment-uni-resolver-frontend.yaml << EOF 74 | apiVersion: apps/v1 75 | kind: Deployment 76 | metadata: 77 | name: uni-resolver-frontend 78 | namespace: ${NAMESPACE} 79 | labels: 80 | app: uni-resolver-frontend 81 | type: frontend 82 | managed-by: github-action 83 | spec: 84 | replicas: 1 85 | selector: 86 | matchLabels: 87 | app: uni-resolver-frontend 88 | template: 89 | metadata: 90 | labels: 91 | app: uni-resolver-frontend 92 | spec: 93 | containers: 94 | - name: uni-resolver-frontend 95 | image: universalresolver/universal-resolver-frontend 96 | imagePullPolicy: Always 97 | ports: 98 | - containerPort: 7081 99 | env: 100 | - name: BACKEND_URL 101 | valueFrom: 102 | configMapKeyRef: 103 | name: uni-resolver-frontend 104 | key: backend_url 105 | --- 106 | apiVersion: v1 107 | kind: Service 108 | metadata: 109 | name: uni-resolver-frontend 110 | namespace: ${NAMESPACE} 111 | labels: 112 | app: uni-resolver-frontend 113 | managed-by: github-action 114 | spec: 115 | type: NodePort 116 | selector: 117 | app: uni-resolver-frontend 118 | ports: 119 | - protocol: TCP 120 | port: 7081 121 | targetPort: 7081 122 | EOF 123 | 124 | if kubectl apply -f deployment-uni-resolver-frontend.yaml; then 125 | echo "✓ Deployment and Service created/updated" 126 | else 127 | echo "✗ Failed to create Deployment and Service" 128 | exit 1 129 | fi 130 | 131 | echo "" 132 | 133 | ################################################################################ 134 | # Step 3: Wait for Deployment to be Ready 135 | ################################################################################ 136 | 137 | echo "Step 3: Waiting for deployment to become ready..." 138 | 139 | if kubectl rollout status deployment/uni-resolver-frontend -n "$NAMESPACE" --timeout=300s; then 140 | echo "✓ Frontend deployment is ready" 141 | else 142 | echo "⚠ Frontend deployment rollout timed out" 143 | echo " Continuing with deployment process..." 144 | fi 145 | 146 | echo "" 147 | 148 | ################################################################################ 149 | # Summary 150 | ################################################################################ 151 | 152 | echo "====================================================================" 153 | echo "Frontend Deployment Summary" 154 | echo "====================================================================" 155 | echo "" 156 | 157 | # Show deployment status 158 | echo "Deployment:" 159 | kubectl get deployment uni-resolver-frontend -n "$NAMESPACE" -o wide 160 | 161 | echo "" 162 | echo "Service:" 163 | kubectl get service uni-resolver-frontend -n "$NAMESPACE" -o wide 164 | 165 | echo "" 166 | echo "Pods:" 167 | kubectl get pods -n "$NAMESPACE" -l app=uni-resolver-frontend -o wide 168 | 169 | echo "" 170 | echo "✓ Frontend deployment complete" 171 | -------------------------------------------------------------------------------- /.github/workflows/docker-multi-arch.yml: -------------------------------------------------------------------------------- 1 | name: Docker Multi-Arch 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | GLOBAL_FRAMEWORK: 7 | required: false 8 | type: string 9 | default: maven 10 | description: Framework used for the build (e.g., maven, node, nodejs, etc.) 11 | GLOBAL_IMAGE_NAME: 12 | required: true 13 | type: string 14 | GLOBAL_REPO_NAME: 15 | type: string 16 | default: docker.io 17 | IMAGE_TAG: 18 | required: false 19 | type: string 20 | IS_RELEASE: 21 | required: false 22 | type: boolean 23 | default: false 24 | PATH_TO_DOCKERFILE: 25 | required: true 26 | type: string 27 | RELEASE_TYPE: 28 | required: false 29 | type: string 30 | default: minor 31 | description: Type of release - Major, Minor, or Patch 32 | secrets: 33 | CI_SECRET_READER_PERIODIC_TOKEN: 34 | required: true 35 | VAULT_ADDR: 36 | required: true 37 | VAULTCA: 38 | required: true 39 | 40 | jobs: 41 | docker-build: 42 | name: Docker Build 43 | runs-on: ${{ matrix.runs-on }} 44 | 45 | strategy: 46 | matrix: 47 | arch: [amd64, arm64] 48 | include: 49 | - arch: amd64 50 | runs-on: ubuntu-24.04 51 | - arch: arm64 52 | runs-on: ubuntu-24.04-arm 53 | 54 | steps: 55 | - uses: actions/checkout@v4 56 | with: 57 | persist-credentials: false 58 | 59 | - name: Import Secrets 60 | uses: hashicorp/vault-action@v3 61 | with: 62 | url: ${{ secrets.VAULT_ADDR }} 63 | token: ${{ secrets.CI_SECRET_READER_PERIODIC_TOKEN }} 64 | caCertificate: ${{ secrets.VAULTCA }} 65 | secrets: | 66 | ci/data/gh-workflows/maven-danubetech-nexus username | MAVEN_USERNAME ; 67 | ci/data/gh-workflows/maven-danubetech-nexus password | MAVEN_PASSWORD 68 | 69 | - name: Setup Docker Buildx 70 | uses: docker/setup-buildx-action@v3 71 | with: 72 | install: true 73 | version: latest 74 | 75 | - name: Docker Build and Cache 76 | uses: docker/build-push-action@v6 77 | with: 78 | context: . 79 | file: ${{ inputs.PATH_TO_DOCKERFILE }} 80 | push: false 81 | build-args: | 82 | DANUBETECH_MAVEN_INTERNAL_USERNAME=${{ env.MAVEN_USERNAME }} 83 | DANUBETECH_MAVEN_INTERNAL_PASSWORD=${{ env.MAVEN_PASSWORD }} 84 | cache-from: type=gha,scope=docker-build-${{ matrix.arch }} 85 | cache-to: type=gha,scope=docker-build-${{ matrix.arch }},mode=max 86 | platforms: linux/${{ matrix.arch }} 87 | 88 | docker-publish: 89 | name: Docker Publish 90 | runs-on: ubuntu-24.04 91 | needs: docker-build 92 | 93 | steps: 94 | - name: Checkout 95 | uses: actions/checkout@v4 96 | with: 97 | persist-credentials: false 98 | fetch-depth: 0 99 | 100 | - name: Import Secrets 101 | uses: hashicorp/vault-action@v3 102 | with: 103 | url: ${{ secrets.VAULT_ADDR }} 104 | token: ${{ secrets.CI_SECRET_READER_PERIODIC_TOKEN }} 105 | caCertificate: ${{ secrets.VAULTCA }} 106 | secrets: | 107 | ci/data/gh-workflows/${{ inputs.GLOBAL_REPO_NAME }} username | DOCKER_USERNAME ; 108 | ci/data/gh-workflows/${{ inputs.GLOBAL_REPO_NAME }} password | DOCKER_PASSWORD ; 109 | ci/data/gh-workflows/maven-danubetech-nexus username | MAVEN_USERNAME ; 110 | ci/data/gh-workflows/maven-danubetech-nexus password | MAVEN_PASSWORD 111 | 112 | - name: Setup Docker Buildx 113 | uses: docker/setup-buildx-action@v3 114 | with: 115 | install: true 116 | version: latest 117 | 118 | - name: Get version 119 | if: inputs.IS_RELEASE 120 | id: get_version 121 | run: echo "version=$(git describe --abbrev=0)" >> $GITHUB_OUTPUT 122 | 123 | - name: Docker Metadata 124 | id: metadata 125 | uses: docker/metadata-action@v5 126 | with: 127 | images: ${{ inputs.GLOBAL_REPO_NAME }}/${{ inputs.GLOBAL_IMAGE_NAME }} 128 | tags: | 129 | type=raw,value=${{ inputs.IMAGE_TAG }},enable=${{ inputs.IMAGE_TAG != '' }} 130 | type=sha,prefix=${{ steps.get_version.outputs.version }}-,enable=${{ inputs.IS_RELEASE }} 131 | 132 | - name: Login to Docker Registry 133 | uses: docker/login-action@v3 134 | with: 135 | registry: ${{ inputs.GLOBAL_REPO_NAME }} 136 | username: ${{ env.DOCKER_USERNAME }} 137 | password: ${{ env.DOCKER_PASSWORD }} 138 | 139 | - name: Docker Push 140 | uses: docker/build-push-action@v6 141 | with: 142 | context: . 143 | file: ${{ inputs.PATH_TO_DOCKERFILE }} 144 | push: true 145 | tags: ${{ steps.metadata.outputs.tags }} 146 | labels: ${{ steps.metadata.outputs.labels }} 147 | build-args: | 148 | DANUBETECH_MAVEN_INTERNAL_USERNAME=${{ env.MAVEN_USERNAME }} 149 | DANUBETECH_MAVEN_INTERNAL_PASSWORD=${{ env.MAVEN_PASSWORD }} 150 | cache-from: | 151 | type=gha,scope=docker-build-arm64 152 | type=gha,scope=docker-build-amd64 153 | platforms: linux/amd64,linux/arm64 154 | -------------------------------------------------------------------------------- /uni-resolver-core/src/main/java/uniresolver/result/ResolveResult.java: -------------------------------------------------------------------------------- 1 | package uniresolver.result; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import foundation.identity.did.DIDDocument; 7 | import foundation.identity.did.representations.production.RepresentationProducer; 8 | import org.apache.http.entity.ContentType; 9 | 10 | import java.io.IOException; 11 | import java.io.Reader; 12 | import java.util.LinkedHashMap; 13 | import java.util.Map; 14 | 15 | @JsonPropertyOrder({ "didResolutionMetadata", "didDocument", "didDocumentMetadata" }) 16 | @JsonIgnoreProperties(ignoreUnknown=true) 17 | public class ResolveResult implements Result { 18 | 19 | public static final String MEDIA_TYPE = "application/did-resolution"; 20 | public static final ContentType CONTENT_TYPE = ContentType.parse(MEDIA_TYPE); 21 | 22 | public static final String LEGACY_MEDIA_TYPE = "application/ld+json;profile=\"https://w3id.org/did-resolution\""; 23 | public static final ContentType LEGACY_CONTENT_TYPE = ContentType.parse(LEGACY_MEDIA_TYPE); 24 | 25 | private static final ObjectMapper objectMapper = new ObjectMapper(); 26 | 27 | @JsonProperty("didResolutionMetadata") 28 | private Map didResolutionMetadata; 29 | 30 | @JsonProperty("didDocument") 31 | private DIDDocument didDocument; 32 | 33 | @JsonProperty("didDocumentMetadata") 34 | private Map didDocumentMetadata; 35 | 36 | private ResolveResult(Map didResolutionMetadata, DIDDocument didDocument, Map didDocumentMetadata) { 37 | this.didResolutionMetadata = didResolutionMetadata != null ? didResolutionMetadata : new LinkedHashMap<>(); 38 | this.didDocument = didDocument; 39 | this.didDocumentMetadata = didDocumentMetadata != null ? didDocumentMetadata : new LinkedHashMap<>(); 40 | } 41 | 42 | /* 43 | * Factory methods 44 | */ 45 | 46 | @JsonCreator 47 | public static ResolveResult build(@JsonProperty(value="didResolutionMetadata") Map didResolutionMetadata, @JsonProperty(value="didDocument") DIDDocument didDocument, @JsonProperty(value="didDocumentMetadata") Map didDocumentMetadata) { 48 | return new ResolveResult(didResolutionMetadata, didDocument, didDocumentMetadata); 49 | } 50 | 51 | public static ResolveResult build() { 52 | return new ResolveResult(new LinkedHashMap<>(), null, new LinkedHashMap<>()); 53 | } 54 | 55 | /* 56 | * Field methods 57 | */ 58 | 59 | @Override 60 | public Map getFunctionMetadata() { 61 | return this.getDidResolutionMetadata(); 62 | } 63 | 64 | @Override 65 | public byte[] getFunctionContent() throws IOException { 66 | if (this.getDidDocument() == null) return null; 67 | return RepresentationProducer.produce(this.getDidDocument(), this.getContentType()); 68 | } 69 | 70 | @Override 71 | public Map getFunctionContentMetadata() { 72 | return this.getDidDocumentMetadata(); 73 | } 74 | 75 | /* 76 | * Serialization 77 | */ 78 | 79 | public static ResolveResult fromJson(String json) throws IOException { 80 | return objectMapper.readValue(json, ResolveResult.class); 81 | } 82 | 83 | public static ResolveResult fromJson(Reader reader) throws IOException { 84 | return objectMapper.readValue(reader, ResolveResult.class); 85 | } 86 | 87 | @Override 88 | public Map toMap() { 89 | return objectMapper.convertValue(this, LinkedHashMap.class); 90 | } 91 | 92 | @Override 93 | public String toJson() { 94 | try { 95 | return objectMapper.writeValueAsString(this); 96 | } catch (JsonProcessingException ex) { 97 | throw new RuntimeException("Cannot write JSON: " + ex.getMessage(), ex); 98 | } 99 | } 100 | 101 | @Override 102 | public boolean isComplete() { 103 | return this.getContentType() != null && this.getDidDocument() != null; 104 | } 105 | 106 | /* 107 | * Getters and setters 108 | */ 109 | 110 | @JsonGetter("didResolutionMetadata") 111 | public final Map getDidResolutionMetadata() { 112 | return this.didResolutionMetadata; 113 | } 114 | 115 | @JsonSetter("didResolutionMetadata") 116 | public final void setDidResolutionMetadata(Map didResolutionMetadata) { 117 | this.didResolutionMetadata = didResolutionMetadata; 118 | } 119 | 120 | @JsonGetter("didDocument") 121 | public DIDDocument getDidDocument() { 122 | return this.didDocument; 123 | } 124 | 125 | @JsonSetter("didDocument") 126 | public void setDidDocument(DIDDocument didDocument) { 127 | this.didDocument = didDocument; 128 | } 129 | 130 | @JsonGetter("didDocumentMetadata") 131 | public final Map getDidDocumentMetadata() { 132 | return this.didDocumentMetadata; 133 | } 134 | 135 | @JsonSetter("didDocumentMetadata") 136 | public final void setDidDocumentMetadata(Map didDocumentMetadata) { 137 | this.didDocumentMetadata = didDocumentMetadata; 138 | } 139 | 140 | /* 141 | * Object methods 142 | */ 143 | 144 | @Override 145 | public String toString() { 146 | return this.toJson(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /ci/deploy-k8s-aws/scripts/deploy-ingress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################################################ 4 | # Deploy Ingress 5 | # 6 | # This script ensures the uni-resolver-ingress exists in the cluster. 7 | # If it doesn't exist, it creates it from scratch. 8 | # 9 | # The Ingress configures AWS ALB to route traffic to: 10 | # - uni-resolver-web service (API endpoints at /1.0/*) 11 | # - uni-resolver-frontend service (UI at /*) 12 | # 13 | # Prerequisites: 14 | # - kubectl must be configured and authenticated 15 | # - NAMESPACE environment variable must be set 16 | # - AWS Load Balancer Controller must be installed in the cluster 17 | # 18 | # Usage: 19 | # NAMESPACE=uni-resolver ./deploy-ingress.sh 20 | ################################################################################ 21 | 22 | set -e 23 | 24 | # Validate prerequisites 25 | if [ -z "$NAMESPACE" ]; then 26 | echo "Error: NAMESPACE environment variable is not set" 27 | exit 1 28 | fi 29 | 30 | # Constants 31 | INGRESS_NAME="uni-resolver-ingress" 32 | DEV_DOMAIN_NAME="dev.uniresolver.io" 33 | PROD_DOMAIN_NAME="resolver.identity.foundation" 34 | CERTIFICATE_ARNS="arn:aws:acm:us-east-2:332553390353:certificate/925fce37-d446-4af3-828e-f803b3746af0,arn:aws:acm:us-east-2:332553390353:certificate/59fa30ca-de05-4024-8f80-fea9ab9ab8bf" 35 | 36 | echo "====================================================================" 37 | echo "Checking Ingress: $INGRESS_NAME" 38 | echo "====================================================================" 39 | 40 | # First, ensure required services exist and are of type NodePort 41 | echo "Checking required services..." 42 | echo "" 43 | 44 | for service in uni-resolver-web uni-resolver-frontend; do 45 | if kubectl get service "$service" -n "$NAMESPACE" &>/dev/null; then 46 | service_type=$(kubectl get service "$service" -n "$NAMESPACE" -o jsonpath='{.spec.type}') 47 | echo " Service '$service' exists (type: $service_type)" 48 | 49 | if [ "$service_type" != "NodePort" ] && [ "$service_type" != "LoadBalancer" ]; then 50 | echo " ⚠ Service type is '$service_type', patching to 'NodePort'..." 51 | kubectl patch service "$service" -n "$NAMESPACE" -p '{"spec":{"type":"NodePort"}}' 52 | echo " ✓ Service patched to NodePort" 53 | fi 54 | else 55 | echo " ⚠ Warning: Service '$service' does not exist" 56 | echo " The Ingress will be created but may not work until the service is deployed" 57 | fi 58 | done 59 | 60 | echo "" 61 | 62 | # Check if ingress exists 63 | if kubectl get ingress "$INGRESS_NAME" -n "$NAMESPACE" &>/dev/null; then 64 | echo "✓ Ingress '$INGRESS_NAME' already exists" 65 | echo "" 66 | echo "Current Ingress configuration:" 67 | kubectl get ingress "$INGRESS_NAME" -n "$NAMESPACE" -o wide 68 | echo "" 69 | echo "No action needed" 70 | exit 0 71 | fi 72 | 73 | echo "⚠ Ingress '$INGRESS_NAME' does not exist" 74 | echo "Creating Ingress..." 75 | echo "" 76 | 77 | # Generate Ingress manifest 78 | cat > ingress.yaml << EOF 79 | apiVersion: networking.k8s.io/v1 80 | kind: Ingress 81 | metadata: 82 | name: ${INGRESS_NAME} 83 | namespace: ${NAMESPACE} 84 | annotations: 85 | alb.ingress.kubernetes.io/scheme: internet-facing 86 | alb.ingress.kubernetes.io/certificate-arn: "${CERTIFICATE_ARNS}" 87 | alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' 88 | alb.ingress.kubernetes.io/ssl-redirect: '443' 89 | labels: 90 | app: uni-resolver-web 91 | managed-by: github-action 92 | spec: 93 | ingressClassName: alb 94 | rules: 95 | - host: ${DEV_DOMAIN_NAME} 96 | http: 97 | paths: 98 | - path: /1.0/* 99 | pathType: ImplementationSpecific 100 | backend: 101 | service: 102 | name: uni-resolver-web 103 | port: 104 | number: 8080 105 | - path: /* 106 | pathType: ImplementationSpecific 107 | backend: 108 | service: 109 | name: uni-resolver-frontend 110 | port: 111 | number: 7081 112 | - host: ${PROD_DOMAIN_NAME} 113 | http: 114 | paths: 115 | - path: /1.0/* 116 | pathType: ImplementationSpecific 117 | backend: 118 | service: 119 | name: uni-resolver-web 120 | port: 121 | number: 8080 122 | - path: /* 123 | pathType: ImplementationSpecific 124 | backend: 125 | service: 126 | name: uni-resolver-frontend 127 | port: 128 | number: 7081 129 | EOF 130 | 131 | echo "Generated Ingress manifest:" 132 | cat ingress.yaml 133 | echo "" 134 | 135 | # Apply Ingress 136 | if kubectl apply -f ingress.yaml; then 137 | echo "✓ Ingress created successfully" 138 | else 139 | echo "✗ Failed to create Ingress" 140 | exit 1 141 | fi 142 | 143 | echo "" 144 | echo "Waiting for ALB to provision (this may take 2-3 minutes)..." 145 | echo "You can check the status with: kubectl get ingress $INGRESS_NAME -n $NAMESPACE -w" 146 | echo "" 147 | 148 | # Show the created ingress 149 | kubectl get ingress "$INGRESS_NAME" -n "$NAMESPACE" -o wide 150 | 151 | echo "" 152 | echo "✓ Ingress deployment complete" 153 | echo "" 154 | echo "Note: The AWS Load Balancer Controller will provision an ALB." 155 | echo "DNS records for ${DEV_DOMAIN_NAME} and ${PROD_DOMAIN_NAME}" 156 | echo "must point to the ALB address shown above." 157 | -------------------------------------------------------------------------------- /uni-resolver-core/src/main/java/uniresolver/result/DereferenceResult.java: -------------------------------------------------------------------------------- 1 | package uniresolver.result; 2 | 3 | import com.fasterxml.jackson.annotation.*; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.apache.commons.codec.DecoderException; 8 | import org.apache.commons.codec.binary.Hex; 9 | import org.apache.http.entity.ContentType; 10 | 11 | import java.io.IOException; 12 | import java.io.Reader; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.LinkedHashMap; 15 | import java.util.Map; 16 | 17 | @JsonPropertyOrder({ "dereferencingMetadata", "content", "contentMetadata" }) 18 | @JsonIgnoreProperties(ignoreUnknown=true) 19 | public class DereferenceResult implements Result { 20 | 21 | public static final String MEDIA_TYPE = "application/did-url-dereferencing"; 22 | public static final ContentType CONTENT_TYPE = ContentType.parse(MEDIA_TYPE); 23 | 24 | public static final String LEGACY_MEDIA_TYPE = "application/ld+json;profile=\"https://w3id.org/did-url-dereferencing\""; 25 | public static final ContentType LEGACY_CONTENT_TYPE = ContentType.parse(LEGACY_MEDIA_TYPE); 26 | 27 | private static final ObjectMapper objectMapper = new ObjectMapper(); 28 | 29 | @JsonProperty("dereferencingMetadata") 30 | private Map dereferencingMetadata; 31 | 32 | @JsonProperty("content") 33 | private byte[] content; 34 | 35 | @JsonProperty("contentMetadata") 36 | private Map contentMetadata; 37 | 38 | private DereferenceResult(Map dereferencingMetadata, byte[] content, Map contentMetadata) { 39 | this.dereferencingMetadata = dereferencingMetadata != null ? dereferencingMetadata : new LinkedHashMap<>(); 40 | this.content = content; 41 | this.contentMetadata = contentMetadata != null ? contentMetadata : new LinkedHashMap<>(); 42 | } 43 | 44 | /* 45 | * Factory methods 46 | */ 47 | 48 | @JsonCreator 49 | public static DereferenceResult build(@JsonProperty(value="dereferencingMetadata") Map dereferencingMetadata, @JsonProperty(value="content", required=true) byte[] content, @JsonProperty(value="contentMetadata") Map contentMetadata) { 50 | return new DereferenceResult(dereferencingMetadata, content, contentMetadata); 51 | } 52 | 53 | public static DereferenceResult build() { 54 | return new DereferenceResult(new LinkedHashMap<>(), null, new LinkedHashMap<>()); 55 | } 56 | 57 | /* 58 | * Field methods 59 | */ 60 | 61 | @Override 62 | public Map getFunctionMetadata() { 63 | return this.getDereferencingMetadata(); 64 | } 65 | 66 | @Override 67 | public byte[] getFunctionContent() { 68 | return this.getContent(); 69 | } 70 | 71 | @Override 72 | public Map getFunctionContentMetadata() { 73 | return this.getContentMetadata(); 74 | } 75 | 76 | /* 77 | * Serialization 78 | */ 79 | 80 | public static DereferenceResult fromJson(String json) throws IOException { 81 | return objectMapper.readValue(json, DereferenceResult.class); 82 | } 83 | 84 | public static DereferenceResult fromJson(Reader reader) throws IOException { 85 | return objectMapper.readValue(reader, DereferenceResult.class); 86 | } 87 | 88 | @Override 89 | public Map toMap() { 90 | return objectMapper.convertValue(this, LinkedHashMap.class); 91 | } 92 | 93 | @Override 94 | public String toJson() { 95 | try { 96 | return objectMapper.writeValueAsString(this); 97 | } catch (JsonProcessingException ex) { 98 | throw new RuntimeException("Cannot write JSON: " + ex.getMessage(), ex); 99 | } 100 | } 101 | 102 | @Override 103 | public boolean isComplete() { 104 | return this.getContentType() != null && this.getContent() != null; 105 | } 106 | 107 | /* 108 | * Getters and setters 109 | */ 110 | 111 | @JsonGetter("dereferencingMetadata") 112 | public final Map getDereferencingMetadata() { 113 | return this.dereferencingMetadata; 114 | } 115 | 116 | @JsonSetter("dereferencingMetadata") 117 | public final void setDereferencingMetadata(Map dereferencingMetadata) { 118 | this.dereferencingMetadata = dereferencingMetadata; 119 | } 120 | 121 | public final byte[] getContent() { 122 | return this.content; 123 | } 124 | 125 | @JsonGetter("content") 126 | public final String getContentAsString() { 127 | if (this.getContent() == null) { 128 | return null; 129 | } else { 130 | try { 131 | try (JsonParser jsonParser = objectMapper.getFactory().createParser(this.getContent())) { 132 | if (jsonParser.readValueAsTree() != null) return new String(this.getContent(), StandardCharsets.UTF_8); 133 | } 134 | } catch (IOException ignored) { 135 | } 136 | return Hex.encodeHexString(this.getContent()); 137 | } 138 | } 139 | 140 | public final void setContent(byte[] content) { 141 | this.content = content; 142 | } 143 | 144 | @JsonSetter("content") 145 | public final void setContentAsString(String contentString) { 146 | if (contentString == null) { 147 | this.setContent(null); 148 | } else { 149 | try { 150 | this.setContent(Hex.decodeHex(contentString)); 151 | } catch (DecoderException ex) { 152 | this.setContent(contentString.getBytes(StandardCharsets.UTF_8)); 153 | } 154 | } 155 | } 156 | 157 | @JsonGetter("contentMetadata") 158 | public final Map getContentMetadata() { 159 | return this.contentMetadata; 160 | } 161 | 162 | @JsonSetter("contentMetadata") 163 | public final void setContentMetadata(Map contentMetadata) { 164 | this.contentMetadata = contentMetadata; 165 | } 166 | 167 | /* 168 | * Object methods 169 | */ 170 | 171 | @Override 172 | public String toString() { 173 | return this.toJson(); 174 | } 175 | } 176 | --------------------------------------------------------------------------------