From 9df060d0d6c48d4aabc38d67e61511d0bea427da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ivan=C4=8Di=C4=87?= <marko.ivancic@srce.hr> Date: Mon, 10 Jul 2023 16:00:54 +0200 Subject: [PATCH] WIP --- public/assets/css/src/icons/no-image.svg | 2 ++ src/Entities/Bases/AbstractProvider.php | 6 +++- src/Entities/Interfaces/ProviderInterface.php | 1 + .../Providers/Bases/AbstractOidcProvider.php | 34 +++++++++++++++++++ .../Providers/Bases/AbstractSaml2Provider.php | 26 ++++++++++++++ src/Entities/Providers/Identity/Oidc.php | 11 ++---- src/Entities/Providers/Service/Oidc.php | 11 ++---- src/Helpers/Arr.php | 29 ++++++++++++++++ templates/user/connected-organizations.twig | 9 ++++- tests/src/Constants/StateArrays.php | 32 ++++++++++++++++- .../Entities/Bases/AbstractProviderTest.php | 5 +++ .../Entities/Providers/Identity/OidcTest.php | 1 + .../Entities/Providers/Service/OidcTest.php | 1 + tests/src/Helpers/ArrayTest.php | 32 +++++++++++++++++ 14 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 public/assets/css/src/icons/no-image.svg create mode 100644 src/Entities/Providers/Bases/AbstractOidcProvider.php 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 0000000..3744910 --- /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 c4b66b6..e02dc39 100755 --- a/src/Entities/Bases/AbstractProvider.php +++ b/src/Entities/Bases/AbstractProvider.php @@ -6,15 +6,18 @@ 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(); } @@ -57,6 +60,7 @@ abstract class AbstractProvider implements ProviderInterface 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 a5957a5..259db35 100755 --- a/src/Entities/Interfaces/ProviderInterface.php +++ b/src/Entities/Interfaces/ProviderInterface.php @@ -15,5 +15,6 @@ interface ProviderInterface public function getName(string $locale = self::DEFAULT_LOCALE): ?string; public function getEntityId(): 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 0000000..5c1e73e --- /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 index 0a5fd98..3ffbdca 100644 --- a/src/Entities/Providers/Bases/AbstractSaml2Provider.php +++ b/src/Entities/Providers/Bases/AbstractSaml2Provider.php @@ -15,6 +15,8 @@ abstract class AbstractSaml2Provider extends AbstractProvider 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 { @@ -49,6 +51,30 @@ abstract class AbstractSaml2Provider extends AbstractProvider $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 */ diff --git a/src/Entities/Providers/Identity/Oidc.php b/src/Entities/Providers/Identity/Oidc.php index 9bc3784..0394cc5 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'; @@ -39,11 +37,6 @@ class Oidc extends AbstractProvider implements IdentityProviderInterface throw new MetadataException('OpenID VersionedDataProvider metadata does not contain entity ID.'); } - public function getProtocol(): AuthenticationProtocolInterface - { - return new Authentication\Protocol\Oidc(); - } - protected function getProviderDescription(): string { return $this->getProtocol()->getDesignation() . ' OpenID Provider'; diff --git a/src/Entities/Providers/Service/Oidc.php b/src/Entities/Providers/Service/Oidc.php index c683718..d074e77 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'; @@ -41,11 +39,6 @@ class Oidc extends AbstractProvider implements ServiceProviderInterface throw new MetadataException('Relying VersionedDataProvider metadata does not contain entity ID.'); } - public function getProtocol(): AuthenticationProtocolInterface - { - return new Authentication\Protocol\Oidc(); - } - protected function getProviderDescription(): string { return $this->getProtocol()->getDesignation() . ' Relying Party'; diff --git a/src/Helpers/Arr.php b/src/Helpers/Arr.php index 465e122..061461f 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 6bbbf8a..3e8e3af 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 1833f18..3d844cd 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', diff --git a/tests/src/Entities/Bases/AbstractProviderTest.php b/tests/src/Entities/Bases/AbstractProviderTest.php index e858379..aae51bd 100755 --- a/tests/src/Entities/Bases/AbstractProviderTest.php +++ b/tests/src/Entities/Bases/AbstractProviderTest.php @@ -117,6 +117,11 @@ class AbstractProviderTest extends TestCase { return 'provider description'; } + + public function getLogoUrl(): ?string + { + return 'https://example.org/logo'; + } }; } } diff --git a/tests/src/Entities/Providers/Identity/OidcTest.php b/tests/src/Entities/Providers/Identity/OidcTest.php index 91ab3e2..9d1a6a3 100755 --- a/tests/src/Entities/Providers/Identity/OidcTest.php +++ b/tests/src/Entities/Providers/Identity/OidcTest.php @@ -11,6 +11,7 @@ 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 */ class OidcTest extends TestCase { diff --git a/tests/src/Entities/Providers/Service/OidcTest.php b/tests/src/Entities/Providers/Service/OidcTest.php index db62ad4..a1445d4 100755 --- a/tests/src/Entities/Providers/Service/OidcTest.php +++ b/tests/src/Entities/Providers/Service/OidcTest.php @@ -11,6 +11,7 @@ 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 */ class OidcTest extends TestCase { diff --git a/tests/src/Helpers/ArrayTest.php b/tests/src/Helpers/ArrayTest.php index dc35b09..c1a409a 100755 --- a/tests/src/Helpers/ArrayTest.php +++ b/tests/src/Helpers/ArrayTest.php @@ -70,4 +70,36 @@ 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->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')); + } } -- GitLab