diff --git a/public/assets/css/src/icons/no-image.svg b/public/assets/css/src/icons/no-image.svg new file mode 100644 index 0000000000000000000000000000000000000000..37449107d892a49858f0d4c4855abfefe57755d7 --- /dev/null +++ b/public/assets/css/src/icons/no-image.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" id="icon" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;}</style></defs><title>no-image</title><path d="M30,3.4141,28.5859,2,2,28.5859,3.4141,30l2-2H26a2.0027,2.0027,0,0,0,2-2V5.4141ZM26,26H7.4141l7.7929-7.793,2.3788,2.3787a2,2,0,0,0,2.8284,0L22,19l4,3.9973Zm0-5.8318-2.5858-2.5859a2,2,0,0,0-2.8284,0L19,19.1682l-2.377-2.3771L26,7.4141Z"/><path d="M6,22V19l5-4.9966,1.3733,1.3733,1.4159-1.416-1.375-1.375a2,2,0,0,0-2.8284,0L6,16.1716V6H22V4H6A2.002,2.002,0,0,0,4,6V22Z"/><rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" class="cls-1" width="32" height="32"/></svg> \ No newline at end of file diff --git a/src/Entities/Bases/AbstractProvider.php b/src/Entities/Bases/AbstractProvider.php index 9e63abde92dc28fc928e623ea3be29f2794de2fb..e02dc39e4b7fbc309668c79ae08f4c8e763204b0 100755 --- a/src/Entities/Bases/AbstractProvider.php +++ b/src/Entities/Bases/AbstractProvider.php @@ -6,35 +6,43 @@ namespace SimpleSAML\Module\accounting\Entities\Bases; use SimpleSAML\Module\accounting\Entities\Interfaces\AuthenticationProtocolInterface; use SimpleSAML\Module\accounting\Entities\Interfaces\ProviderInterface; +use SimpleSAML\Module\accounting\Services\HelpersManager; abstract class AbstractProvider implements ProviderInterface { protected array $metadata; + protected HelpersManager $helpersManager; protected string $entityId; - public function __construct(array $metadata) + public function __construct(array $metadata, HelpersManager $helpersManager = null) { $this->metadata = $metadata; + $this->helpersManager = $helpersManager ?? new HelpersManager(); $this->entityId = $this->resolveEntityId(); } - protected function resolveOptionallyLocalizedString(string $key, string $locale = 'en'): ?string - { - if (!isset($this->metadata[$key])) { + protected function resolveOptionallyLocalizedString( + string $key, + string $locale = self::DEFAULT_LOCALE, + array $metadataOverride = null + ): ?string { + $metadata = $metadataOverride ?? $this->metadata; + + if (!isset($metadata[$key])) { return null; } // Check for non-localized version. - if (is_string($this->metadata[$key])) { - return $this->metadata[$key]; + if (is_string($metadata[$key])) { + return $metadata[$key]; } if ( - is_array($this->metadata[$key]) && - !empty($this->metadata[$key][$locale]) && - is_string($this->metadata[$key][$locale]) + is_array($metadata[$key]) && + !empty($metadata[$key][$locale]) && + is_string($metadata[$key][$locale]) ) { - return $this->metadata[$key][$locale]; + return $metadata[$key][$locale]; } return null; @@ -50,8 +58,10 @@ abstract class AbstractProvider implements ProviderInterface return $this->entityId; } - abstract public function getName(string $locale = 'en'): ?string; - abstract public function getDescription(string $locale = 'en'): ?string; + abstract public function getName(string $locale = self::DEFAULT_LOCALE): ?string; + abstract public function getDescription(string $locale = self::DEFAULT_LOCALE): ?string; + abstract public function getLogoUrl(): ?string; abstract protected function resolveEntityId(): string; abstract public function getProtocol(): AuthenticationProtocolInterface; + abstract protected function getProviderDescription(): string; } diff --git a/src/Entities/Interfaces/ProviderInterface.php b/src/Entities/Interfaces/ProviderInterface.php index c82a1792698873b1ba3e295addba031708832ca4..259db355e48fa85e7efd7915a3174045880a4c0d 100755 --- a/src/Entities/Interfaces/ProviderInterface.php +++ b/src/Entities/Interfaces/ProviderInterface.php @@ -9,9 +9,12 @@ use SimpleSAML\Module\accounting\Entities\Bases\AbstractPayload; interface ProviderInterface { + public const DEFAULT_LOCALE = 'en'; + public function getMetadata(): array; - public function getName(string $locale = 'en'): ?string; + public function getName(string $locale = self::DEFAULT_LOCALE): ?string; public function getEntityId(): string; - public function getDescription(string $locale = 'en'): ?string; + public function getDescription(string $locale = self::DEFAULT_LOCALE): ?string; + public function getLogoUrl(): ?string; public function getProtocol(): AuthenticationProtocolInterface; } diff --git a/src/Entities/Providers/Bases/AbstractOidcProvider.php b/src/Entities/Providers/Bases/AbstractOidcProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..5c1e73eeeb10b4d0f22cc31cd1eee16e566f0e54 --- /dev/null +++ b/src/Entities/Providers/Bases/AbstractOidcProvider.php @@ -0,0 +1,34 @@ +<?php + +namespace SimpleSAML\Module\accounting\Entities\Providers\Bases; + +use SimpleSAML\Module\accounting\Entities\Authentication; +use SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider; +use SimpleSAML\Module\accounting\Entities\Interfaces\AuthenticationProtocolInterface; + +abstract class AbstractOidcProvider extends AbstractProvider +{ + public const METADATA_KEY_LOGO_URI = 'logo_uri'; + + abstract public function getName(string $locale = self::DEFAULT_LOCALE): ?string; + abstract public function getDescription(string $locale = self::DEFAULT_LOCALE): ?string; + abstract protected function resolveEntityId(): string; + abstract protected function getProviderDescription(): string; + + public function getLogoUrl(): ?string + { + if ( + !empty($this->metadata[self::METADATA_KEY_LOGO_URI]) && + is_string($this->metadata[self::METADATA_KEY_LOGO_URI]) + ) { + return $this->metadata[self::METADATA_KEY_LOGO_URI]; + } + + return null; + } + + public function getProtocol(): AuthenticationProtocolInterface + { + return new Authentication\Protocol\Oidc(); + } +} diff --git a/src/Entities/Providers/Bases/AbstractSaml2Provider.php b/src/Entities/Providers/Bases/AbstractSaml2Provider.php new file mode 100644 index 0000000000000000000000000000000000000000..3ffbdca60813796319520ac1b9d7a30e677e22b3 --- /dev/null +++ b/src/Entities/Providers/Bases/AbstractSaml2Provider.php @@ -0,0 +1,95 @@ +<?php + +namespace SimpleSAML\Module\accounting\Entities\Providers\Bases; + +use SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider; +use SimpleSAML\Module\accounting\Entities\Interfaces\AuthenticationProtocolInterface; +use SimpleSAML\Module\accounting\Exceptions\MetadataException; +use SimpleSAML\Module\accounting\Entities\Authentication; + +abstract class AbstractSaml2Provider extends AbstractProvider +{ + public const METADATA_KEY_ENTITY_ID = 'entityid'; + public const METADATA_KEY_NAME = 'name'; + public const METADATA_KEY_DESCRIPTION = 'description'; + public const METADATA_KEY_UI_INFO = 'UIInfo'; + public const METADATA_KEY_UI_INFO_DESCRIPTION = 'Description'; + public const METADATA_KEY_UI_INFO_DISPLAY_NAME = 'DisplayName'; + public const METADATA_KEY_UI_INFO_LOGO = 'Logo'; + public const METADATA_KEY_UI_INFO_LOGO_URL = 'url'; + + protected function getEntityInfoString(string $key, string $locale = self::DEFAULT_LOCALE): ?string + { + return $this->resolveOptionallyLocalizedString($key, $locale); + } + + protected function getEntityUiInfoString(string $key, string $locale = self::DEFAULT_LOCALE): ?string + { + if ( + isset($this->metadata[self::METADATA_KEY_UI_INFO]) && + is_array($this->metadata[self::METADATA_KEY_UI_INFO]) + ) { + return $this->resolveOptionallyLocalizedString( + $key, + $locale, + $this->metadata[self::METADATA_KEY_UI_INFO] + ); + } + + return null; + } + + public function getName(string $locale = self::DEFAULT_LOCALE): ?string + { + return $this->getEntityInfoString(self::METADATA_KEY_NAME, $locale) ?? + $this->getEntityUiInfoString(self::METADATA_KEY_UI_INFO_DISPLAY_NAME, $locale); + } + + public function getDescription(string $locale = self::DEFAULT_LOCALE): ?string + { + return $this->getEntityInfoString(self::METADATA_KEY_DESCRIPTION, $locale) ?? + $this->getEntityUiInfoString(self::METADATA_KEY_UI_INFO_DESCRIPTION, $locale); + } + + public function getLogoUrl(): ?string + { + $logoElement = $this->helpersManager->getArr()->getNestedElementByKey( + $this->metadata, + self::METADATA_KEY_UI_INFO, + self::METADATA_KEY_UI_INFO_LOGO, + 0, + self::METADATA_KEY_UI_INFO_LOGO_URL + ); + + if (!is_array($logoElement)) { + return null; + } + + /** @var mixed $logoUrl */ + $logoUrl = current($logoElement); + + if (is_string($logoUrl) && filter_var($logoUrl, FILTER_VALIDATE_URL)) { + return $logoUrl; + } + + return null; + } + + /** + * @throws MetadataException + */ + protected function resolveEntityId(): string + { + if (empty($entityId = $this->getEntityInfoString(self::METADATA_KEY_ENTITY_ID))) { + $message = sprintf('Provider metadata does not contain entity ID (%s).', $this->getProviderDescription()); + throw new MetadataException($message); + } + + return $entityId; + } + + public function getProtocol(): AuthenticationProtocolInterface + { + return new Authentication\Protocol\Saml2(); + } +} diff --git a/src/Entities/Providers/Identity/Oidc.php b/src/Entities/Providers/Identity/Oidc.php index c662f78753be98476d0c03d8086fb94a0dafa24b..0ead0e2b14903484b8f7a2c3a47dbd584cdb2a72 100755 --- a/src/Entities/Providers/Identity/Oidc.php +++ b/src/Entities/Providers/Identity/Oidc.php @@ -4,13 +4,11 @@ declare(strict_types=1); namespace SimpleSAML\Module\accounting\Entities\Providers\Identity; -use SimpleSAML\Module\accounting\Entities\Authentication; -use SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider; -use SimpleSAML\Module\accounting\Entities\Interfaces\AuthenticationProtocolInterface; use SimpleSAML\Module\accounting\Entities\Interfaces\IdentityProviderInterface; +use SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractOidcProvider; use SimpleSAML\Module\accounting\Exceptions\MetadataException; -class Oidc extends AbstractProvider implements IdentityProviderInterface +class Oidc extends AbstractOidcProvider implements IdentityProviderInterface { public const METADATA_KEY_ENTITY_ID = 'issuer'; @@ -36,11 +34,11 @@ class Oidc extends AbstractProvider implements IdentityProviderInterface return $this->metadata[self::METADATA_KEY_ENTITY_ID]; } - throw new MetadataException('OpenID VersionedDataProvider metadata does not contain entity ID.'); + throw new MetadataException($this->getProviderDescription() . ' metadata does not contain entity ID.'); } - public function getProtocol(): AuthenticationProtocolInterface + protected function getProviderDescription(): string { - return new Authentication\Protocol\Oidc(); + return $this->getProtocol()->getDesignation() . ' OpenID Provider'; } } diff --git a/src/Entities/Providers/Identity/Saml2.php b/src/Entities/Providers/Identity/Saml2.php index 04768636840af510618c96d26587f396b283afe4..8bef30d9a6a930bac920b202f27e8d3e90a13afa 100755 --- a/src/Entities/Providers/Identity/Saml2.php +++ b/src/Entities/Providers/Identity/Saml2.php @@ -5,44 +5,15 @@ declare(strict_types=1); namespace SimpleSAML\Module\accounting\Entities\Providers\Identity; use SimpleSAML\Module\accounting\Entities\Authentication; -use SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider; use SimpleSAML\Module\accounting\Entities\Interfaces\AuthenticationProtocolInterface; use SimpleSAML\Module\accounting\Entities\Interfaces\IdentityProviderInterface; +use SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractSaml2Provider; use SimpleSAML\Module\accounting\Exceptions\MetadataException; -class Saml2 extends AbstractProvider implements IdentityProviderInterface +class Saml2 extends AbstractSaml2Provider implements IdentityProviderInterface { - public const METADATA_KEY_NAME = 'name'; - public const METADATA_KEY_ENTITY_ID = 'entityid'; - public const METADATA_KEY_DESCRIPTION = 'description'; - - public function getName(string $locale = 'en'): ?string - { - return $this->resolveOptionallyLocalizedString(self::METADATA_KEY_NAME, $locale); - } - - public function getDescription(string $locale = 'en'): ?string - { - return $this->resolveOptionallyLocalizedString(self::METADATA_KEY_DESCRIPTION, $locale); - } - - /** - * @throws MetadataException - */ - protected function resolveEntityId(): string - { - if ( - !empty($this->metadata[self::METADATA_KEY_ENTITY_ID]) && - is_string($this->metadata[self::METADATA_KEY_ENTITY_ID]) - ) { - return $this->metadata[self::METADATA_KEY_ENTITY_ID]; - } - - throw new MetadataException('Identity provider metadata does not contain entity ID.'); - } - - public function getProtocol(): AuthenticationProtocolInterface + protected function getProviderDescription(): string { - return new Authentication\Protocol\Saml2(); + return $this->getProtocol()->getDesignation() . ' Identity Provider'; } } diff --git a/src/Entities/Providers/Service/Oidc.php b/src/Entities/Providers/Service/Oidc.php index 5f5ff82477128169db5708b40932172d79b54a6d..10cb0300be2a4d12985f64f6bde1060570f5a28a 100755 --- a/src/Entities/Providers/Service/Oidc.php +++ b/src/Entities/Providers/Service/Oidc.php @@ -4,13 +4,11 @@ declare(strict_types=1); namespace SimpleSAML\Module\accounting\Entities\Providers\Service; -use SimpleSAML\Module\accounting\Entities\Authentication; -use SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider; -use SimpleSAML\Module\accounting\Entities\Interfaces\AuthenticationProtocolInterface; use SimpleSAML\Module\accounting\Entities\Interfaces\ServiceProviderInterface; +use SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractOidcProvider; use SimpleSAML\Module\accounting\Exceptions\MetadataException; -class Oidc extends AbstractProvider implements ServiceProviderInterface +class Oidc extends AbstractOidcProvider implements ServiceProviderInterface { public const METADATA_KEY_ENTITY_ID = 'id'; public const METADATA_KEY_NAME = 'name'; @@ -38,11 +36,11 @@ class Oidc extends AbstractProvider implements ServiceProviderInterface return $this->metadata[self::METADATA_KEY_ENTITY_ID]; } - throw new MetadataException('Relying VersionedDataProvider metadata does not contain entity ID.'); + throw new MetadataException($this->getProviderDescription() . ' metadata does not contain entity ID.'); } - public function getProtocol(): AuthenticationProtocolInterface + protected function getProviderDescription(): string { - return new Authentication\Protocol\Oidc(); + return $this->getProtocol()->getDesignation() . ' Relying Party'; } } diff --git a/src/Entities/Providers/Service/Saml2.php b/src/Entities/Providers/Service/Saml2.php index d0dfd4ac065c2b22a7f3dcdcf82c14096981f40d..deeb30727e3c05b0446c315b726a73923321c9c1 100755 --- a/src/Entities/Providers/Service/Saml2.php +++ b/src/Entities/Providers/Service/Saml2.php @@ -4,45 +4,13 @@ declare(strict_types=1); namespace SimpleSAML\Module\accounting\Entities\Providers\Service; -use SimpleSAML\Module\accounting\Entities\Authentication; -use SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider; -use SimpleSAML\Module\accounting\Entities\Interfaces\AuthenticationProtocolInterface; use SimpleSAML\Module\accounting\Entities\Interfaces\ServiceProviderInterface; -use SimpleSAML\Module\accounting\Exceptions\MetadataException; +use SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractSaml2Provider; -class Saml2 extends AbstractProvider implements ServiceProviderInterface +class Saml2 extends AbstractSaml2Provider implements ServiceProviderInterface { - public const METADATA_KEY_ENTITY_ID = 'entityid'; - public const METADATA_KEY_NAME = 'name'; - public const METADATA_KEY_DESCRIPTION = 'description'; - - public function getName(string $locale = 'en'): ?string - { - return $this->resolveOptionallyLocalizedString(self::METADATA_KEY_NAME, $locale); - } - - public function getDescription(string $locale = 'en'): ?string - { - return $this->resolveOptionallyLocalizedString(self::METADATA_KEY_DESCRIPTION, $locale); - } - - /** - * @throws MetadataException - */ - protected function resolveEntityId(): string - { - if ( - !empty($this->metadata[self::METADATA_KEY_ENTITY_ID]) && - is_string($this->metadata[self::METADATA_KEY_ENTITY_ID]) - ) { - return $this->metadata[self::METADATA_KEY_ENTITY_ID]; - } - - throw new MetadataException('Service provider metadata does not contain entity ID.'); - } - - public function getProtocol(): AuthenticationProtocolInterface + protected function getProviderDescription(): string { - return new Authentication\Protocol\Saml2(); + return $this->getProtocol()->getDesignation() . ' Service Provider'; } } diff --git a/src/Helpers/Arr.php b/src/Helpers/Arr.php index 465e122ad3de3e8d9f57aed34b8c8e3494d3678c..061461f1a228b83391a24c445efdd0c4ab1932ec 100755 --- a/src/Helpers/Arr.php +++ b/src/Helpers/Arr.php @@ -37,4 +37,33 @@ class Arr $keys = array_keys($array); return $keys !== array_keys($keys); } + + /** + * @param array $array + * @param string|int ...$keys + * @return array|null + */ + public function getNestedElementByKey(array $array, ...$keys): ?array + { + $element = $array; + + foreach ($keys as $key) { + if (!is_array($element)) { + return null; + } + + if (!isset($element[$key])) { + return null; + } + + /** @var mixed $element */ + $element = $element[$key]; + } + + if (is_array($element)) { + return $element; + } + + return [$element]; + } } diff --git a/templates/user/connected-organizations.twig b/templates/user/connected-organizations.twig index 6bbbf8a4c2966de007cb4c13e1372bd453adf052..3e8e3afb5140bb30f6ffde8a024327c0f39708b0 100755 --- a/templates/user/connected-organizations.twig +++ b/templates/user/connected-organizations.twig @@ -20,7 +20,14 @@ {% for connectedServiceProvider in connectedServiceProviderBag.getAll %} <tr id="connected-service-provider-{{ loop.index }}"> - <td>{{ connectedServiceProvider.getServiceProvider.getName|e }}</td> + <td> + <img src="{{ connectedServiceProvider.serviceProvider.logoUrl ?? asset('css/src/icons/no-image.svg', 'accounting') }}" + width="30" + height="30" + loading="lazy" + alt="Service Logo"/> + {{ connectedServiceProvider.getServiceProvider.getName|e }} + </td> <td>{{ connectedServiceProvider.getNumberOfAuthentications|e }}</td> <td>{{ connectedServiceProvider.getLastAuthenticationAt|date() }}</td> </tr> diff --git a/tests/src/Constants/StateArrays.php b/tests/src/Constants/StateArrays.php index 1833f187400ec15444a6d5cd5ae2cdba4e3a4f31..8b4b425b056a7c2141e6e20ea519b5fafda2386b 100755 --- a/tests/src/Constants/StateArrays.php +++ b/tests/src/Constants/StateArrays.php @@ -44,7 +44,22 @@ final class StateArrays 'metadata-index' => 'https://pc-example.org.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp', 'metadata-set' => 'saml20-sp-remote', 'name' => 'Test service', - 'description' => 'Test service description' + 'description' => 'Test service description', + 'UIInfo' => [ + 'DisplayName' => [ + 'en' => 'Test service UiInfo', + ], + 'Description' => [ + 'en' => 'Test service description UiInfo', + ], + 'Logo' => [ + [ + 'url' => 'https://placehold.co/100x80/orange/white?text=test', + 'height' => 80, + 'width' => 100, + ], + ], + ], ], 'saml:RelayState' => 'https://localhost.someone.from.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/admin/test/default-sp', 'saml:RequestId' => null, @@ -117,6 +132,21 @@ final class StateArrays 'entityid' => 'https://pc-example.org.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp', 'metadata-index' => 'https://pc-example.org.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp', 'metadata-set' => 'saml20-sp-remote', + 'UIInfo' => [ + 'DisplayName' => [ + 'en' => 'Test service UiInfo', + ], + 'Description' => [ + 'en' => 'Test service description UiInfo', + ], + 'Logo' => [ + [ + 'url' => 'https://placehold.co/100x80/orange/white?text=test', + 'height' => 80, + 'width' => 100, + ], + ], + ], ], 'Source' => [ 'host' => 'localhost.someone.from.hr', @@ -219,6 +249,7 @@ final class StateArrays 'owner' => null, 'post_logout_redirect_uri' => [], 'backchannel_logout_uri' => 'http://sp.host.internal:8074/logout.php', + 'logo_uri' => 'http://sp.host.internal:8074/logo.svg', ], 'AuthorizationRequestParameters' => [ 'response_type' => 'code', diff --git a/tests/src/Entities/Bases/AbstractProviderTest.php b/tests/src/Entities/Bases/AbstractProviderTest.php index e8d8a2743b4492f6242c9ae8aa5b162bc8cead90..aae51bd5093d34fa7a257d5cd209e4628b873057 100755 --- a/tests/src/Entities/Bases/AbstractProviderTest.php +++ b/tests/src/Entities/Bases/AbstractProviderTest.php @@ -112,6 +112,16 @@ class AbstractProviderTest extends TestCase } }; } + + protected function getProviderDescription(): string + { + return 'provider description'; + } + + public function getLogoUrl(): ?string + { + return 'https://example.org/logo'; + } }; } } diff --git a/tests/src/Entities/Providers/Bases/AbstractOidcProviderTest.php b/tests/src/Entities/Providers/Bases/AbstractOidcProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..40dc7dccee1875fec35515ed9c29371a4a8c6531 --- /dev/null +++ b/tests/src/Entities/Providers/Bases/AbstractOidcProviderTest.php @@ -0,0 +1,81 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Test\Module\accounting\Entities\Providers\Bases; + +use PHPUnit\Framework\MockObject\Stub; +use SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Oidc; +use SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractOidcProvider; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Helpers\Arr; +use SimpleSAML\Module\accounting\Services\HelpersManager; +use SimpleSAML\Test\Module\accounting\Constants\StateArrays; + +/** + * @covers \SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractOidcProvider + * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider + */ +class AbstractOidcProviderTest extends TestCase +{ + protected Stub $helpersManagerStub; + protected Stub $arrStub; + protected array $metadata; + + protected function setUp(): void + { + $this->helpersManagerStub = $this->createStub(HelpersManager::class); + $this->arrStub = $this->createStub(Arr::class); + $this->metadata = StateArrays::OIDC_FULL['Oidc']['RelyingPartyMetadata']; + } + + public function testCanCreateInstance(): void + { + $this->assertInstanceOf(AbstractOidcProvider::class, $this->prepareInstance(StateArrays::OIDC_FULL)); + } + + public function testCanGetLogoUrl(): void + { + $this->assertSame( + $this->metadata['logo_uri'], + $this->prepareInstance($this->metadata)->getLogoUrl() + ); + + $metadaNoLogo = $this->metadata; + unset($metadaNoLogo['logo_uri']); + + $this->assertNull($this->prepareInstance($metadaNoLogo)->getLogoUrl()); + } + + public function testCanGetProtocol(): void + { + $this->assertInstanceOf(Oidc::class, $this->prepareInstance($this->metadata)->getProtocol()); + } + + protected function prepareInstance(array $metadata): AbstractOidcProvider + { + $this->helpersManagerStub->method('getArr')->willReturn($this->arrStub); + + return new class ($metadata, $this->helpersManagerStub) extends AbstractOidcProvider { + public function getName(string $locale = self::DEFAULT_LOCALE): ?string + { + return 'name'; + } + + public function getDescription(string $locale = self::DEFAULT_LOCALE): ?string + { + return 'description'; + } + + protected function resolveEntityId(): string + { + return 'entityId'; + } + + protected function getProviderDescription(): string + { + return 'provider description'; + } + }; + } +} diff --git a/tests/src/Entities/Providers/Bases/AbstractSaml2ProviderTest.php b/tests/src/Entities/Providers/Bases/AbstractSaml2ProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0822ec2bbda16b5fbc9eee41e08c443c54ac9bc0 --- /dev/null +++ b/tests/src/Entities/Providers/Bases/AbstractSaml2ProviderTest.php @@ -0,0 +1,136 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Test\Module\accounting\Entities\Providers\Bases; + +use PHPUnit\Framework\MockObject\Stub; +use SimpleSAML\Module\accounting\Entities\Interfaces\AuthenticationProtocolInterface; +use SimpleSAML\Module\accounting\Entities\Interfaces\ProviderInterface; +use SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractSaml2Provider; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Exceptions\MetadataException; +use SimpleSAML\Module\accounting\Helpers\Arr; +use SimpleSAML\Module\accounting\Services\HelpersManager; +use SimpleSAML\Test\Module\accounting\Constants\StateArrays; + +/** + * @covers \SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractSaml2Provider + * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider + */ +class AbstractSaml2ProviderTest extends TestCase +{ + protected Stub $helpersManagerStub; + protected Stub $arrStub; + protected array $metadata; + + protected function setUp(): void + { + $this->helpersManagerStub = $this->createStub(HelpersManager::class); + $this->arrStub = $this->createStub(Arr::class); + $this->metadata = StateArrays::SAML2_FULL['SPMetadata']; + } + + protected function prepareInstance(array $metadata): AbstractSaml2Provider + { + $this->helpersManagerStub->method('getArr')->willReturn($this->arrStub); + + return new class ($metadata, $this->helpersManagerStub) extends AbstractSaml2Provider { + protected function getProviderDescription(): string + { + return 'service description'; + } + }; + } + + public function testCanCreateInstance(): void + { + $this->assertInstanceOf(AbstractSaml2Provider::class, $this->prepareInstance($this->metadata)); + } + + public function testCanGetName(): void + { + $metadata = $this->metadata; + $this->assertSame( + $this->metadata[AbstractSaml2Provider::METADATA_KEY_NAME], + $this->prepareInstance($metadata)->getName() + ); + + unset($metadata[AbstractSaml2Provider::METADATA_KEY_NAME]); + $this->assertSame( + $this->metadata[AbstractSaml2Provider::METADATA_KEY_UI_INFO] + [AbstractSaml2Provider::METADATA_KEY_UI_INFO_DISPLAY_NAME] + [ProviderInterface::DEFAULT_LOCALE], + $this->prepareInstance($metadata)->getName() + ); + + unset($metadata[AbstractSaml2Provider::METADATA_KEY_UI_INFO]); + $this->assertNull($this->prepareInstance($metadata)->getName()); + } + + public function testCanGetDescription(): void + { + $metadata = $this->metadata; + $this->assertSame( + $this->metadata[AbstractSaml2Provider::METADATA_KEY_DESCRIPTION], + $this->prepareInstance($metadata)->getDescription() + ); + + unset($metadata[AbstractSaml2Provider::METADATA_KEY_DESCRIPTION]); + $this->assertSame( + $this->metadata[AbstractSaml2Provider::METADATA_KEY_UI_INFO] + [AbstractSaml2Provider::METADATA_KEY_UI_INFO_DESCRIPTION] + [ProviderInterface::DEFAULT_LOCALE], + $this->prepareInstance($metadata)->getDescription() + ); + + unset($metadata[AbstractSaml2Provider::METADATA_KEY_UI_INFO]); + $this->assertNull($this->prepareInstance($metadata)->getDescription()); + } + + public function testCanGetLogo(): void + { + $this->arrStub->method('getNestedElementByKey')->willReturn(['https://example.org/logo']); + + $this->assertSame( + 'https://example.org/logo', + $this->prepareInstance($this->metadata)->getLogoUrl() + ); + } + + public function testGetLogoNotFound(): void + { + $this->arrStub->method('getNestedElementByKey')->willReturn(null); + + $this->assertNull( + $this->prepareInstance($this->metadata)->getLogoUrl() + ); + } + + public function testGetLogoIsNullIfNotValid(): void + { + $this->arrStub->method('getNestedElementByKey')->willReturn(['not-valid']); + + $this->assertNull( + $this->prepareInstance($this->metadata)->getLogoUrl() + ); + } + + public function testThrowsForInvalidEntityId(): void + { + $metadata = $this->metadata; + unset($metadata[AbstractSaml2Provider::METADATA_KEY_ENTITY_ID]); + + $this->expectException(MetadataException::class); + + $this->prepareInstance($metadata); + } + + public function testCanGetProtocol(): void + { + $this->assertInstanceOf( + AuthenticationProtocolInterface::class, + $this->prepareInstance($this->metadata)->getProtocol() + ); + } +} diff --git a/tests/src/Entities/Providers/Identity/OidcTest.php b/tests/src/Entities/Providers/Identity/OidcTest.php index 91ab3e260936ecdbb74d3c17d9da8a6130916141..32591e217a0a4c2d3d28c8e660d8396bb7708d8c 100755 --- a/tests/src/Entities/Providers/Identity/OidcTest.php +++ b/tests/src/Entities/Providers/Identity/OidcTest.php @@ -11,6 +11,8 @@ use SimpleSAML\Module\accounting\Exceptions\MetadataException; /** * @covers \SimpleSAML\Module\accounting\Entities\Providers\Identity\Oidc * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider + * @uses \SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractOidcProvider + * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Oidc */ class OidcTest extends TestCase { diff --git a/tests/src/Entities/Providers/Identity/Saml2Test.php b/tests/src/Entities/Providers/Identity/Saml2Test.php index 7c995952a1fee1d1a238a78853682c393d655c6f..5c4db09f39ab25ac6fabe0066dc9d6aa76e15e17 100755 --- a/tests/src/Entities/Providers/Identity/Saml2Test.php +++ b/tests/src/Entities/Providers/Identity/Saml2Test.php @@ -12,6 +12,8 @@ use SimpleSAML\Test\Module\accounting\Constants\StateArrays; /** * @covers \SimpleSAML\Module\accounting\Entities\Providers\Identity\Saml2 * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider + * @uses \SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractSaml2Provider + * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2 */ class Saml2Test extends TestCase { diff --git a/tests/src/Entities/Providers/Service/OidcTest.php b/tests/src/Entities/Providers/Service/OidcTest.php index db62ad41c0a6337284d72342876b4e478248d3bc..dbba56c8fed584fb7ed1feb2126b08d4d84bef44 100755 --- a/tests/src/Entities/Providers/Service/OidcTest.php +++ b/tests/src/Entities/Providers/Service/OidcTest.php @@ -11,6 +11,8 @@ use SimpleSAML\Module\accounting\Exceptions\MetadataException; /** * @covers \SimpleSAML\Module\accounting\Entities\Providers\Service\Oidc * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider + * @uses \SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractOidcProvider + * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Oidc */ class OidcTest extends TestCase { diff --git a/tests/src/Entities/Providers/Service/Saml2Test.php b/tests/src/Entities/Providers/Service/Saml2Test.php index c4475d7089d65d70742a6b2b14d65b0860f72316..225c92ab25da2ff79911036270f30ce651c88f26 100755 --- a/tests/src/Entities/Providers/Service/Saml2Test.php +++ b/tests/src/Entities/Providers/Service/Saml2Test.php @@ -9,6 +9,8 @@ use SimpleSAML\Module\accounting\Exceptions\MetadataException; /** * @covers \SimpleSAML\Module\accounting\Entities\Providers\Service\Saml2 * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider + * @uses \SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractSaml2Provider + * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2 */ class Saml2Test extends TestCase { diff --git a/tests/src/Helpers/ArrayTest.php b/tests/src/Helpers/ArrayTest.php index dc35b097da155765d5f25dc94fa382e18091e05a..2ad8905cc1236493d8ee658dc0f490c005f64a0f 100755 --- a/tests/src/Helpers/ArrayTest.php +++ b/tests/src/Helpers/ArrayTest.php @@ -70,4 +70,37 @@ class ArrayTest extends TestCase $this->assertTrue((new Arr())->isAssociative($associative)); $this->assertTrue((new Arr())->isAssociative($mixed)); } + + public function testGetNestedElementByKey(): void + { + $simpleIndexed = [1, 2, 3]; + $nestedIndexed = [[1, [2, [4, [5]]]], [3, 4]]; + $nestedAssociative = ['a' => ['b' => 'c'], 'd' => 'e']; + + $arrHelper = new Arr(); + + $this->assertSame($arrHelper->getNestedElementByKey($simpleIndexed, 0), [1]); + $this->assertNull($arrHelper->getNestedElementByKey($simpleIndexed, 3)); + $this->assertNull($arrHelper->getNestedElementByKey($simpleIndexed, 'a')); + $this->assertNull($arrHelper->getNestedElementByKey($simpleIndexed, 0, 'a')); + + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 0), [1, [2, [4, [5]]]]); + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 0), [1, [2, [4, [5]]]]); + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 0, 0), [1]); + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 0, 1), [2, [4, [5]]]); + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 0, 1, 0), [2]); + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 0, 1, 1), [4, [5]]); + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 0, 1, 1, 0), [4]); + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 0, 1, 1, 1), [5]); + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 1), [3, 4]); + $this->assertSame($arrHelper->getNestedElementByKey($nestedIndexed, 1, 0), [3]); + $this->assertNull($arrHelper->getNestedElementByKey($nestedIndexed, 3)); + $this->assertNull($arrHelper->getNestedElementByKey($nestedIndexed, 'a')); + + $this->assertSame($arrHelper->getNestedElementByKey($nestedAssociative, 'a'), ['b' => 'c']); + $this->assertSame($arrHelper->getNestedElementByKey($nestedAssociative, 'a', 'b'), ['c']); + $this->assertSame($arrHelper->getNestedElementByKey($nestedAssociative, 'd'), ['e']); + $this->assertNull($arrHelper->getNestedElementByKey($nestedAssociative, 3)); + $this->assertNull($arrHelper->getNestedElementByKey($nestedAssociative, 'f')); + } } diff --git a/tests/src/Helpers/ProviderResolverTest.php b/tests/src/Helpers/ProviderResolverTest.php index 3661701b49175bb33f0e00568a2dbbd82b0fe60c..854e1af69375e87281c090ef48f6f4e63d879a68 100755 --- a/tests/src/Helpers/ProviderResolverTest.php +++ b/tests/src/Helpers/ProviderResolverTest.php @@ -21,6 +21,10 @@ use SimpleSAML\Test\Module\accounting\Constants\StateArrays; * @uses \SimpleSAML\Module\accounting\Entities\Providers\Identity\Oidc * @uses \SimpleSAML\Module\accounting\Entities\Providers\Service\Saml2 * @uses \SimpleSAML\Module\accounting\Entities\Providers\Service\Oidc + * @uses \SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractSaml2Provider + * @uses \SimpleSAML\Module\accounting\Entities\Providers\Bases\AbstractOidcProvider + * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2 + * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Oidc */ class ProviderResolverTest extends TestCase {