Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow logging in with new role #633

Merged
merged 1 commit into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.dev
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions config/packages/dashboard_saml.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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)%"
1 change: 1 addition & 0 deletions config/packages/security.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
security:
role_hierarchy:
ROLE_ADMINISTRATOR: ROLE_USER
ROLE_SURFCONEXT_RESPONSIBLE: ROLE_USER

providers:
saml-provider:
Expand Down
14 changes: 9 additions & 5 deletions docs/testing-with-different-priviliges.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,19 +47,28 @@ 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,
parijke marked this conversation as resolved.
Show resolved Hide resolved
) {
$teams = explode(",", str_replace('\'', '', $administratorTeams));
Assert::allStringNotEmpty(
$teams,
'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
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,38 @@ 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);
$this->expectExceptionMessage('All entries in the `administrator_teams` config parameter should be string.');
$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
);
}
}
Loading