Skip to content
Snippets Groups Projects
Commit b45df072 authored by Jaime Pérez's avatar Jaime Pérez
Browse files

authproc: Add new filter to remove invalid scopes.

The new saml:FilterScopes allows a SAML Service Provider to remove the values from a scoped attribute whose scope is not declared in the IdP metadata and/or does not match with the domain in use by the IdP itself.

This closes #22.
parent d8dc33c1
No related branches found
No related tags found
No related merge requests found
......@@ -145,6 +145,7 @@ The following filters are included in the SimpleSAMLphp distribution:
- [`preprodwarning:Warning`](./preprodwarning:warning): Warn the user about accessing a test IdP.
- [`saml:AttributeNameID`](./saml:nameid): Generate custom NameID with the value of an attribute.
- [`saml:ExpectedAuthnContextClassRef`](./saml:authproc_expectedauthncontextclassref): Verify the user's authentication context.
- [`saml:FilterScopes`](./saml:filterscopes): Filter attribute values with scopes forbidden for an IdP.
- [`saml:NameIDAttribute`](./saml:nameidattribute): Create an attribute based on the NameID we receive from the IdP.
- [`saml:PersistentNameID`](./saml:nameid): Generate persistent NameID from an attribute.
- [`saml:PersistentNameID2TargetedID`](./saml:nameid): Store persistent NameID as eduPersonTargetedID.
......
Scoped Attributes Filtering
===========================
This document describes the **FilterScopes** attribute filter in the saml module.
This filter allows a Service Provider to make sure the scopes included in the values
of certain attributes correspond to what the Identity Provider declares in its
metadata. If the IdP includes a list of scopes in the metadata, only those scopes will
be allowed. On the other hand, if no scopes are declared or the scope is not included
in the list of declared scopes, it will be matched against the domain used by the
SAML `SingleSignOnService` endpoint. This means the `example.com` scope will be
allowed in attributes received from an IdP whose `SingleSignOnService` endpoint
is located on the `example.com` top domain or any subdomain of that. Such scope will
be rejected though if the match with the IdP's endpoint does not happen at the top
level, like for example with `example.com.domain.net`.
If you are configuring the metadata of an IdP manually, remember to add an array
to it with the key `scope`, containing the list of scopes expected from that entity.
Configuration
-------------
This filter can be configured in the `config/authsources.php` file, inside the
`authproc` array of the corresponding SAML authentication source in use.
Note that this filter **can only be used with SAML authentication sources**.
Here are the options available for the filter:
`attributes`
: An array containing a list of attributes that are scoped and therefore should be evaluated.
Defaults to _eduPersonPrincipalName_ and _eduPersonScopedAffiliation_.
Examples
--------
Basic configuration:
```php
'authproc' => array(
90 => array(
'class' => 'saml:FilterScopes',
),
),
```
Specify `mail` and `eduPersonPrincipalName` as scoped attributes:
```php
'authproc' => array(
90 => array(
'class' => 'saml:FilterScopes',
'attributes' => array(
'mail',
'eduPersonPrincipalName',
),
),
),
```
Specify the same attributes in OID format:
```php
'authproc' => array(
90 => array(
'class' => 'saml:FilterScopes',
'attributes' => array(
'urn:oid:0.9.2342.19200300.100.1.3',
'urn:oid:1.3.6.1.4.1.5923.1.1.1.6',
),
),
),
```
<?php
namespace SimpleSAML\Module\saml\Auth\Process;
use SimpleSAML\Logger;
/**
* Filter to remove attribute values which are not properly scoped.
*
* @author Adam Lantos, NIIF / Hungarnet
* @author Jaime Pérez Crespo, UNINETT AS <jaime.perez@uninett.no>
* @package SimpleSAMLphp
*/
class FilterScopes extends \SimpleSAML_Auth_ProcessingFilter
{
/**
* Stores any pre-configured scoped attributes which come from the filter configuration.
*/
private $scopedAttributes = array(
'eduPersonScopedAffiliation',
'eduPersonPrincipalName'
);
/**
* Constructor for the processing filter.
*
* @param array &$config Configuration for this filter.
* @param mixed $reserved For future use.
*/
public function __construct(&$config, $reserved)
{
parent::__construct($config, $reserved);
assert('is_array($config)');
if (array_key_exists('attributes', $config) && !empty($config['attributes'])) {
$this->scopedAttributes = $config['attributes'];
}
}
/**
* This method applies the filter, removing any values
*
* @param array &$request the current request
*/
public function process(&$request)
{
$src = $request['Source'];
if (!count($this->scopedAttributes)) {
// paranoia, should never happen
Logger::warning('No scoped attributes configured.');
return;
}
$validScopes = array();
if (array_key_exists('scope', $src) && is_array($src['scope']) && !empty($src['scope'])) {
$validScopes = $src['scope'];
}
foreach ($this->scopedAttributes as $attribute) {
if (!isset($request['Attributes'][$attribute])) {
continue;
}
$values = $request['Attributes'][$attribute];
$newValues = array();
foreach ($values as $value) {
$ep = \SimpleSAML\Utils\Config\Metadata::getDefaultEndpoint($request['Source']['SingleSignOnService']);
$loc = $ep['Location'];
$host = parse_url($loc, PHP_URL_HOST);
if ($host === null) {
$host = '';
}
$value_a = explode('@', $value, 2);
if (count($value_a) < 2) {
continue; // there's no scope
}
$scope = $value_a[1];
if (in_array($scope, $validScopes, true)) {
$newValues[] = $value;
} elseif (strpos($host, $scope) === strlen($host) - strlen($scope)) {
$newValues[] = $value;
} else {
Logger::warning("Removing value '$value' for attribute '$attribute'. Undeclared scope.");
}
}
if (empty($newValues)) {
Logger::warning("No suitable values for attribute '$attribute', removing it.");
unset($request['Attributes'][$attribute]); // remove empty attributes
} else {
$request['Attributes'][$attribute] = $newValues;
}
}
}
}
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