Skip to content

Feature/pre 3563 unified auth upc#306

Open
adumont-payplug wants to merge 5 commits into
developfrom
feature/PRE-3563_unified_auth_upc
Open

Feature/pre 3563 unified auth upc#306
adumont-payplug wants to merge 5 commits into
developfrom
feature/PRE-3563_unified_auth_upc

Conversation

@adumont-payplug

@adumont-payplug adumont-payplug commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Description

Migrates this plugin's OAuth2 connection flow — the interactive authorization-code+PKCE login (UnifiedAuthenticationController) and the background client-credentials token refresh (PayPlugApiClientFactory) — off the legacy payplug/payplug-php Authentication class and onto payplug/unified-plugin-core's new Auth/OAuth2Client + Auth/TokenManager.

Two new adapters bridge UPC's contracts to this plugin's existing infrastructure:

  • SyliusOAuthHttpClient implements UPC's IOAuthHttpClient via Symfony's HttpClientInterface. It also catches TransportExceptionInterface (network-level failures — DNS, timeout, connection reset) rather than letting them escape uncaught, so a transient network blip is reported the same way a non-2xx response from PayPlug is: as an ApiException from OAuth2Client, caught and translated by PayPlugApiClientFactory into GatewayConfigurationException — matching the resilience the legacy Authentication::generateJWT() had (it caught all exceptions internally).
  • SyliusTokenCache implements UPC's ITokenCache via the PSR-6 CacheItemPoolInterface, sanitizing cache keys since PSR-6 rejects {}()/\@: and TokenManager's own key format contains a colon.

createClientIdAndSecret() (portal client provisioning, still called from oauthCallback() after the token exchange) is untouched — out of scope, still going through the legacy SDK.

This PR also adds a SonarCloud code coverage check to CI, mirroring the setup already in place on payplug/unified-plugin-core. Previously this plugin's sonarcloud job ran static analysis only, with no coverage data for SonarCloud to report or gate on. A new coverage job installs the Sylius test application (PHP 8.2, PCOV) and runs PHPUnit with --coverage-clover, uploading the report as a workflow artifact; sonarcloud now consumes it via sonarcloud-coverage.yml and enforces the Quality Gate. A Dockerfile + make coverage target were added so coverage can also be generated locally (PHP 8.2 + PCOV in a container), since host PHP setups commonly lack a coverage driver for the required PHP version.

Motivation:

The legacy flow had two real defects this closes, not just an internal refactor:

  1. Authentication::initiateOAuth() did a raw header('Location: ...') call, forcing setupRedirection() to scrape headers_list() for a Location: line to recover the URL. OAuth2Client::buildAuthorizationUrl() returns the URL directly instead.
  2. The legacy SDK generated an OAuth state internally but never returned it to the caller — oauthCallback() had no CSRF protection at all on the OAuth callback. This PR stores state (and code_verifier) in session from buildAuthorizationUrl()'s return value and rejects the callback outright on a mismatch, before any token exchange happens.

It also adds the first automated test coverage this connection flow has ever had — UnifiedAuthenticationControllerTest, PayPlugApiClientFactoryTest, plus tests for both new adapters, including a case covering the transport-failure handling in SyliusOAuthHttpClient. (Full happy-path coverage of oauthCallback() still stops at the createClientIdAndSecret() boundary — those are static calls on the legacy SDK and unmockable; see the docblock on UnifiedAuthenticationControllerTest.)

Separately, the coverage-check addition to CI is meant to catch coverage regressions on new code going forward — worth noting current overall src/ coverage is only ~22% (693/3,111 statements), so enforce-quality-gate: true on the sonarcloud job may start blocking PRs sooner than expected until that baseline improves.

Related issue(s): PRE-3563


Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue) — missing OAuth CSRF protection; unhandled transport-level exceptions in the background token refresh path
  • ✨ New feature (non-breaking change that adds functionality) — SonarCloud coverage check in CI
    [ ] 💥 Breaking change (fix or feature that causes existing functionality to change and that could impact other libs)
  • 🔧 Refactor (no functional changes, code improvement only)
  • 📦 Dependency update
  • 🔒 Security fix
  • 📝 Documentation update — README badge, CLAUDE.md, PR template checklist

Checklist

Code Quality

  • Code is linted and formatted
  • No unnecessary commented-out code or debug logs
  • No hardcoded values (use env variables or config)

Testing

  • Unit tests added / updated
  • New/changed code is covered by tests — SonarCloud Quality Gate (coverage on new code) passes on the sonarcloud CI job

Security & Ops

  • No sensitive data or secrets introduced
  • Logging and error handling are appropriate

Files changed

