diff --git a/composer.json b/composer.json index 3e76e5ce..81f723a8 100644 --- a/composer.json +++ b/composer.json @@ -62,6 +62,7 @@ "malukenho/docheader": "^1.1", "mockery/mockery": "^1.6.12", "overtrue/phplint": ">=9.5.6", + "phpcpd-next/phpcpd": "^1.1", "phpmd/phpmd": "^2.15", "phpstan/phpstan": "^2.0", "phpstan/phpstan-doctrine": "^2.0", diff --git a/composer.lock b/composer.lock index 647c9d92..4bb1373d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ec2b15084bf3b654744c7f3b3eecbe7f", + "content-hash": "612a0a745758944144c8146d4a69d054", "packages": [ { "name": "beberlei/assert", @@ -9899,6 +9899,80 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpcpd-next/phpcpd", + "version": "v1.2", + "source": { + "type": "git", + "url": "https://github.com/phpcpd-next/phpcpd.git", + "reference": "b2ed86a01cea42385ccbae46611a0623e7aeb104" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpcpd-next/phpcpd/zipball/b2ed86a01cea42385ccbae46611a0623e7aeb104", + "reference": "b2ed86a01cea42385ccbae46611a0623e7aeb104", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.5" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.64", + "phpstan/phpstan": "^2.2", + "phpunit/phpunit": "^11.0 || ^12.0 || ^13.0", + "rector/rector": "^2.0" + }, + "suggest": { + "phpunit/phpunit": "To use the PHPUnit integration (AssertNoDuplication trait / DuplicationConstraint) for duplication-as-a-test" + }, + "bin": [ + "phpcpd" + ], + "type": "library", + "autoload": { + "psr-4": { + "LucianoPereira\\PhpcpdNext\\": [ + "src/", + "src/CLI/", + "src/Exceptions/" + ], + "LucianoPereira\\PhpcpdNext\\PHPUnit\\": "integration/phpunit/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Original author" + }, + { + "name": "Luciano Federico Pereira", + "role": "Fork maintainer" + } + ], + "description": "phpcpd-next — Copy/Paste (clone) detector for PHP 8.5+ with Type-1/2/3 detection. The maintained successor to the archived sebastianbergmann/phpcpd.", + "homepage": "https://github.com/phpcpd-next/phpcpd", + "keywords": [ + "ci", + "clone-detection", + "code-quality", + "copy-paste-detector", + "duplication", + "phpcpd", + "static-analysis" + ], + "support": { + "issues": "https://github.com/phpcpd-next/phpcpd/issues", + "source": "https://github.com/phpcpd-next/phpcpd" + }, + "time": "2026-07-19T17:58:20+00:00" + }, { "name": "phpmd/phpmd", "version": "2.15.0", diff --git a/src/Surfnet/StepupMiddleware/ManagementBundle/Tests/Controller/ConfigurationControllerTest.php b/src/Surfnet/StepupMiddleware/ManagementBundle/Tests/Controller/ConfigurationControllerTest.php index 958e5bb3..6cb48de5 100644 --- a/src/Surfnet/StepupMiddleware/ManagementBundle/Tests/Controller/ConfigurationControllerTest.php +++ b/src/Surfnet/StepupMiddleware/ManagementBundle/Tests/Controller/ConfigurationControllerTest.php @@ -218,6 +218,22 @@ public function validPushWithOnlyGatewayIsAccepted(): void ]); } + #[Test] + #[Group('management')] + public function validPushWithServiceNameIsAccepted(): void + { + $this->pushConfiguration([ + 'gateway' => [ + 'identity_providers' => [], + 'service_providers' => [ + array_merge(self::minimalServiceProvider(), [ + 'service_name' => ['en_GB' => 'Test Service', 'nl_NL' => 'Test Dienst'], + ]), + ], + ], + ]); + } + #[Test] #[Group('management')] public function pushWithSraaAndEmailTemplatesIsSilentlyAccepted(): void diff --git a/src/Surfnet/StepupMiddleware/ManagementBundle/Tests/Validator/Fixtures/invalid_configuration/invalid_sp_service_name.php b/src/Surfnet/StepupMiddleware/ManagementBundle/Tests/Validator/Fixtures/invalid_configuration/invalid_sp_service_name.php new file mode 100644 index 00000000..088063d1 --- /dev/null +++ b/src/Surfnet/StepupMiddleware/ManagementBundle/Tests/Validator/Fixtures/invalid_configuration/invalid_sp_service_name.php @@ -0,0 +1,44 @@ + 'gateway.service_providers[0].service_name', + 'configuration' => [ + 'gateway' => [ + 'identity_providers' => [], + 'service_providers' => [ + [ + "entity_id" => "https://entity.tld/id", + "public_key" => "MIIE...", + "acs" => ["https://entity.tld/consume-assertion"], + "loa" => [ + "__default__" => "https://entity.tld/authentication/loa2", + ], + "second_factor_only" => false, + "second_factor_only_nameid_patterns" => [], + "assertion_encryption_enabled" => false, + "blacklisted_encryption_algorithms" => [], + // A well-formed locale map (e.g. ['en_GB' => 'Test Service']) is now + // valid. This fixture demonstrates a malformed value is still + // rejected: locale keys must be non-empty strings, not numeric. + "service_name" => [0 => 'Some Name'], + ], + ], + ], + ], +]; diff --git a/src/Surfnet/StepupMiddleware/ManagementBundle/Validator/ServiceProviderConfigurationValidator.php b/src/Surfnet/StepupMiddleware/ManagementBundle/Validator/ServiceProviderConfigurationValidator.php index 3da30b8a..14a23b6d 100644 --- a/src/Surfnet/StepupMiddleware/ManagementBundle/Validator/ServiceProviderConfigurationValidator.php +++ b/src/Surfnet/StepupMiddleware/ManagementBundle/Validator/ServiceProviderConfigurationValidator.php @@ -40,6 +40,7 @@ public function validate(array $configuration, string $propertyPath): void 'use_pdp', 'allow_sso_on_2fa', 'set_sso_cookie_on_2fa', + 'service_name', ]; if (empty($configuration['use_pdp'])) { @@ -54,6 +55,10 @@ public function validate(array $configuration, string $propertyPath): void $configuration['set_sso_cookie_on_2fa'] = false; } + if (!array_key_exists('service_name', $configuration)) { + $configuration['service_name'] = null; + } + StepupAssert::keysMatch( $configuration, $requiredProperties, @@ -91,6 +96,32 @@ public function validate(array $configuration, string $propertyPath): void $this->validateBooleanValue($configuration, 'use_pdp', $propertyPath); $this->validateBooleanValue($configuration, 'allow_sso_on_2fa', $propertyPath); $this->validateBooleanValue($configuration, 'set_sso_cookie_on_2fa', $propertyPath); + + if (isset($configuration['service_name'])) { + $this->validateServiceNameLocaleMap($configuration, 'service_name', $propertyPath); + } + } + + /** + * service_name is optional; when present it must be a map of locale code + * (e.g. "en_GB", "nl_NL") to a non-empty display name string for that + * locale. Per RFC OpenConext/Stepup-Gateway#587: "If set it must be a + * map of locale to service name." + * + * @param array $configuration + */ + private function validateServiceNameLocaleMap(array $configuration, string $name, string $propertyPath): void + { + $value = $configuration[$name]; + $path = $propertyPath . '.' . $name; + + Assertion::isArray($value, 'value must be a map of locale to service name', $path); + foreach ($value as $locale => $serviceName) { + Assertion::string($locale, 'locale keys must be strings', $path); + Assertion::notBlank($locale, 'locale keys must not be empty', $path); + Assertion::string($serviceName, 'service name values must be strings', $path . '.' . $locale); + Assertion::notBlank($serviceName, 'service name values must not be empty', $path . '.' . $locale); + } } private function validateStringValue(array $configuration, string $name, string $propertyPath): void