From cec906ca21af658acf8b495b8e20b19af725f9c8 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 24 Mar 2026 12:47:49 +0100 Subject: [PATCH 1/5] Improve CI workflows and local dev tooling Bump actions/checkout to v6, add docker-compose ci profile to keep matrix services from starting during local development, fix coverage command to run via docker-compose, and remove stale .markdownlint.json. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/changelog.yaml | 2 +- .github/workflows/composer.yaml | 6 +++--- .github/workflows/github_build_release.yml | 2 +- .github/workflows/markdown.yaml | 2 +- .github/workflows/php.yaml | 6 +++--- .github/workflows/yaml.yaml | 2 +- .markdownlint.json | 4 ---- Taskfile.yml | 5 +++-- docker-compose.yml | 16 ++++++++++++++++ 9 files changed, 29 insertions(+), 16 deletions(-) delete mode 100644 .markdownlint.json diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml index 15297e3..e0cd8a0 100644 --- a/.github/workflows/changelog.yaml +++ b/.github/workflows/changelog.yaml @@ -18,7 +18,7 @@ jobs: fail-fast: false steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 2 diff --git a/.github/workflows/composer.yaml b/.github/workflows/composer.yaml index 539693f..f807f83 100644 --- a/.github/workflows/composer.yaml +++ b/.github/workflows/composer.yaml @@ -41,7 +41,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create docker network run: | @@ -55,7 +55,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create docker network run: | @@ -70,7 +70,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create docker network run: | diff --git a/.github/workflows/github_build_release.yml b/.github/workflows/github_build_release.yml index 63aca71..9246e0a 100644 --- a/.github/workflows/github_build_release.yml +++ b/.github/workflows/github_build_release.yml @@ -16,7 +16,7 @@ jobs: APP_ENV: prod steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Create a release in GitHub run: gh release create ${{ github.ref_name }} --verify-tag --generate-notes diff --git a/.github/workflows/markdown.yaml b/.github/workflows/markdown.yaml index cdd8b4b..4aa1189 100644 --- a/.github/workflows/markdown.yaml +++ b/.github/workflows/markdown.yaml @@ -34,7 +34,7 @@ jobs: fail-fast: false steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Create docker network run: | diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml index b4ed005..0220370 100644 --- a/.github/workflows/php.yaml +++ b/.github/workflows/php.yaml @@ -15,7 +15,7 @@ jobs: name: PHP - Check Coding Standards runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create docker network run: | @@ -29,7 +29,7 @@ jobs: name: PHPStan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create docker network run: | @@ -65,7 +65,7 @@ jobs: php: "8.5" prefer: prefer-stable steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create docker network run: | diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml index ba8ca52..2e910c0 100644 --- a/.github/workflows/yaml.yaml +++ b/.github/workflows/yaml.yaml @@ -31,7 +31,7 @@ jobs: yaml-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create docker network run: | diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index cf2915d..0000000 --- a/.markdownlint.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "default": true, - "line-length": { "code_blocks": false } -} \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml index 905c7f8..ca0349a 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -142,7 +142,7 @@ tasks: test:coverage: desc: Run tests with coverage cmds: - - "{{.PHP}} vendor/bin/phpunit --coverage-clover=coverage/unit.xml" + - "{{.DOCKER_COMPOSE}} exec -e XDEBUG_MODE=coverage phpfpm vendor/bin/phpunit --coverage-text --coverage-clover=coverage/unit.xml" test:run: desc: "Run tests for a PHP version and dependency set (e.g. task test:run PHP=8.4 DEPS=lowest)" @@ -162,6 +162,7 @@ tasks: cp /app/vendor/.composer.lock /app/composer.lock composer install -q else + rm -f /app/composer.lock composer update -q --{{.PREFER}} cp /app/composer.lock /app/vendor/.composer.lock fi' @@ -170,7 +171,7 @@ tasks: test:matrix:reset: desc: Remove cached vendor volumes to force a fresh dependency resolve cmds: - - "{{.DOCKER_COMPOSE}} down --volumes" + - "{{.DOCKER_COMPOSE}} --profile ci down --volumes" test:matrix: desc: Run tests across all PHP versions (mirrors CI matrix) diff --git a/docker-compose.yml b/docker-compose.yml index 64fc402..f71d58c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,16 +30,22 @@ services: extends: service: phpfpm image: itkdev/php8.4-fpm:latest + profiles: + - ci phpfpm85: extends: service: phpfpm image: itkdev/php8.5-fpm:latest + profiles: + - ci # Test matrix services (PHP version × dependency set) phpfpm83-stable: extends: service: phpfpm + profiles: + - ci volumes: - .:/app - phpfpm83-stable-vendor:/app/vendor @@ -48,6 +54,8 @@ services: phpfpm83-lowest: extends: service: phpfpm + profiles: + - ci volumes: - .:/app - phpfpm83-lowest-vendor:/app/vendor @@ -56,6 +64,8 @@ services: phpfpm84-stable: extends: service: phpfpm84 + profiles: + - ci volumes: - .:/app - phpfpm84-stable-vendor:/app/vendor @@ -64,6 +74,8 @@ services: phpfpm84-lowest: extends: service: phpfpm84 + profiles: + - ci volumes: - .:/app - phpfpm84-lowest-vendor:/app/vendor @@ -72,6 +84,8 @@ services: phpfpm85-stable: extends: service: phpfpm85 + profiles: + - ci volumes: - .:/app - phpfpm85-stable-vendor:/app/vendor @@ -80,6 +94,8 @@ services: phpfpm85-lowest: extends: service: phpfpm85 + profiles: + - ci volumes: - .:/app - phpfpm85-lowest-vendor:/app/vendor From 88d7d87d82446d038a95daa5dda6e59ad3d22a7a Mon Sep 17 00:00:00 2001 From: turegjorup Date: Tue, 24 Mar 2026 12:50:07 +0100 Subject: [PATCH 2/5] Update Changelog with unreleased changes Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25e0d65..31b8435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Bumped `actions/checkout` from v5 to v6 in all CI workflows +- Added `ci` profile to docker-compose matrix services to avoid starting them during local development +- Fixed `test:coverage` task to run via docker-compose with `XDEBUG_MODE=coverage` +- Fixed `test:run` to remove stale `composer.lock` before `composer update` +- Fixed `test:matrix:reset` to use `--profile ci` flag +- Removed unused `.markdownlint.json` + ## [4.1.0] - 2026-03-20 - Achieved 100% test coverage (methods and lines) From 6c5c4435d84a0c3aa560d8148c683517978fcc63 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Thu, 7 May 2026 09:59:35 +0200 Subject: [PATCH 3/5] Chain previous consistently and document HTTP transport options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Catch blocks in OpenIdConfigurationProvider (validateIdToken, getJwks, fetchJsonResource, getConfiguration) now pass the original exception as `previous` so consumers can walk back to the underlying Guzzle/firebase/PSR cause via getPrevious(). Previously only getIdToken did this. Tightened @throws phpdoc on validateIdToken, getIdToken, and getBaseAuthorizationUrl to enumerate actual transitive exceptions instead of declaring only the ItkOpenIdConnectException parent. Removed the inaccurate ClientExceptionInterface declaration on getIdToken — the catch-all wraps Guzzle exceptions as CodeException with the original chained. README adds a section documenting the existing timeout/proxy/verify support already provided by league/oauth2-client via constructor $options, plus a short note explaining why the stack uses Guzzle rather than Symfony HttpClient. BC-safe: all changes are additive (third constructor arg on the wrapped exceptions is optional; phpdoc tightening is a stricter declaration of existing behavior). Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 11 ++++++++ README.md | 27 ++++++++++++++++++++ src/Security/OpenIdConfigurationProvider.php | 26 +++++++++++++------ 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31b8435..884f2d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Chained `previous` consistently in `OpenIdConfigurationProvider` catch + blocks (`validateIdToken`, `getJwtVerificationKeys`, `fetchJsonResource`, + `getConfiguration`) so consumers can walk back to the underlying + Guzzle/firebase/PSR exception via `getPrevious()` +- Tightened `@throws` phpdoc on public methods (`validateIdToken`, + `getIdToken`, `getBaseAuthorizationUrl`) to enumerate the actual + transitive exceptions instead of declaring only the parent type. Removed + the inaccurate `ClientExceptionInterface` declaration on `getIdToken` + (the catch-all wraps it as `CodeException` with the original chained) +- Documented HTTP timeout/proxy/verify configuration via constructor `$options` + (capability already provided by league/oauth2-client; no code change) - Bumped `actions/checkout` from v5 to v6 in all CI workflows - Added `ci` profile to docker-compose matrix services to avoid starting them during local development - Fixed `test:coverage` task to run via docker-compose with `XDEBUG_MODE=coverage` diff --git a/README.md b/README.md index 2c72d77..f2718cc 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,33 @@ $provider = new OpenIdConfigurationProvider([ ]); ``` +##### HTTP timeout, proxy, and TLS verification + +This library extends `league/oauth2-client`, which uses Guzzle for HTTP. To +bound how long a request to the IdP can take (recommended for production), +pass `timeout` (seconds) in the constructor `$options`: + +```php +$provider = new OpenIdConfigurationProvider([ + // ... required options ... + 'timeout' => 5, + 'proxy' => 'http://proxy.example.com:8080', + 'verify' => true, // only consulted by Guzzle when proxy is set +]); +``` + +`league/oauth2-client` whitelists exactly these three keys (`timeout`, `proxy`, +`verify`) and forwards them to the underlying Guzzle client. Other Guzzle +options (e.g. `connect_timeout`) are silently dropped. + +> **Why Guzzle and not Symfony HttpClient?** +> `league/oauth2-client` hard-types its HTTP client as +> `GuzzleHttp\ClientInterface`. Symfony HttpClient implements PSR-18 / HTTPlug, +> not Guzzle's interface, and there is no maintained adapter going Symfony → +> Guzzle. To plug in a non-Guzzle client you would need to write such an +> adapter yourself and pass it via `$collaborators['httpClient']` to the +> constructor. + ##### Leeway To account for clock skew times between the signing and verifying servers, diff --git a/src/Security/OpenIdConfigurationProvider.php b/src/Security/OpenIdConfigurationProvider.php index bd4a10f..5b34a06 100644 --- a/src/Security/OpenIdConfigurationProvider.php +++ b/src/Security/OpenIdConfigurationProvider.php @@ -103,6 +103,11 @@ public function getGuarded(): array return ['cacheItemPool', 'cacheDuration', 'openIDConnectMetadataUrl', 'leeway', 'allowHttp']; } + /** + * @throws CacheException + * @throws HttpException + * @throws JsonException + */ public function getBaseAuthorizationUrl(): string { return $this->getConfiguration('authorization_endpoint'); @@ -197,7 +202,13 @@ public function getEndSessionUrl(?string $postLogoutRedirectUri = null, ?string * @return object * The JWT's payload as a PHP object * - * @throws ItkOpenIdConnectException + * @throws CacheException + * @throws ClaimsException + * @throws DecodeException + * @throws HttpException + * @throws JsonException + * @throws KeyException + * @throws ValidationException */ public function validateIdToken(string $idToken, string $nonce): object { @@ -222,7 +233,7 @@ public function validateIdToken(string $idToken, string $nonce): object return $claims; } catch (\UnexpectedValueException $e) { - throw new ValidationException('ID token validation failed: '.$e->getMessage()); + throw new ValidationException('ID token validation failed: '.$e->getMessage(), 0, $e); } } @@ -235,8 +246,7 @@ public function validateIdToken(string $idToken, string $nonce): object * @return string * The ID token * - * @throws CodeException - * @throws ClientExceptionInterface + * @throws CodeException Wraps any \Exception thrown by token-endpoint HTTP, JSON parsing, or `getConfiguration()` (with `previous` chained) */ public function getIdToken(string $code): string { @@ -371,7 +381,7 @@ private function getJwtVerificationKeys(): array $this->cacheItemPool->save($item); } } catch (InvalidArgumentException $e) { - throw new CacheException($e->getMessage()); + throw new CacheException($e->getMessage(), 0, $e); } return $keys; @@ -418,9 +428,9 @@ private function fetchJsonResource(string $resourceUrl): array return $resource; } catch (ClientExceptionInterface $e) { - throw new HttpException($e->getMessage()); + throw new HttpException($e->getMessage(), 0, $e); } catch (\JsonException $e) { - throw new JsonException($e->getMessage()); + throw new JsonException($e->getMessage(), 0, $e); } } @@ -461,7 +471,7 @@ private function getConfiguration(string $key): string return $value; } catch (InvalidArgumentException $e) { - throw new CacheException($e->getMessage()); + throw new CacheException($e->getMessage(), 0, $e); } } From a32a774eb4a1eb5ce092b1aae2d23b5c3c862638 Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 11 May 2026 13:57:33 +0200 Subject: [PATCH 4/5] Fix broken CI badge in README Point at the actual `php.yaml` workflow on `develop` instead of the non-existent `pr.yaml` and the stale "Test & Code Style Review" workflow name. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2718cc..852c83d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Github](https://img.shields.io/badge/source-itk--dev/openid--connect-blue?style=flat-square)](https://github.com/itk-dev/openid-connect) [![Release](https://img.shields.io/packagist/v/itk-dev/openid-connect.svg?style=flat-square&label=release)](https://packagist.org/packages/itk-dev/openid-connect) [![PHP Version](https://img.shields.io/packagist/php-v/itk-dev/openid-connect.svg?style=flat-square&colorB=%238892BF)](https://www.php.net/downloads) -[![Build Status](https://img.shields.io/github/actions/workflow/status/itk-dev/openid-connect/pr.yaml?label=CI&logo=github&style=flat-square)](https://github.com/itk-dev/openid-connect/actions?query=workflow%3A%22Test+%26+Code+Style+Review%22) +[![Build Status](https://img.shields.io/github/actions/workflow/status/itk-dev/openid-connect/php.yaml?branch=develop&label=CI&logo=github&style=flat-square)](https://github.com/itk-dev/openid-connect/actions/workflows/php.yaml?query=branch%3Adevelop) [![Codecov Code Coverage](https://img.shields.io/codecov/c/gh/itk-dev/openid-connect?label=codecov&logo=codecov&style=flat-square)](https://codecov.io/gh/itk-dev/openid-connect) [![Read License](https://img.shields.io/packagist/l/itk-dev/openid-connect.svg?style=flat-square&colorB=darkcyan)](https://github.com/itk-dev/openid-connect/blob/master/LICENSE.md) [![Package downloads on Packagist](https://img.shields.io/packagist/dt/itk-dev/openid-connect.svg?style=flat-square&colorB=darkmagenta)](https://packagist.org/packages/itk-dev/openid-connect/stats) From 2df37c30148bc366641427d812c617debbe24d1f Mon Sep 17 00:00:00 2001 From: turegjorup Date: Mon, 11 May 2026 14:00:29 +0200 Subject: [PATCH 5/5] release: 4.1.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roll up the [Unreleased] section into 4.1.2 dated 2026-05-11. PATCH-level: all changes are strictly additive (consistent `$previous` chaining across the four `OpenIdConfigurationProvider` catch sites), documentation accuracy fixes on `@throws` PHPDoc for three public methods (the `ClientExceptionInterface` declaration on `getIdToken` was always wrong — the catch-all already wraps it as `CodeException`), README docs for the HTTP transport options that league/oauth2-client already forwards to Guzzle, and CI / Taskfile housekeeping. No new public API, no removed/renamed/re-typed public API, no required behaviour change for existing consumers. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 376285d..557a083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.1.2] - 2026-05-11 + - Chained `previous` consistently in `OpenIdConfigurationProvider` catch blocks (`validateIdToken`, `getJwtVerificationKeys`, `fetchJsonResource`, `getConfiguration`) so consumers can walk back to the underlying @@ -161,7 +163,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - This CHANGELOG file to hopefully serve as an evolving example of a standardized open source project CHANGELOG. -[Unreleased]: https://github.com/itk-dev/openid-connect/compare/4.1.1...HEAD +[Unreleased]: https://github.com/itk-dev/openid-connect/compare/4.1.2...HEAD +[4.1.2]: https://github.com/itk-dev/openid-connect/compare/4.1.1...4.1.2 [4.1.1]: https://github.com/itk-dev/openid-connect/compare/4.1.0...4.1.1 [4.1.0]: https://github.com/itk-dev/openid-connect/compare/4.0.3...4.1.0 [4.0.3]: https://github.com/itk-dev/openid-connect/compare/4.0.2...4.0.3