OAuth2/PKCE migration:

  • src/Action/Admin/Auth/UnifiedAuthenticationController.php — built on OAuth2Client, state/PKCE stored in session
  • src/ApiClient/PayPlugApiClientFactory.php — background token retrieval via TokenManager::getValidToken()
  • src/Auth/SyliusOAuthHttpClient.php — new IOAuthHttpClient adapter; catches TransportExceptionInterface so network failures translate into ApiException/GatewayConfigurationException instead of an uncaught exception reaching payment processing (capture/refund/IPN/status)
  • src/Auth/SyliusTokenCache.php — new ITokenCache adapter

Coverage check in CI:

  • .github/workflows/ci.yml — new coverage job; sonarcloud job switched to sonarcloud-coverage.yml@main with enforce-quality-gate: true
  • composer.json — new test-coverage script
  • phpunit.xml.dist<coverage><include> restricted to src/
  • Dockerfile + Makefilemake coverage runs PHPUnit w/ PCOV in a container, for local parity with CI
  • README.md — added SonarCloud Coverage badge
  • .github/PULL_REQUEST_TEMPLATE.md — added coverage/Quality Gate checklist item

Before merging, please double-check:

  • Given the ~22% baseline coverage noted above, confirm the SonarCloud Quality Gate thresholds for this project are configured for "coverage on new code," not overall coverage — otherwise enforce-quality-gate: true will fail this and most future PRs outright.
  • Consider splitting this into two PRs (auth migration vs. CI coverage check) — they're unrelated in scope and a security-sensitive change is easier to review in isolation.

Copilot AI review requested due to automatic review settings July 20, 2026 13:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates PayPlug authentication/token acquisition toward payplug/unified-plugin-core by introducing Sylius adapters (HTTP + cache), switching PayPlugApiClientFactory to TokenManager, and updating the admin OAuth flow to PKCE/state-based OAuth2.

Changes:

  • Add Sylius implementations of IOAuthHttpClient and ITokenCache, with PHPUnit coverage.
  • Switch PayPlugApiClientFactory from legacy Authentication::generateJWT() to TokenManager::getValidToken(), and wire new services.
  • Update UnifiedAuthenticationController to use PayplugUnifiedCore\Auth\OAuth2Client for PKCE/state and code exchange.

1. What's Good

  • SyliusOAuthHttpClient correctly uses getContent(false) to avoid throwing on non-2xx and leaves status handling to the caller.
  • SyliusTokenCache explicitly sanitizes PSR-6 reserved characters and has a dedicated data-provider test for it.
  • config/services/client.xml sets autowire="true", which makes the new Unified Core services wiring straightforward.

2. Summary table

Dimension Rating
Security ⚠️ Low (query param normalization gap for company_id)
Correctness ⚠️ Medium (missing guards + one test expectation mismatch)
Performance ✅ Fine
Maintainability ⚠️ Medium (composer dependency/repository setup likely not shippable)
Operational Implications ❌ High (path repository + dev branch dependency likely breaks installs)

3. Closing one-liner

Address the installability problem in composer.json and the few guard/test issues before merge.


4. Individual findings (one section per issue)

Heading: Operational Implications ❌ High

Subtitle (bold): Non-portable Composer dependency setup (composer.json:120)

+    "require": {
+        ...
+        "payplug/unified-plugin-core": "dev-feature/PRE-3563_oauth2_client",
+        ...
+    },
+    "repositories": [
+        {
+            "type": "path",
+            "url": "../unified-plugin-core",
+            "canonical": true,
+            "options": { "symlink": true }
+        }
+    ]

Including a path repository pointing to a sibling directory will fail for typical users installing the plugin via Packagist/VCS (there is no ../unified-plugin-core on their filesystem). Pinning to a hard dev-* branch also makes releases brittle.

Fix: Publish/consume Unified Core via VCS/tagged release, and remove the path repo from the distributed composer.json (exact constraint/repo depends on your release workflow).


Heading: Correctness ⚠️ Medium

Subtitle (bold): Missing client_id / client_secret validation before TokenManager call (src/ApiClient/PayPlugApiClientFactory.php:81)

+        try {
+            return $this->tokenManager->getValidToken($clientConfig['client_id'] ?? '', $clientConfig['client_secret'] ?? '');
+        } catch (ApiException $e) {
+            throw new GatewayConfigurationException('Unable to connect to PayPlug API. Please check your credentials in the PayPlug plugin configuration.', 0, $e);
+        }

If config keys are missing, this currently calls the token flow with empty strings and then wraps the error as an API connectivity/credentials issue. That makes a configuration problem harder to diagnose and can cause unnecessary HTTP calls.

Fix: Extract and validate both values first; throw GatewayConfigurationException for missing config before calling TokenManager.


Heading: Security ⚠️ Low

Subtitle (bold): company_id query param should be normalized to string (src/Action/Admin/Auth/UnifiedAuthenticationController.php:62)

-            $companyId = $request->query->get('company_id');
+            $companyId = $request->query->getString('company_id');

