summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJaime Pérez <jaime.perez@uninett.no>2016-07-29 14:16:07 +0200
committerJaime Pérez <jaime.perez@uninett.no>2016-07-29 14:16:07 +0200
commitb45df0729c5d9048d975a1bb7b6e77f58c54edf5 (patch)
treefd5b34efef7dddd427d6360925af1bf6faba7062
parentd8dc33c1b3c7aaf318401b0825889a4e77d65798 (diff)
downloadsimplesamlphp-b45df0729c5d9048d975a1bb7b6e77f58c54edf5.zip
simplesamlphp-b45df0729c5d9048d975a1bb7b6e77f58c54edf5.tar.gz
simplesamlphp-b45df0729c5d9048d975a1bb7b6e77f58c54edf5.tar.bz2
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.
-rw-r--r--docs/simplesamlphp-authproc.md1
-rw-r--r--modules/saml/docs/filterscopes.md71
-rw-r--r--modules/saml/lib/Auth/Process/FilterScopes.php97
3 files changed, 169 insertions, 0 deletions
diff --git a/docs/simplesamlphp-authproc.md b/docs/simplesamlphp-authproc.md
index e0211a5..784ae9e 100644
--- a/docs/simplesamlphp-authproc.md
+++ b/docs/simplesamlphp-authproc.md
@@ -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.
diff --git a/modules/saml/docs/filterscopes.md b/modules/saml/docs/filterscopes.md
new file mode 100644
index 0000000..a3c28fa
--- /dev/null
+++ b/modules/saml/docs/filterscopes.md
@@ -0,0 +1,71 @@
+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',
+ ),
+ ),
+ ),
+```
diff --git a/modules/saml/lib/Auth/Process/FilterScopes.php b/modules/saml/lib/Auth/Process/FilterScopes.php
new file mode 100644
index 0000000..5457ae9
--- /dev/null
+++ b/modules/saml/lib/Auth/Process/FilterScopes.php
@@ -0,0 +1,97 @@
+<?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;
+ }
+ }
+ }
+}