Skip to content

Commit b45df07

Browse files
committed
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 simplesamlphp#22.
1 parent d8dc33c commit b45df07

3 files changed

Lines changed: 169 additions & 0 deletions

File tree

docs/simplesamlphp-authproc.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ The following filters are included in the SimpleSAMLphp distribution:
145145
- [`preprodwarning:Warning`](./preprodwarning:warning): Warn the user about accessing a test IdP.
146146
- [`saml:AttributeNameID`](./saml:nameid): Generate custom NameID with the value of an attribute.
147147
- [`saml:ExpectedAuthnContextClassRef`](./saml:authproc_expectedauthncontextclassref): Verify the user's authentication context.
148+
- [`saml:FilterScopes`](./saml:filterscopes): Filter attribute values with scopes forbidden for an IdP.
148149
- [`saml:NameIDAttribute`](./saml:nameidattribute): Create an attribute based on the NameID we receive from the IdP.
149150
- [`saml:PersistentNameID`](./saml:nameid): Generate persistent NameID from an attribute.
150151
- [`saml:PersistentNameID2TargetedID`](./saml:nameid): Store persistent NameID as eduPersonTargetedID.

modules/saml/docs/filterscopes.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
Scoped Attributes Filtering
2+
===========================
3+
4+
This document describes the **FilterScopes** attribute filter in the saml module.
5+
6+
This filter allows a Service Provider to make sure the scopes included in the values
7+
of certain attributes correspond to what the Identity Provider declares in its
8+
metadata. If the IdP includes a list of scopes in the metadata, only those scopes will
9+
be allowed. On the other hand, if no scopes are declared or the scope is not included
10+
in the list of declared scopes, it will be matched against the domain used by the
11+
SAML `SingleSignOnService` endpoint. This means the `example.com` scope will be
12+
allowed in attributes received from an IdP whose `SingleSignOnService` endpoint
13+
is located on the `example.com` top domain or any subdomain of that. Such scope will
14+
be rejected though if the match with the IdP's endpoint does not happen at the top
15+
level, like for example with `example.com.domain.net`.
16+
17+
If you are configuring the metadata of an IdP manually, remember to add an array
18+
to it with the key `scope`, containing the list of scopes expected from that entity.
19+
20+
Configuration
21+
-------------
22+
23+
This filter can be configured in the `config/authsources.php` file, inside the
24+
`authproc` array of the corresponding SAML authentication source in use.
25+
26+
Note that this filter **can only be used with SAML authentication sources**.
27+
28+
Here are the options available for the filter:
29+
30+
`attributes`
31+
: An array containing a list of attributes that are scoped and therefore should be evaluated.
32+
Defaults to _eduPersonPrincipalName_ and _eduPersonScopedAffiliation_.
33+
34+
35+
Examples
36+
--------
37+
38+
Basic configuration:
39+
```php
40+
'authproc' => array(
41+
90 => array(
42+
'class' => 'saml:FilterScopes',
43+
),
44+
),
45+
```
46+
47+
Specify `mail` and `eduPersonPrincipalName` as scoped attributes:
48+
```php
49+
'authproc' => array(
50+
90 => array(
51+
'class' => 'saml:FilterScopes',
52+
'attributes' => array(
53+
'mail',
54+
'eduPersonPrincipalName',
55+
),
56+
),
57+
),
58+
```
59+
60+
Specify the same attributes in OID format:
61+
```php
62+
'authproc' => array(
63+
90 => array(
64+
'class' => 'saml:FilterScopes',
65+
'attributes' => array(
66+
'urn:oid:0.9.2342.19200300.100.1.3',
67+
'urn:oid:1.3.6.1.4.1.5923.1.1.1.6',
68+
),
69+
),
70+
),
71+
```
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
namespace SimpleSAML\Module\saml\Auth\Process;
4+
5+
use SimpleSAML\Logger;
6+
7+
/**
8+
* Filter to remove attribute values which are not properly scoped.
9+
*
10+
* @author Adam Lantos, NIIF / Hungarnet
11+
* @author Jaime Pérez Crespo, UNINETT AS <jaime.perez@uninett.no>
12+
* @package SimpleSAMLphp
13+
*/
14+
class FilterScopes extends \SimpleSAML_Auth_ProcessingFilter
15+
{
16+
17+
/**
18+
* Stores any pre-configured scoped attributes which come from the filter configuration.
19+
*/
20+
private $scopedAttributes = array(
21+
'eduPersonScopedAffiliation',
22+
'eduPersonPrincipalName'
23+
);
24+
25+
26+
/**
27+
* Constructor for the processing filter.
28+
*
29+
* @param array &$config Configuration for this filter.
30+
* @param mixed $reserved For future use.
31+
*/
32+
public function __construct(&$config, $reserved)
33+
{
34+
parent::__construct($config, $reserved);
35+
assert('is_array($config)');
36+
37+
if (array_key_exists('attributes', $config) && !empty($config['attributes'])) {
38+
$this->scopedAttributes = $config['attributes'];
39+
}
40+
}
41+
42+
43+
/**
44+
* This method applies the filter, removing any values
45+
*
46+
* @param array &$request the current request
47+
*/
48+
public function process(&$request)
49+
{
50+
$src = $request['Source'];
51+
if (!count($this->scopedAttributes)) {
52+
// paranoia, should never happen
53+
Logger::warning('No scoped attributes configured.');
54+
return;
55+
}
56+
$validScopes = array();
57+
if (array_key_exists('scope', $src) && is_array($src['scope']) && !empty($src['scope'])) {
58+
$validScopes = $src['scope'];
59+
}
60+
61+
foreach ($this->scopedAttributes as $attribute) {
62+
if (!isset($request['Attributes'][$attribute])) {
63+
continue;
64+
}
65+
66+
$values = $request['Attributes'][$attribute];
67+
$newValues = array();
68+
foreach ($values as $value) {
69+
$ep = \SimpleSAML\Utils\Config\Metadata::getDefaultEndpoint($request['Source']['SingleSignOnService']);
70+
$loc = $ep['Location'];
71+
$host = parse_url($loc, PHP_URL_HOST);
72+
if ($host === null) {
73+
$host = '';
74+
}
75+
$value_a = explode('@', $value, 2);
76+
if (count($value_a) < 2) {
77+
continue; // there's no scope
78+
}
79+
$scope = $value_a[1];
80+
if (in_array($scope, $validScopes, true)) {
81+
$newValues[] = $value;
82+
} elseif (strpos($host, $scope) === strlen($host) - strlen($scope)) {
83+
$newValues[] = $value;
84+
} else {
85+
Logger::warning("Removing value '$value' for attribute '$attribute'. Undeclared scope.");
86+
}
87+
}
88+
89+
if (empty($newValues)) {
90+
Logger::warning("No suitable values for attribute '$attribute', removing it.");
91+
unset($request['Attributes'][$attribute]); // remove empty attributes
92+
} else {
93+
$request['Attributes'][$attribute] = $newValues;
94+
}
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)