diff --git a/.env.dev b/.env.dev index 359d6bb59..18910e13f 100644 --- a/.env.dev +++ b/.env.dev @@ -1,4 +1,5 @@ -APP_ENV=prod +APP_ENV=dev +APP_DEBUG=true APP_SECRET=e1023e5989bec76e282bd0ee405200e0 DATABASE_URL="mysql://spdrw:secret@mariadb/spdashboard?serverVersion=mariadb-10.4.11&charset=utf8" MAILER_DSN=null://null diff --git a/.env.dist b/.env.dist index 21086a24f..826e74f0a 100644 --- a/.env.dist +++ b/.env.dist @@ -55,6 +55,9 @@ logout_redirect_url='https=//www.surf.nl/over-surf/werkmaatschappijen/surfnet' # All users in these teams get the administrator role administrator_teams="'urn:collab:org:surf.nl','urn:collab:org:vm.openconext.org','urn:collab:org:dev.support.surfconext.nl'" +# Whhen the `surf-autorsaties` attribute contains this value, the user is granted +surfconext_responsible_authorization='surfconext_verantwoordelijke' + saml_sp_publickey='%kernel.project_dir%//vendor/surfnet/stepup-saml-bundle/src/Resources/keys/development_publickey.cer' saml_sp_privatekey='%kernel.project_dir%//vendor/surfnet/stepup-saml-bundle/src/Resources/keys/development_privatekey.pem' saml_metadata_publickey='%kernel.project_dir%//vendor/surfnet/stepup-saml-bundle/src/Resources/keys/development_publickey.cer' diff --git a/config/packages/dashboard_saml.yaml b/config/packages/dashboard_saml.yaml index bd29d05e2..4842ff2cc 100644 --- a/config/packages/dashboard_saml.yaml +++ b/config/packages/dashboard_saml.yaml @@ -3,3 +3,4 @@ dashboard_saml: max_absolute_lifetime: "%env(int:session_max_absolute_lifetime)%" max_relative_lifetime: "%env(int:session_max_relative_lifetime)%" administrator_teams: "%env(administrator_teams)%" + surfconext_responsible_authorization: "%env(surfconext_responsible_authorization)%" diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 371ab5245..61786b35f 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,6 +1,7 @@ security: role_hierarchy: ROLE_ADMINISTRATOR: ROLE_USER + ROLE_SURFCONEXT_RESPONSIBLE: ROLE_USER providers: saml-provider: diff --git a/docs/testing-with-different-priviliges.md b/docs/testing-with-different-priviliges.md index bd455b3e9..c2e585ee5 100644 --- a/docs/testing-with-different-priviliges.md +++ b/docs/testing-with-different-priviliges.md @@ -3,16 +3,20 @@ When adjusting the designs for the site, you need to test changes both as a normal user and as a user with administrator access. ## Logging in as admin: -- make sure that a team identifier for a service which exists in your box is set in `parameters.yml` for the `administrator_teams` key. By default, it should look like this: +- make sure that a team identifier for a service which exists in your box is set in `.env` for the `administrator_teams` key. By default, it should look like this: ``` -administrator_teams: - - 'urn:collab:org:surf.nl' - - 'urn:collab:org:dev.support.surfconext.nl' +administrator_teams="'urn:collab:org:surf.nl','urn:collab:org:dev.openconext.local','urn:collab:org:dev.support.surfconext.nl','urn:collab:group:dev.openconext.local:dev:openconext:local:spd_admin'" ``` Doing this ensures that when you log in you are a member of the teams listed there by default & that users of those teams have admin rights. +## Logging in as SURFconext verantwoordelijke: +Just like setting the administrator teams. You can also configure a role(s) that will make the logged in user a 'SURFconext verantwoordelijke'. +Use the `surfconext_responsible_authorization` `.env` var to configure these roles. + +In this case. SP Dashboard will read the `surf-authorisaties` attribute (from SAML Response attributes). And see if the value there matches our `surfconext_responsible_authorization` value. + ## Logging in as a "normal user": -- remove the content under the `administrator_teams` key in `parameters.yml`, so no key is present. +- remove the content under the `administrator_teams` key in `.env`, so no key is present. - ensure that you know the team identifiers for at least two existing services. - when logging in to spdashboard with mujina, opt to add the `isMemberOf` attribute. Pass in the team-identifiers you noted above. Add the `isMemberOf` attribute once for each identifier. diff --git a/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/DependencyInjection/Configuration.php b/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/DependencyInjection/Configuration.php index d0640b794..f1287e014 100644 --- a/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/DependencyInjection/Configuration.php +++ b/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/DependencyInjection/Configuration.php @@ -46,6 +46,10 @@ private function appendSessionConfiguration(NodeBuilder $childNodes): void ->info('All users in these teams get the administrator role. Teams is a string containing roles seperated by comma\'s') ->isRequired() ->end() + ->scalarNode('surfconext_responsible_authorization') + ->info('All users in these teams get the ROLE_SURFCONEXT_RESPONSIBLE role. Teams is a string containing roles seperated by comma\'s') + ->isRequired() + ->end() ->arrayNode('session_lifetimes') ->isRequired() ->children() diff --git a/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/DependencyInjection/DashboardSamlExtension.php b/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/DependencyInjection/DashboardSamlExtension.php index bc89a8a69..f37b8e83c 100644 --- a/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/DependencyInjection/DashboardSamlExtension.php +++ b/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/DependencyInjection/DashboardSamlExtension.php @@ -48,6 +48,10 @@ public function load(array $configs, ContainerBuilder $container): void 'surfnet.dashboard.security.authentication.administrator_teams', $config['administrator_teams'] ); + $container->setParameter( + 'surfnet.dashboard.security.authentication.surfconext_responsible_authorization', + $config['surfconext_responsible_authorization'] + ); $container->setParameter( 'surfnet.dashboard.security.authentication.session.maximum_absolute_lifetime_in_seconds', $config['session_lifetimes']['max_absolute_lifetime'] diff --git a/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/Resources/config/services.yml b/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/Resources/config/services.yml index fb95ca381..d70b16344 100644 --- a/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/Resources/config/services.yml +++ b/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/Resources/config/services.yml @@ -22,6 +22,7 @@ services: - '@surfnet_saml.saml.attribute_dictionary' - '@logger' - '%surfnet.dashboard.security.authentication.administrator_teams%' + - '%surfnet.dashboard.security.authentication.surfconext_responsible_authorization%' Surfnet\ServiceProviderDashboard\Infrastructure\DashboardSamlBundle\Security\Authentication\Provider\SamlProvider: alias: surfnet_saml.saml_provider diff --git a/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/Security/Authentication/Provider/SamlProvider.php b/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/Security/Authentication/Provider/SamlProvider.php index 7c19a1649..03760c7ab 100644 --- a/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/Security/Authentication/Provider/SamlProvider.php +++ b/src/Surfnet/ServiceProviderDashboard/Infrastructure/DashboardSamlBundle/Security/Authentication/Provider/SamlProvider.php @@ -18,7 +18,6 @@ namespace Surfnet\ServiceProviderDashboard\Infrastructure\DashboardSamlBundle\Security\Authentication\Provider; -use BadMethodCallException; use Psr\Log\LoggerInterface; use SAML2\Assertion; use Surfnet\SamlBundle\Exception\RuntimeException; @@ -48,12 +47,15 @@ class SamlProvider implements SamlProviderInterface, UserProviderInterface */ private readonly array $administratorTeams; + private readonly string $surfConextResponsibleAuthorization; + public function __construct( private readonly ContactRepository $contacts, private readonly ServiceRepository $services, private readonly AttributeDictionary $attributeDictionary, private readonly LoggerInterface $logger, string $administratorTeams, + string $surfConextResponsibleAuthorization, ) { $teams = explode(",", str_replace('\'', '', $administratorTeams)); Assert::allStringNotEmpty( @@ -61,6 +63,12 @@ public function __construct( 'All entries in the `administrator_teams` config parameter should be string.' ); $this->administratorTeams = $teams; + + Assert::stringNotEmpty( + $surfConextResponsibleAuthorization, + 'The `surfconext_responsible_authorization` config parameter should be a non empty string.' + ); + $this->surfConextResponsibleAuthorization = $surfConextResponsibleAuthorization; } public function getNameId(Assertion $assertion): string @@ -95,10 +103,21 @@ public function getUser(Assertion $assertion): UserInterface $teamNames = []; } + try { + // An exception is thrown when isMemberOf is empty. + $surfAuthorizations = (array) $translatedAssertion->getAttributeValue('surf-autorisaties'); + } catch (RuntimeException) { + $surfAuthorizations = []; + } + if (array_intersect($this->administratorTeams, $teamNames) !== []) { $role = 'ROLE_ADMINISTRATOR'; } + if (in_array($this->surfConextResponsibleAuthorization, $surfAuthorizations)) { + $role = 'ROLE_SURFCONEXT_RESPONSIBLE'; + } + $contact = $this->contacts->findByNameId($nameId); if ($contact === null) { @@ -112,6 +131,11 @@ public function getUser(Assertion $assertion): UserInterface $this->assignServicesToContact($contact, $teamNames); $this->contacts->save($contact); } + + if ($role === 'ROLE_SURFCONEXT_RESPONSIBLE') { + $this->assignServicesToContact($contact, $teamNames); + $this->contacts->save($contact); + } $contact->assignRole($role); return new Identity($contact); } diff --git a/tests/unit/Infrastructure/DashboardSamlBundle/Security/Authentication/Provider/SamlProviderTest.php b/tests/unit/Infrastructure/DashboardSamlBundle/Security/Authentication/Provider/SamlProviderTest.php index 0c7642250..7f0e92e97 100644 --- a/tests/unit/Infrastructure/DashboardSamlBundle/Security/Authentication/Provider/SamlProviderTest.php +++ b/tests/unit/Infrastructure/DashboardSamlBundle/Security/Authentication/Provider/SamlProviderTest.php @@ -69,6 +69,15 @@ public function test_administrator_teams_validation_accepts_valid_teams() ); } + public function test_surfconext_responsible_teams_validation_accepts_valid_teams() + { + $provider = $this->buildProvider("'urn:collab:foo:team.foobar.com'", "'urn:collab:foo:team.foobar.com', 'urn:collab:foo:team.foobar2.com'"); + self::assertInstanceOf( + SamlProvider::class, + $provider + ); + } + public function test_administrator_teams_validation_rejects_invalid_teams() { $this->expectException(InvalidArgumentException::class); @@ -76,8 +85,22 @@ public function test_administrator_teams_validation_rejects_invalid_teams() $this->buildProvider(",345345,true,false,foo,bar"); } - private function buildProvider($administratorTeams) + public function test_surfconext_responsible_teams_rejects_invalid_teams() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('All entries in the `surfconext_responsible_authorization` config parameter should be string.'); + $this->buildProvider("'urn:collab:foo:team.foobar.com'", ",345345,true,false,foo,bar"); + } + + private function buildProvider(string $administratorTeams, string $surfConextResponsible = "'defualt") { - return new SamlProvider($this->contactRepo, $this->serviceRepo, $this->attributeDictionary, $this->logger, $administratorTeams); + return new SamlProvider( + $this->contactRepo, + $this->serviceRepo, + $this->attributeDictionary, + $this->logger, + $administratorTeams, + $surfConextResponsible + ); } }