Skip to content

Commit

Permalink
Merge pull request #16 from madmatt/pulls/add-azure-ad
Browse files Browse the repository at this point in the history
Add Azure AD capability to module
  • Loading branch information
NightJar authored May 20, 2019
2 parents 4077e54 + 4c53578 commit f6e6103
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 45 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/silverstripe/silverstripe-saml/badges/quality-score.png)](https://scrutinizer-ci.com/g/silverstripe/silverstripe-saml/)
[![codecov](https://codecov.io/gh/silverstripe/silverstripe-saml/branch/master/graph/badge.svg)](https://codecov.io/gh/silverstripe/silverstripe-saml)

## Table of Contents

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Introduction](#introduction)
- [Requirements](#requirements)
- [Overview](#overview)
- [Security](#security)
- [In-depth guides](#in-depth-guides)
- [Changelog](#changelog)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Introduction

This SilverStripe module provides single sign-on authentication integration with a SAML provider.
Expand Down
4 changes: 3 additions & 1 deletion _config/saml.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ SilverStripe\SAML\Authenticators\SAMLAuthenticator:
SilverStripe\SAML\Services\SAMLConfiguration:
strict: true
debug: false
expect_binary_nameid: true
allow_insecure_email_linking: false
Security:
# Algorithm that the toolkit will use on signing process. Options:
# - 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
# - 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
# - 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
# - 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
signatureAlgorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
signatureAlgorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
53 changes: 53 additions & 0 deletions docs/en/azure-ad.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Azure AD administrator guide](#azure-ad-administrator-guide)
- [Table of contents](#table-of-contents)
- [Overview](#overview)
- [Creating a new Enterprise Application](#creating-a-new-enterprise-application)
- [FAQs](#faqs)
- [Why do we require using `objectId` instead of `userprincipalname`?](#why-do-we-require-using-objectid-instead-of-userprincipalname)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

# Azure AD administrator guide

This guide will step you through configuring a new SAML integration in Azure AD, such that Azure AD can act as an Identity Provider (IdP) for a SilverStripe site.

## Table of contents

## Overview

This is not an exhaustive guide, and it only covers Azure AD. Other SAML IdPs should have similar configuration, and the module should be able to work with them provided they can give the same guarantees about the attributes being passed through in the SAML assertion.

As an implementor of the IdP, you need to ensure the following have been set up:

* A bi-directional trust between SP and IdP (via metadata files)
* Ensuring that `user.objectId` is used as the User Identifier (aka SAML Name ID) rather than `user.userprincipalname`

## Creating a new Enterprise Application

1. Log in as an Azure portal administrator.
2. In the Azure portal, go to the ['Azure Active Directory' service](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview)
3. Click on 'Enterprise Applications', then 'New application'
4. Select 'Non-gallery application' and enter a descriptive name (e.g. 'Intranet'), then click Add
5. Once the application is created, select 'Single sign-on' from the left-menu, then select 'SAML'
6. Either upload the metadata file provided by the site developers, or enter the Entity ID and Reply URLs provided by the developers. These should always have the same base hostname, with the Reply URL including '/saml/acs' at the end
7. Under 'User Attributes', select `user.objectId` as the User Identifier, instead of the default (`user.userprincipalname`)
8. Provide the 'App Federation Metadata Url' and the base64 copy of the certificate under section 4 to the site developers

Once the site developers have deployed the certificates and login URLs, the configuration should be set up and ready to be tested. You will need to configure users or groups that have access to this application before Azure AD will pass them through to the website, you can do this from the 'Users and groups' sidebar menu.

## FAQs

### Why do we require using `objectId` instead of `userprincipalname`?

This is because `user.userprincipalname` (UPN) is not guaranteed to be unique *forever*, it is only guaranteed to be unique for the lifetime of a user's account. This is problematic in some situations. For example:

1. John Smith joins the organisation, and given a UPN of john.smith@contoso.com
2. John Smith is added to the list of users that can access the website, and is given special administrator privileges over the website
3. John then leaves the organisation and his account is deleted. The website is not aware that John has been deleted because SAML provides no mechanism for this, so the website still has an administrator account configured for John, but nobody can login to it right now.
4. A different John Smith starts at the organisation in a different role (e.g. not an administrator). They are given access to the website, and when they login they see they have administrative privileges.

This is because the UPN is only unique at any given time, not forever. To ensure that we don't run into security issues with the above, we require that Azure AD integrations use the `objectId` of the user instead. This means that the second John Smith in the above example will have a new account created for him, as his `objectId` will not match the `objectId` of the former John Smith.
2 changes: 1 addition & 1 deletion docs/en/build-toc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ if [ "$?" -ne "0" ]; then
exit 2
fi

for file in adfs.md developer.md troubleshooting.md; do
for file in adfs.md azure-ad.md contributing.md developer.md tech_notes.md troubleshooting.md ../../README.md; do
doctoc $file && sed -i "" "/Table of Contents.*generated with/d" $file
done
35 changes: 35 additions & 0 deletions docs/en/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Contribution guidelines
This document describes additional contribution guidelines that apply to this module only.

## Table of Contents

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Documentation](#documentation)
- [Adding new functionality](#adding-new-functionality)
- [Adding support for new identity providers (IdPs)](#adding-support-for-new-identity-providers-idps)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Documentation
All changes should be documented to a similar (or better) level as the existing code.

If you add or change headings in any of the existing documentation files, or create a new markdown file, make sure you re-run `./build-toc`:

- `npm install -g doctoc`
- `cd /path/to/module/docs/en`
- `./build-toc`

If the above doesn't work because build-toc fails or similar, feel free to submit your PR without updating the table of contents and we can do this upon merge.

## Adding new functionality
This module follows semantic versioning, therefore id you change anything that breaks backwards compatibility that will require a new major release. Please be clear about this when creating new pull requests. Alternatively, you can get you feature merged faster if you include YML configuration to disable your feature, and make it optional to enable.

If you add additional functionality, make sure to document it both in the codebase as well as the appropriate file in the docs directory.

## Adding support for new identity providers (IdPs)
Adding support for new IdPs generally means you also need to add documentation that describes how that IdP should be configured to work with this module.

For an example of the level of detail to include, see the existing documentation for [Azure AD](azure-ad.md) and [ADFS](adfs.md). Generally, this should describe at a high level exactly what you expect from the administrator of the IdP, and should be a document that you are comfortable sending to your IdP sysadmin to implement.
79 changes: 72 additions & 7 deletions docs/en/developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,21 @@ We assume ADFS 2.0 or greater is used as an IdP.
- [A note on signature algorithm config](#a-note-on-signature-algorithm-config)
- [Service Provider (SP)](#service-provider-sp)
- [Identity Provider (IdP)](#identity-provider-idp)
- [Additional configuration for Azure AD](#additional-configuration-for-azure-ad)
- [Establish trust](#establish-trust)
- [Configure SilverStripe Authenticators](#configure-silverstripe-authenticators)
- [Show the SAML Login button on login form](#show-the-saml-login-button-on-login-form)
- [Bypass auto login](#bypass-auto-login)
- [Automatically require SAML login for every request](#automatically-require-saml-login-for-every-request)
- [Test the connection](#test-the-connection)
- [Configure LDAP synchronisation](#configure-ldap-synchronisation)
- [Connect with LDAP](#connect-with-ldap)
- [More information on LDAP](#more-information-on-ldap)
- [Debugging](#debugging)
- [SAML debugging](#saml-debugging)
- [Advanced SAML configuration](#advanced-saml-configuration)
- [Allow insecure linking-by-email](#allow-insecure-linking-by-email)
- [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)
- [Resources](#resources)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -65,19 +69,26 @@ Contact your system administrator if you are not sure how to install these.

You also need to make the certificate for your ADFS endpoint available to the SilverStripe site. Talk with your ADFS administrator to find out how to obtain this.

If you are managing ADFS yourself, consult the [ADFS administrator guide](adfs.md).
* In you are integrating with ADFS, direct the ADFS administrator to the [ADFS administrator guide](adfs.md).
* If you are integrating with Azure AD, direct the Azure AD administrator to the [Azure AD administrator guide](azure-ad.md).

Note: For Azure AD, you will first need to decide on the Entity ID (see next step) so that you can provide this to the Azure AD administrator - they can't provide you the certificates until you provide them the Entity ID and Reply URL values.

You may also be able to extract the certificate yourself from the IdP endpoint if it has already been configured: `https://<idp-domain>/FederationMetadata/2007-06/FederationMetadata.xml`.

## YAML configuration

Now we need to make the *silverstripe-saml* module aware of where the certificates can be found.

Add the following configuration to `mysite/_config/saml.yml` (make sure to replace paths to the certificates and keys):
**Note:** If you are configuring this application for integration with Azure AD, a couple of extra keys need to be set. See the '[Additional configuration for Azure AD](#additional-configuration-for-azure-ad)' section below.

Add the following configuration to `app/_config/saml.yml` (make sure to replace paths to the certificates and keys):

```yaml

---
Name: mysamlsettings
After: '#samlsettings'
---
SilverStripe\SAML\Services\SAMLConfiguration:
strict: true
Expand All @@ -92,6 +103,7 @@ SilverStripe\SAML\Services\SAMLConfiguration:
singleSignOnService: "https://<idp-domain>/adfs/ls/"
Security:
signatureAlgorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"

```

If you don't use absolute paths, the certificate paths will be relative to the site web root.
Expand All @@ -112,7 +124,7 @@ SilverStripe\SAML\Services\SAMLConfiguration:
### Service Provider (SP)
- `entityId`: This should be the base URL with https for the SP
- `entityId`: This should be the base URL with https for the SP (e.g. https://example.com)
- `privateKey`: The private key used for signing SAML request
- `x509cert`: The public key that the IdP is using for verifying a signed request

Expand All @@ -122,13 +134,36 @@ SilverStripe\SAML\Services\SAMLConfiguration:
- `x509cert`: The token-signing certificate from ADFS (base 64 encoded)
- `singleSignOnService`: The endpoint on ADFS for where to send the SAML login request

### Additional configuration for Azure AD

When configuring the module to support Azure AD, a couple of additional configuration values need to be set to work with Azure AD out of the box. The below configuration should be merged into the YML configuration you have added above.

```yaml
SilverStripe\SAML\Services\SAMLConfiguration:
expect_binary_nameid: false
SP:
nameIdFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
SilverStripe\SAML\Extensions\SAMLMemberExtension:
claims_field_mappings:
- 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name': 'Email'
```

## Establish trust

At this stage the SilverStripe site trusts the ADFS, but the ADFS does not have any way to establish the identity of the SilverStripe site.
At this stage the SilverStripe site trusts the IdP, but the IdP does not have any way to establish the identity of the SilverStripe site.

The IdP should now be configured to extract the SP certificate from SilverStripe's SP endpoint. Once this is completed, bi-directional trust has been established and the authentication should be possible.

ADFS should now be configured to extract the SP certificate from SilverStripe's SP endpoint. Once this is completed, bi-directional trust has been established and the authentication should be possible.
*silverstripe-saml* has some specific requirements on how ADFS, Azure AD and other IdPs are configured. Consult one of the following guides depending on the IdP you are integrating with.

*silverstripe-saml* has some specific requirements on how ADFS is configured. If you are managing ADFS yourself, or you are assisting an ADFS administrator, consult the [ADFS administrator guide](adfs.md).
* [ADFS administrator guide](adfs.md)
* [Azure AD administrator guide](azure-ad.md)

In particular, most IdPs will require that you provide them with the entity ID and reply URLs (sometimes called the Assertion Consumer Service URL or ACS URL). These can be found by going to https://<site-domain>/saml/metadata once the above YML configuration is in place.

* The Entity ID is the URL exactly as you have entered it in the YML above, which should be the URL to the root of your website (e.g. https://example.com)
* The Reply URL is the Entity ID, with the suffix '/saml/acs' added to the end (e.g. https://example.com/saml/acs)

## Configure SilverStripe Authenticators

Expand Down Expand Up @@ -243,6 +278,36 @@ Also ensure that all protocols are matching. SAML is very sensitive to differenc

## Advanced SAML configuration

### Allow insecure linking-by-email

Normally the SAML module looks for a `Member` record based only on the GUID returned by the IdP. However, this can break in some situations, particularly when you are retrofitting single-sign-on into an existing system that already has member records in the database. A common use-case is that the website is setup, with centralised SSO being added later. At that point, you already have members that are setup with standard email/password logins, and those email addresses are the same as the user's primary email in the IdP. When the SAML module searches for a user when they login via SSO for the first time, it won't find them based on GUID, and will throw an error because it will attempt to create a new member with the same email as an existing user.

For this reason, the `allow_insecure_email_linking` YML config variable exists. During the transition period, you can enable this option so that if the lookup-by-GUID fails to find a valid member, the module will then attempt to lookup via the provided email address before falling back to creating a new member record.

**Note: This is not recommended in production.** If this setting is enabled, then we fall back to relying on the non-unique email address to log in to an existing member's account. For example, consider the situation where John Smith previously had an email/password login to your website. They leave the company, and a new John Smith (unrelated to the website) inherits the email address when they join the company. If this option is enabled and the new John Smith attempts to login, despite them not being allowed access, we will set the user up and link them to the old accounts (and whatever permissions the old user had).

We strongly recommend that you perform a full review of all users and permission levels for all members in the CMS **before you enable this setting** to ensure you will only create accounts for people that currently exist at the IdP.

You can enable this setting with the following YML config:

```yaml
---
Name: mysamlsettings
After: '#samlsettings'
---
SilverStripe\SAML\Services\SAMLConfiguration:
allow_insecure_email_linking: true
```

**Note**: You will also need to specify a `SAMLMemberExtension.claims_field_mappings` claim map that sets a value for 'Email', so that the IdP provides a value to include in the email field, for example:

```yaml
SilverStripe\SAML\Extensions\SAMLMemberExtension:
claims_field_mappings:
- 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress': 'Email'
```

### Adjust the requested AuthN contexts

By default, this module requests the following contexts (aka. 'ways users can login'):
Expand Down
11 changes: 11 additions & 0 deletions docs/en/tech_notes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Technical notes](#technical-notes)
- [Interface between SAML and LDAP](#interface-between-saml-and-ldap)
- [SAML+LDAP sequence](#samlldap-sequence)
- [Member record manipulation](#member-record-manipulation)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

# Technical notes

## Interface between SAML and LDAP
Expand Down
Loading

0 comments on commit f6e6103

Please sign in to comment.