Refuse access tokens at UserInfo and the credential endpoint as RFC 6… #1848
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: ["**"] | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| jobs: | |
| basic-tests: | |
| name: Syntax and unit tests, PHP ${{ matrix.php-versions }}, ${{ matrix.operating-system }} | |
| runs-on: [ubuntu-latest] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| php-versions: ["8.3", "8.4", "8.5"] | |
| steps: | |
| - name: Setup PHP, with composer and extensions | |
| uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php | |
| with: | |
| php-version: ${{ matrix.php-versions }} | |
| extensions: mbstring, xml | |
| tools: composer:v2 | |
| coverage: pcov | |
| - name: Setup problem matchers for PHP | |
| run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" | |
| - name: Setup problem matchers for PHPUnit | |
| run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" | |
| - name: Set git to use LF | |
| run: | | |
| git config --global core.autocrlf false | |
| git config --global core.eol lf | |
| - uses: actions/checkout@v6 | |
| - name: Get composer cache directory | |
| id: composer-cache | |
| run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" | |
| - name: Cache composer dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: $COMPOSER_CACHE | |
| key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}" | |
| restore-keys: ${{ runner.os }}-composer- | |
| - name: Validate composer.json and composer.lock | |
| run: composer validate | |
| - name: Install Composer dependencies | |
| run: composer install --no-progress --prefer-dist --optimize-autoloader | |
| - name: Decide whether to run code coverage or not | |
| if: ${{ matrix.php-versions != '8.5' }} | |
| run: | | |
| echo "NO_COVERAGE=--no-coverage" >> $GITHUB_ENV | |
| - name: Run unit tests | |
| run: | | |
| echo $NO_COVERAGE | |
| ./vendor/bin/phpunit $NO_COVERAGE | |
| - name: Run integration tests | |
| run: | | |
| echo $NO_COVERAGE | |
| ./vendor/bin/phpunit $NO_COVERAGE --no-configuration -c phpunit.integration.xml | |
| - name: Merge coverage data | |
| if: ${{ matrix.php-versions == '8.5' }} | |
| run: | | |
| ./vendor/bin/phpcov merge --clover build/logs/clover.xml build/logs/partial_clover/ | |
| - name: Save coverage data | |
| if: ${{ matrix.php-versions == '8.5' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-data | |
| path: ${{ github.workspace }}/build | |
| security: | |
| name: Security checks | |
| runs-on: [ubuntu-latest] | |
| steps: | |
| - name: Setup PHP, with composer and extensions | |
| uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php | |
| with: | |
| php-version: "8.3" | |
| extensions: mbstring, xml | |
| tools: composer:v2 | |
| coverage: none | |
| - name: Setup problem matchers for PHP | |
| run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" | |
| - uses: actions/checkout@v6 | |
| - name: Get composer cache directory | |
| id: composer-cache | |
| run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" | |
| - name: Cache composer dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: $COMPOSER_CACHE | |
| key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}" | |
| restore-keys: ${{ runner.os }}-composer- | |
| - name: Install Composer dependencies | |
| run: composer install --no-progress --prefer-dist --optimize-autoloader | |
| - name: Security check for locked dependencies | |
| uses: symfonycorp/security-checker-action@v5 | |
| - name: Update Composer dependencies | |
| run: composer update --no-progress --prefer-dist --optimize-autoloader | |
| - name: Security check for updated dependencies | |
| uses: symfonycorp/security-checker-action@v5 | |
| sanity-check: | |
| name: Sanity checks | |
| runs-on: [ubuntu-latest] | |
| steps: | |
| - name: Setup PHP, with composer and extensions | |
| uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php | |
| with: | |
| php-version: "8.3" | |
| extensions: mbstring, xml | |
| tools: composer:v2 | |
| coverage: none | |
| - name: Setup problem matchers for PHP | |
| run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" | |
| - uses: actions/checkout@v6 | |
| - name: Get composer cache directory | |
| id: composer-cache | |
| run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" | |
| - name: Cache composer dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: $COMPOSER_CACHE | |
| key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}" | |
| restore-keys: ${{ runner.os }}-composer- | |
| - name: Install Composer dependencies | |
| run: composer install --no-progress --prefer-dist --optimize-autoloader | |
| quality: | |
| name: Quality control | |
| runs-on: [ubuntu-latest] | |
| needs: [basic-tests] | |
| steps: | |
| - name: Setup PHP, with composer and extensions | |
| uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php | |
| with: | |
| php-version: "8.5" | |
| tools: composer:v2 | |
| extensions: mbstring, xml | |
| - name: Setup problem matchers for PHP | |
| run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" | |
| - uses: actions/checkout@v6 | |
| - name: Get composer cache directory | |
| id: composer-cache | |
| run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" | |
| - name: Cache composer dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: $COMPOSER_CACHE | |
| key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}" | |
| restore-keys: ${{ runner.os }}-composer- | |
| - name: Install Composer dependencies | |
| run: composer install --no-progress --prefer-dist --optimize-autoloader | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: build-data | |
| path: ${{ github.workspace }}/build | |
| - name: Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: true | |
| verbose: true | |
| - name: PHP Code Sniffer | |
| if: always() | |
| run: php vendor/bin/phpcs | |
| - name: Rector | |
| if: always() | |
| run: php vendor/bin/rector --dry-run | |
| - name: Psalm | |
| if: always() | |
| run: php vendor/bin/psalm --show-info=true | |
| conformance-suite: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # SimpleSAMLphp ref to build the OP base image from. Any Composer version works: a released tag | |
| # (e.g. "v2.5.3.1") or a branch (e.g. "dev-simplesamlphp-2.5"). Keep this on a released tag which | |
| # satisfies the simplesamlphp/simplesamlphp requirement in composer.json, so that conformance | |
| # runs against a SimpleSAMLphp a deployment can actually install. Keep it usable as a Docker tag | |
| # as well: the base image is addressed as ssp-base:<this value> below, while the build script | |
| # replaces anything a tag may not hold, so a ref with a '/' in it would not be found here. | |
| ssp-composer-version: ["v2.5.3.1"] | |
| env: | |
| SUITE_BASE_URL: https://localhost.emobix.co.uk:8443 | |
| VERSION: release-v5.3.1 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| path: main | |
| - name: Setup Python Dependencies | |
| run: | | |
| pip install --upgrade pip | |
| pip install httpx | |
| - name: Conformance Suite Checkout | |
| if: ${{ steps.cache.outputs.cache-hit != 'true' }} | |
| run: git clone --depth 1 --single-branch --branch $VERSION https://gitlab.com/openid/conformance-suite.git | |
| - name: Conformance Suite Build | |
| working-directory: ./conformance-suite | |
| if: ${{ steps.cache.outputs.cache-hit != 'true' }} | |
| env: | |
| MAVEN_CACHE: ./m2 | |
| run: | | |
| sed -i -e 's/localhost/localhost.emobix.co.uk/g' src/main/resources/application.properties | |
| sed -i -e 's/-B clean/-B -DskipTests=true/g' builder-compose.yml | |
| # Workaround for intermittent NPE when document.readyState is null: | |
| # https://gitlab.com/openid/conformance-suite/-/work_items/1998 | |
| python3 -c "import pathlib; p = pathlib.Path('src/main/java/net/openid/conformance/frontchannel/BrowserControl.java'); p.write_text(p.read_text().replace('((JavascriptExecutor) webDriver).executeScript(\"return document.readyState\").equals(\"complete\")', '\"complete\".equals(((JavascriptExecutor) webDriver).executeScript(\"return document.readyState\"))'))" | |
| docker compose -f builder-compose.yml run builder | |
| - name: Run Conformance Suite | |
| working-directory: ./conformance-suite | |
| run: | | |
| docker compose -f docker-compose-dev.yml up -d | |
| while ! curl -skfail https://localhost.emobix.co.uk:8443/api/runner/available >/dev/null; do sleep 2; done | |
| - name: Build SimpleSAMLphp base image | |
| working-directory: ./main | |
| # SimpleSAMLphp is installed into the base image via Composer (cirrus's SSP_COMPOSER_VERSION | |
| # path), so any tag or branch can be used. The image is built locally each run and not pushed | |
| # to any registry. Same script a developer runs locally, so the two cannot drift apart. | |
| run: ./docker/build-ssp-base.sh ${{ matrix.ssp-composer-version }} | |
| - name: Generate development certificates | |
| working-directory: ./main | |
| # The TLS certificate nginx-proxy serves the OP with, and the OIDC module | |
| # signing key pairs, are generated per run instead of being kept in Git. | |
| run: ./docker/generate-dev-certs.sh | |
| - name: Start SSP docker | |
| working-directory: ./main | |
| # Must run after conformance suite since they share a docker network. | |
| run: | | |
| SSP_IMAGE=ssp-base:${{ matrix.ssp-composer-version }} OIDC_VERSION=@dev docker compose -f docker/docker-compose.yml --project-directory . up -d --build | |
| sleep 30 | |
| # while ! curl -skfail https://op.local.stack-dev.cirrusidentity.com/.well-known/openid-configuration >/dev/null; do sleep 2; done | |
| - name: Run Basic conformance tests | |
| run: | | |
| ./conformance-suite/scripts/run-test-plan.py --expected-failures-file ./main/conformance-tests/basic-warnings.json --expected-skips-file ./main/conformance-tests/basic-skips.json "oidcc-basic-certification-test-plan[server_metadata=discovery][client_registration=static_client]" ./main/conformance-tests/conformance-basic-ci.json | |
| - name: Run Implicit conformance tests | |
| run: | | |
| ./conformance-suite/scripts/run-test-plan.py --expected-failures-file ./main/conformance-tests/implicit-warnings.json --expected-skips-file ./main/conformance-tests/implicit-skips.json "oidcc-implicit-certification-test-plan[server_metadata=discovery][client_registration=static_client]" ./main/conformance-tests/conformance-implicit-ci.json | |
| - name: Run RP logout | |
| run: | | |
| ./conformance-suite/scripts/run-test-plan.py "oidcc-rp-initiated-logout-certification-test-plan[response_type=code][client_registration=static_client]" ./main/conformance-tests/conformance-rp-initiated-logout-ci.json | |
| - name: Run RP backchannel | |
| run: | | |
| ./conformance-suite/scripts/run-test-plan.py "oidcc-backchannel-rp-initiated-logout-certification-test-plan[response_type=code][client_registration=static_client]" ./main/conformance-tests/conformance-back-channel-logout-ci.json | |
| - name: Run form_post basic tests | |
| run: | | |
| ./conformance-suite/scripts/run-test-plan.py "oidcc-formpost-basic-certification-test-plan[server_metadata=discovery][client_registration=static_client]" ./main/conformance-tests/conformance-basic-ci.json | |
| - name: Run form_post implicit tests | |
| run: | | |
| ./conformance-suite/scripts/run-test-plan.py "oidcc-formpost-implicit-certification-test-plan[server_metadata=discovery][client_registration=static_client]" ./main/conformance-tests/conformance-implicit-ci.json | |
| - name: Run Dynamic registration conformance tests | |
| # The only remaining non-passing tests are two OP-wide gaps (signed UserInfo | |
| # and signing-key rotation), which are recorded as expected failures in | |
| # conformance-tests/dynamic-warnings.json, so this step is a blocking gate. | |
| # See docs/5-oidc-conformance.md for the inventory. | |
| run: | | |
| ./conformance-suite/scripts/run-test-plan.py --expected-failures-file ./main/conformance-tests/dynamic-warnings.json --expected-skips-file ./main/conformance-tests/dynamic-skips.json "oidcc-dynamic-certification-test-plan[response_type=code]" ./main/conformance-tests/conformance-dynamic-ci.json | |
| - name: Run VCI issuer conformance tests | |
| # The OpenID4VCI 1.0 issuer plan: the wallet-initiated authorization code flow for the dc+sd-jwt | |
| # credential. Every test which runs passes but for one check the suite can not perform, the Status | |
| # List Token's signature, recorded in conformance-tests/vci-issuer-warnings.json; the three tests the | |
| # suite skips for optional features the module does not offer are listed in | |
| # conformance-tests/vci-issuer-skips.json. So this step is a blocking gate. See | |
| # docs/5-oidc-conformance.md for the variants and what they leave out. | |
| run: | | |
| ./conformance-suite/scripts/run-test-plan.py --expected-failures-file ./main/conformance-tests/vci-issuer-warnings.json --expected-skips-file ./main/conformance-tests/vci-issuer-skips.json "oid4vci-1_0-issuer-test-plan[sender_constrain=dpop][client_auth_type=private_key_jwt][credential_format=sd_jwt_vc][vci_authorization_code_flow_variant=wallet_initiated][authorization_request_type=simple][openid=plain_oauth][fapi_request_method=unsigned][vci_grant_type=authorization_code][vci_credential_encryption=plain][fapi_profile=vci][fapi_response_mode=plain_response]" ./main/conformance-tests/conformance-vci-issuer.json | |
| - name: Run VCI issuer conformance tests (credential offer, RAR, signed request objects) | |
| # The same plan with the flow started by a Credential Offer, the credential asked for through | |
| # authorization_details, and the authorization request sent as a signed Request Object. Each test | |
| # waits for an offer, which run-test-plan.py never sends, so conformance-tests/vci-offer-driver.py | |
| # runs beside it, getting one from the OP's credential offer API and handing it to the test. Two more | |
| # expected failures apply to these variants, in conformance-tests/vci-issuer-offer-warnings.json (a | |
| # file of their own, since the runner fails on an entry no test matched): the suite sending the second | |
| # client on with the first client's offer, fixed on its master, and the unknown-configuration test | |
| # sending what OpenID4VCI 1.0 section 8.2 does not allow once authorization_details was used. A driver | |
| # which failed would leave every test waiting out the runner's own limit, hence the timeout. | |
| timeout-minutes: 15 | |
| run: | | |
| python3 ./main/conformance-tests/vci-offer-driver.py & | |
| DRIVER=$! | |
| STATUS=0 | |
| ./conformance-suite/scripts/run-test-plan.py --expected-failures-file "./main/conformance-tests/vci-issuer-warnings.json|./main/conformance-tests/vci-issuer-offer-warnings.json" --expected-skips-file ./main/conformance-tests/vci-issuer-skips.json "oid4vci-1_0-issuer-test-plan[sender_constrain=dpop][client_auth_type=private_key_jwt][credential_format=sd_jwt_vc][vci_authorization_code_flow_variant=issuer_initiated][authorization_request_type=rar][openid=plain_oauth][fapi_request_method=signed_non_repudiation][vci_grant_type=authorization_code][vci_credential_encryption=plain][fapi_profile=vci][fapi_response_mode=plain_response]" ./main/conformance-tests/conformance-vci-issuer.json || STATUS=$? | |
| kill $DRIVER || true | |
| exit $STATUS | |
| - name: Run VCI issuer conformance tests (pre-authorized code) | |
| # The pre-authorized code grant, its offer carrying a transaction code. The OP mails the code to the | |
| # user, and the driver reads it from the Mailpit container which catches the OP's mail | |
| # (docker/docker-compose.yml). | |
| timeout-minutes: 15 | |
| run: | | |
| python3 ./main/conformance-tests/vci-offer-driver.py --use-tx-code & | |
| DRIVER=$! | |
| STATUS=0 | |
| ./conformance-suite/scripts/run-test-plan.py --expected-failures-file ./main/conformance-tests/vci-issuer-warnings.json --expected-skips-file ./main/conformance-tests/vci-issuer-skips.json "oid4vci-1_0-issuer-test-plan[sender_constrain=dpop][client_auth_type=private_key_jwt][credential_format=sd_jwt_vc][vci_authorization_code_flow_variant=issuer_initiated][authorization_request_type=simple][openid=plain_oauth][fapi_request_method=unsigned][vci_grant_type=pre_authorization_code][vci_credential_encryption=plain][fapi_profile=vci][fapi_response_mode=plain_response]" ./main/conformance-tests/conformance-vci-issuer.json || STATUS=$? | |
| kill $DRIVER || true | |
| exit $STATUS | |
| - name: Dump OP logs | |
| if: failure() | |
| working-directory: ./main | |
| # What the suite reports is only its own side of the exchange, and some failures say nothing there: | |
| # a request the OP never sent reads as a test waiting for a callback until it times out, with no | |
| # failed check anywhere. The reason is in the OP's log, which otherwise leaves with the container. | |
| # Same compose invocation as "Start SSP docker", so this addresses the project that step created. | |
| run: | | |
| docker compose -f docker/docker-compose.yml --project-directory . logs --no-color --tail 2000 oidc-op || true | |
| - name: Stop SSP | |
| working-directory: ./main | |
| run: | | |
| docker compose -f docker/docker-compose.yml down | |
| - name: Stop Conformance Suite | |
| working-directory: ./conformance-suite | |
| # cleaning up errors out on removing the network, so add "|| true" to prevent that from breaking the build | |
| run: | | |
| docker compose -f docker-compose-dev.yml down || true | |
| sudo rm -rf mongo | |
| proxied-introspection-harness: | |
| name: Proxied introspection harness | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # The SimpleSAMLphp ref the base image is built from, kept as in conformance-suite above: a released tag | |
| # which satisfies composer.json, usable as a Docker tag. | |
| ssp-composer-version: ["v2.5.3.1"] | |
| steps: | |
| - name: Setup PHP, with composer and extensions | |
| uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php | |
| with: | |
| # The tests run from the checkout, with its own dependencies; the nodes run on the base image's PHP. | |
| php-version: "8.3" | |
| extensions: mbstring, xml | |
| tools: composer:v2 | |
| coverage: none | |
| - uses: actions/checkout@v6 | |
| - name: Get composer cache directory | |
| id: composer-cache | |
| run: echo COMPOSER_CACHE="$(composer config cache-files-dir)" >> "$GITHUB_ENV" | |
| - name: Cache composer dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: $COMPOSER_CACHE | |
| key: "${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}" | |
| restore-keys: ${{ runner.os }}-composer- | |
| - name: Install Composer dependencies | |
| run: composer install --no-progress --prefer-dist --optimize-autoloader | |
| - name: Build SimpleSAMLphp base image | |
| # Same script as in conformance-suite, and as a developer runs locally. | |
| run: ./docker/build-ssp-base.sh ${{ matrix.ssp-composer-version }} | |
| - name: Run the proxied introspection harness | |
| # Builds the harness image on the base image, starts nodes a, h and b and the rest, runs | |
| # tests/ProxiedIntrospectionHarness against them and removes everything again. On a failure it prints the | |
| # containers' logs first. | |
| env: | |
| SSP_IMAGE: ssp-base:${{ matrix.ssp-composer-version }} | |
| run: ./docker/proxied-introspection-harness/run.sh |