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 user group mapping #51

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
26 changes: 26 additions & 0 deletions docs/en/developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ We assume ADFS 2.0 or greater is used as an IdP.
- [Service Provider (SP)](#service-provider-sp)
- [Identity Provider (IdP)](#identity-provider-idp)
- [Additional configuration for Azure AD](#additional-configuration-for-azure-ad)
- [User groups mapping](#user-groups-mapping)
- [GUID Transformation](#guid-transformation)
- [Establish trust](#establish-trust)
- [Configure SilverStripe Authenticators](#configure-silverstripe-authenticators)
Expand All @@ -37,6 +38,7 @@ We assume ADFS 2.0 or greater is used as an IdP.
- [Adjust the requested AuthN contexts](#adjust-the-requested-authn-contexts)
- [Create your own SAML configuration for completely custom settings](#create-your-own-saml-configuration-for-completely-custom-settings)
- [Additional GET Query Params for SAML](#additional-get-query-params-for-saml)
- [Automatically redirect after authentication](#automatically-redirect-after-authentication)
- [Resources](#resources)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -151,6 +153,22 @@ SilverStripe\SAML\Extensions\SAMLMemberExtension:
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name': 'Email'
```

### User groups mapping

By default, any new users logged in using SSO will not have any groups assigned to them. User groups can be enabled via

```yml
SilverStripe\SAML\Services\SAMLConfiguration:
map_user_group: true
```

and specify the claims field to map

```yml
SilverStripe\SAML\Helpers\SAMLUserGroupMapper:
group_claims_field: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/groups'
```

### GUID Transformation

If you prefer to receive the GUID in lower-case or upper-case format you can use the
Expand Down Expand Up @@ -392,6 +410,14 @@ SilverStripe\SAML\Services\SAMLConfiguration:
this configuration allows you to add two GET query parameters to endpoint request URL:
`https://your-idp.com/singleSignOnService/saml2?someGetQueryParameter=value&AnotherParameter=differentValue&SAMLRequest=XYZ....`

### Automatically redirect after authentication
If the user has CMS permission and you want to redirect to the CMS after successful authentication, you can set the default login destination via:

```yaml
SilverStripe\Security\Security:
default_login_dest: 'admin'
```

## Resources

- [ADFS Deep-Dive: Onboarding Applications](http://blogs.technet.com/b/askpfeplat/archive/2015/03/02/adfs-deep-dive-onboarding-applications.aspx)
23 changes: 15 additions & 8 deletions src/Control/SAMLController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,28 @@
use Exception;

use function gmmktime;

use function uniqid;
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Constants;
use OneLogin\Saml2\Utils;
use OneLogin\Saml2\Error;
use OneLogin\Saml2\Utils;
use Psr\Log\LoggerInterface;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\SAML\Authenticators\SAMLAuthenticator;
use SilverStripe\SAML\Authenticators\SAMLLoginForm;
use SilverStripe\SAML\Helpers\SAMLHelper;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\SAML\Helpers\SAMLUserGroupMapper;
use SilverStripe\SAML\Model\SAMLResponse;
use SilverStripe\SAML\Services\SAMLConfiguration;
use SilverStripe\Security\IdentityStore;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;

use function uniqid;

/**
* Class SAMLController
*
Expand Down Expand Up @@ -212,6 +211,14 @@ public function acs()
// Both SAML and LDAP identify Members by the same GUID field.
$member->write();

$mapUserGroup = Config::inst()->get(SAMLConfiguration::class, 'map_user_group');
// Map user groups
if ($mapUserGroup) {
$mapper = SAMLUserGroupMapper::singleton();

$member = $mapper->map($attributes, $member);
}

// Hook for modifying login behaviour
$this->extend('updateLogin');

Expand Down
65 changes: 65 additions & 0 deletions src/Helpers/SAMLUserGroupMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace SilverStripe\SAML\Helpers;

use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\ORM\DataObject;
use SilverStripe\SAML\Services\SAMLConfiguration;
use SilverStripe\Security\Group;
use SilverStripe\Security\Member;

class SAMLUserGroupMapper
{
use Injectable;
use Configurable;

/**
* @var string
*/
private static $group_claims_field;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static $group_claims_field;
private static string $group_claims_field;


/**
* @var array
*/
private static $dependencies = [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static $dependencies = [
private static array $dependencies = [

'SAMLConfService' => '%$' . SAMLConfiguration::class,
];

/**
* Check if group claims field is set and assigns member to group
*
* @param [] $attributes
* @param Member $member
* @return Member
*/
public function map($attributes, $member): Member
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public function map($attributes, $member): Member
public function map(array $attributes, Member $member): Member

{
$groups = $this->config()->get('group_claims_field');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config() is a static method

Suggested change
$groups = $this->config()->get('group_claims_field');
$groups = self::config()->get('group_claims_field');

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


if (!isset($attributes[$groups])) {
return $member;
}

// Get groups from saml response
$groupTitles = $attributes[$groups];

foreach ($groupTitles as $groupTitle) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the query outside the loop, so we are executing one query that fetches everything into Arraylist. then you can look at the titles and check if we have the group or if we need a new one.

// Get Group object by Title
$group = DataObject::get_one(Group::class, [
'"Group"."Title"' => $groupTitle
]);

// Create group if it doesn't exist yet
if (!$group) {
$group = new Group();
$group->Title = $groupTitle;
$group->write();
}

$member->Groups()->add($group);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also want to remove users from groups they are no longer part of - this probably requires marking groups as 'synced' in some way so we know which ones to remove them from. Its quite likely that a site would have some groups synced with saml but others that are not (we've got at least one project with this requirement) - we wouldn't want to remove users from these 'manual' groups.

}

return $member;
}
}