Request::query->get() can return arrays (e.g. ?company_id[]=x), and that value is later used in Authentication::createClientIdAndSecret(). Normalizing at the boundary avoids type confusion.

Fix: Use getString('company_id') (same approach as client_id).


Heading: Correctness ⚠️ Medium

Subtitle (bold): Missing guard for PKCE code verifier before token exchange (src/Action/Admin/Auth/UnifiedAuthenticationController.php:101)

+            /** @var string $codeVerifier */
+            $codeVerifier = $request->getSession()->get('payplug_oauth_code_verifier');
...
+            $token = $this->buildOAuth2Client($callback)->exchangeAuthorizationCode($clientId, $code, $codeVerifier);

If the session expired or setup wasn’t completed, payplug_oauth_code_verifier may be null/non-string. Failing fast with a clear 400 avoids a vendor exception/TypeError and prevents an unnecessary token call.

Fix: Validate is_string($codeVerifier) && $codeVerifier !== '' before calling exchangeAuthorizationCode().


Heading: Correctness ⚠️ Medium

Subtitle (bold): Test expects an exception message that no longer matches (tests/PHPUnit/ApiClient/PayPlugApiClientFactoryTest.php:93)

-        $this->expectExceptionMessage('No client config found for payplug');
+        // factory now appends guidance: "Please renew your credentials..."

expectExceptionMessage() checks the full message, so the current assertion will fail.

Fix: Update the expected message to match the thrown message (or switch to expectExceptionMessageMatches() for a prefix/regex).


Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/PHPUnit/Auth/SyliusTokenCacheTest.php Adds coverage for cache hit/miss, TTL, deletion, and reserved-character sanitization.
tests/PHPUnit/Auth/SyliusOAuthHttpClientTest.php Adds coverage for form-encoded POST behavior and non-2xx handling.
tests/PHPUnit/ApiClient/PayPlugApiClientFactoryTest.php Adds tests for TokenManager-based token retrieval and error wrapping (one assertion needs update).
tests/PHPUnit/Action/Admin/Auth/UnifiedAuthenticationControllerTest.php Adds unit coverage for PKCE setup and callback failure guards.
src/Auth/SyliusTokenCache.php Implements Unified Core ITokenCache with PSR-6 key sanitization.
src/Auth/SyliusOAuthHttpClient.php Implements Unified Core IOAuthHttpClient via Symfony HttpClient.
src/ApiClient/PayPlugApiClientFactory.php Switches JWT generation to Unified Core TokenManager.
src/Action/Admin/Auth/UnifiedAuthenticationController.php Replaces legacy OAuth initiation with Unified Core OAuth2Client PKCE/state flow.
config/services/client.xml Registers Unified Core services and aliases to Sylius adapters.
config/services.yaml Adds env-overridable OAuth base URL/audience parameters and binds for DI.
composer.json Adds Unified Core dependency + local path repository (currently non-portable).
.gitignore Ignores docs/ directory.

Comment thread tests/PHPUnit/ApiClient/PayPlugApiClientFactoryTest.php
Comment thread src/ApiClient/PayPlugApiClientFactory.php
Comment thread src/Action/Admin/Auth/UnifiedAuthenticationController.php
Comment thread src/Action/Admin/Auth/UnifiedAuthenticationController.php
Comment thread composer.json Outdated
Copilot AI review requested due to automatic review settings July 21, 2026 14:43
@adumont-payplug
adumont-payplug force-pushed the feature/PRE-3563_unified_auth_upc branch from a8be416 to 25beb1e Compare July 21, 2026 14:43

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 6 comments.

Comment thread src/Auth/SyliusTokenCache.php Outdated
Comment thread src/Action/Admin/Auth/UnifiedAuthenticationController.php
Comment thread src/Action/Admin/Auth/UnifiedAuthenticationController.php Outdated
Comment thread src/Action/Admin/Auth/UnifiedAuthenticationController.php Outdated
Comment thread src/ApiClient/PayPlugApiClientFactory.php Outdated
Comment thread src/ApiClient/PayPlugApiClientFactory.php Outdated
@adumont-payplug
adumont-payplug changed the base branch from master to develop July 22, 2026 09:47
Copilot AI review requested due to automatic review settings July 22, 2026 10:06
@wiz-14d684d7a6

wiz-14d684d7a6 Bot commented Jul 22, 2026

Copy link
Copy Markdown

Wiz Scan Summary

Scanner Findings
Vulnerability Finding Vulnerabilities -
Data Finding Sensitive Data -
Secret Finding Secrets -
IaC Misconfiguration IaC Misconfigurations 10 Medium 3 Low
SAST Finding SAST Findings -
Software Management Finding Software Management Findings -
Total 10 Medium 3 Low

View scan details in Wiz

To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings July 22, 2026 13:30
@adumont-payplug adumont-payplug self-assigned this Jul 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants