Skip to content
Snippets Groups Projects
Commit 56ed532d authored by Thijs Kinkhorst's avatar Thijs Kinkhorst
Browse files

Merge branch 'ecp-support' of https://github.com/JohnMaguire/simplesamlphp...

Merge branch 'ecp-support' of https://github.com/JohnMaguire/simplesamlphp into JohnMaguire-ecp-support
parents 0c6669c0 ee4dcfb7
No related branches found
No related tags found
No related merge requests found
Adding Enhanced Client or Proxy (ECP) Profile support to the IdP
===============================================================
This document describes the necessary steps to enable support for the [SAML V2.0 Enhanced Client or Proxy Profile Version 2.0](http://docs.oasis-open.org/security/saml/Post2.0/saml-ecp/v2.0/cs01/saml-ecp-v2.0-cs01.pdf) on a simpleSAMLphp Identity Provider (IdP).
The SAML V2.0 Enhanced Client or Proxy (ECP) profile is a SSO profile for use with HTTP, and clients with the capability to directly contact a principal's identity provider(s) without requiring discovery and redirection by the service provider, as in the case of a browser. It is particularly useful for desktop or server-side HTTP clients.
Limitations
-----------
* Authentication must be done via [HTTP Basic authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#Basic_authentication_scheme).
* Authentication must not require user interaction (e.g. auth filters that require user input).
* Channel Bindings are unsupported.
* "Holder of Key" Subject Confirmation is unsupported.
This feature has been tested to work with Microsoft Office 365, but other service providers may require features of the ECP profile that are currently unsupported!
Enabling ECP Profile on the IdP
-----------------------------------
To enable the IdP to send ECP assertions you must add the `saml20.ecp` option to the `saml20-idp-hosted` metadata file:
$metadata['__DYNAMIC:1__'] = array(
[....]
'auth' => 'example-userpass',
'saml20.ecp' => true,
);
Note: authentication filters that require interaction with the user will not work with ECP.
Add new metadata to SPs
-----------------------
After enabling the ECP Profile your IdP metadata will change. An additional ECP `SingleSignOnService` endpoint is added.
You therefore need to update the metadata for your IdP at your SPs.
The `saml20-idp-remote` metadata for simpleSAMLphp SPs should contain something like the following code:
'SingleSignOnService' =>
array (
0 =>
array (
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
'Location' => 'https://idp.example.org/simplesaml/saml2/idp/SSOService.php',
),
1 =>
array (
'index' => 0,
'Location' => 'https://didp.example.org/simplesaml/saml2/idp/SSOService.php',
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP',
),
),
SP metadata on the IdP
----------------------
A SP using the ECP Profile must have an `AssertionConsumerService` endpoint supporting that profile.
This means that you have to use the complex endpoint format in `saml20-sp-remote` metadata.
In general, this should look like the following code:
'AssertionConsumerService' =>
array (
0 =>
array (
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
'Location' => 'https://sp.example.org/Shibboleth.sso/SAML2/POST',
'index' => 1,
),
1 =>
array (
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:PAOS',
'Location' => 'https://sp.example.org/ECP',
'index' => 2,
),
),
...@@ -255,6 +255,10 @@ The following SAML 2.0 options are available: ...@@ -255,6 +255,10 @@ The following SAML 2.0 options are available:
: Note that this requires a configured memcache server. : Note that this requires a configured memcache server.
`saml20.ecp`
: Set to `true` to enable the IdP to recieve authnrequests and send responses according the Enhanced Client or Proxy (ECP) Profile. Note: authentication filters that require interaction with the user will not work with ECP.
Defaults to `false`.
`saml20.hok.assertion` `saml20.hok.assertion`
: Set to `TRUE` to enable the IdP to send responses according the [Holder-of-Key Web Browser SSO Profile](./simplesamlphp-hok-idp). : Set to `TRUE` to enable the IdP to send responses according the [Holder-of-Key Web Browser SSO Profile](./simplesamlphp-hok-idp).
Defaults to `FALSE`. Defaults to `FALSE`.
......
...@@ -31,9 +31,9 @@ abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source { ...@@ -31,9 +31,9 @@ abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source {
* If this is NULL, we won't force any username. * If this is NULL, we won't force any username.
*/ */
private $forcedUsername; private $forcedUsername;
/** /**
* Links to pages from login page. * Links to pages from login page.
* From configuration * From configuration
*/ */
protected $loginLinks; protected $loginLinks;
...@@ -183,7 +183,24 @@ abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source { ...@@ -183,7 +183,24 @@ abstract class sspmod_core_Auth_UserPassBase extends SimpleSAML_Auth_Source {
* is allowed to change the username. * is allowed to change the username.
*/ */
$state['forcedUsername'] = $this->forcedUsername; $state['forcedUsername'] = $this->forcedUsername;
} }
// ECP requests supply authentication credentials with the AUthnRequest
// so we validate them now rather than redirecting
if (isset($state['core:auth:username']) && isset($state['core:auth:password'])) {
$username = $state['core:auth:username'];
$password = $state['core:auth:password'];
if (isset($state['forcedUsername'])) {
$username = $state['forcedUsername'];
}
$attributes = $this->login($username, $password);
assert('is_array($attributes)');
$state['Attributes'] = $attributes;
return;
}
/* Save the $state-array, so that we can restore it after a redirect. */ /* Save the $state-array, so that we can restore it after a redirect. */
$id = SimpleSAML_Auth_State::saveState($state, self::STAGEID); $id = SimpleSAML_Auth_State::saveState($state, self::STAGEID);
......
<?php <?php
use RobRichards\XMLSecLibs\XMLSecurityKey; use RobRichards\XMLSecLibs\XMLSecurityKey;
/** /**
...@@ -258,6 +257,9 @@ class sspmod_saml_IdP_SAML2 ...@@ -258,6 +257,9 @@ class sspmod_saml_IdP_SAML2
if ($idpMetadata->getBoolean('saml20.hok.assertion', false)) { if ($idpMetadata->getBoolean('saml20.hok.assertion', false)) {
$supportedBindings[] = \SAML2\Constants::BINDING_HOK_SSO; $supportedBindings[] = \SAML2\Constants::BINDING_HOK_SSO;
} }
if ($idpMetadata->getBoolean('saml20.ecp', false)) {
$supportedBindings[] = \SAML2\Constants::BINDING_PAOS;
}
if (isset($_REQUEST['spentityid'])) { if (isset($_REQUEST['spentityid'])) {
/* IdP initiated authentication. */ /* IdP initiated authentication. */
...@@ -428,9 +430,25 @@ class sspmod_saml_IdP_SAML2 ...@@ -428,9 +430,25 @@ class sspmod_saml_IdP_SAML2
'saml:RequestedAuthnContext' => $authnContext, 'saml:RequestedAuthnContext' => $authnContext,
); );
// ECP AuthnRequests need to supply credentials
if ($binding instanceof SOAP) {
self::processSOAPAuthnRequest($state);
}
$idp->handleAuthenticationRequest($state); $idp->handleAuthenticationRequest($state);
} }
public static function processSOAPAuthnRequest(array &$state)
{
if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
SimpleSAML_Logger::error("ECP AuthnRequest did not contain Basic Authentication header");
// TODO Throw some sort of ECP-specific exception / convert this to SOAP fault
throw new SimpleSAML_Error_Error("WRONGUSERPASS");
}
$state['core:auth:username'] = $_SERVER['PHP_AUTH_USER'];
$state['core:auth:password'] = $_SERVER['PHP_AUTH_PW'];
}
/** /**
* Send a logout request to a given association. * Send a logout request to a given association.
......
<?php
class sspmod_core_Auth_UserPassBaseTest extends \PHPUnit_Framework_TestCase
{
public function testAuthenticateECPCallsLoginAndSetsAttributes()
{
$state = array();
$attributes = array('attrib' => 'val');
$username = $state['core:auth:username'] = 'username';
$password = $state['core:auth:password'] = 'password';
$stub = $this->getMockBuilder('sspmod_core_Auth_UserPassBase')
->disableOriginalConstructor()
->setMethods(array('login'))
->getMockForAbstractClass();
$stub->expects($this->once())
->method('login')
->with($username, $password)
->will($this->returnValue($attributes));
$stub->authenticate($state);
$this->assertSame($attributes, $state['Attributes']);
}
public function testAuthenticateECPCallsLoginWithForcedUsername()
{
$state = array();
$attributes = array();
$forcedUsername = 'forcedUsername';
$state['core:auth:username'] = 'username';
$password = $state['core:auth:password'] = 'password';
$stub = $this->getMockBuilder('sspmod_core_Auth_UserPassBase')
->disableOriginalConstructor()
->setMethods(array('login'))
->getMockForAbstractClass();
$stub->expects($this->once())
->method('login')
->with($forcedUsername, $password)
->will($this->returnValue($attributes));
$stub->setForcedUsername($forcedUsername);
$stub->authenticate($state);
}
}
<?php
class sspmod_saml_IdP_SAML2Test extends \PHPUnit_Framework_TestCase
{
public function testProcessSOAPAuthnRequest()
{
$username = $_SERVER['PHP_AUTH_USER'] = 'username';
$password = $_SERVER['PHP_AUTH_PW'] = 'password';
$state = array();
sspmod_saml_IdP_SAML2::processSOAPAuthnRequest($state);
$this->assertEquals($username, $state['core:auth:username']);
$this->assertEquals($password, $state['core:auth:password']);
}
public function testProcessSOAPAuthnRequestMissingUsername()
{
$this->setExpectedException('SimpleSAML_Error_Error', 'WRONGUSERPASS');
$_SERVER['PHP_AUTH_PW'] = 'password';
unset($_SERVER['PHP_AUTH_USER']);
$state = array();
sspmod_saml_IdP_SAML2::processSOAPAuthnRequest($state);
}
public function testProcessSOAPAuthnRequestMissingPassword()
{
$this->setExpectedException('SimpleSAML_Error_Error', 'WRONGUSERPASS');
$_SERVER['PHP_AUTH_USER'] = 'username';
unset($_SERVER['PHP_AUTH_PW']);
$state = array();
sspmod_saml_IdP_SAML2::processSOAPAuthnRequest($state);
}
}
...@@ -127,6 +127,14 @@ try { ...@@ -127,6 +127,14 @@ try {
)); ));
} }
if ($idpmeta->getBoolean('saml20.ecp', false)) {
$metaArray['SingleSignOnService'][] = array(
'index' => 0,
'Binding' => SAML2_Const::BINDING_SOAP,
'Location' => SimpleSAML_Utilities::getHostnameURL() . 'saml2/idp/SSOService.php',
);
}
$metaArray['NameIDFormat'] = $idpmeta->getString( $metaArray['NameIDFormat'] = $idpmeta->getString(
'NameIDFormat', 'NameIDFormat',
'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment