From 0c07d7bdcd425303ef4b9ce01e5834240a38cee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=A3o=20Martins?= <Lasering@users.noreply.github.com> Date: Tue, 15 Dec 2020 21:52:50 +0000 Subject: [PATCH] Filter multiauth authentication sources from SP using AuthnContextClassRef (#1362) Co-authored-by: Tim van Dijen <tvdijen@gmail.com> --- modules/multiauth/docs/multiauth.md | 13 ++++++- .../multiauth/lib/Auth/Source/MultiAuth.php | 37 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/modules/multiauth/docs/multiauth.md b/modules/multiauth/docs/multiauth.md index 97ae265fe..5b474513a 100644 --- a/modules/multiauth/docs/multiauth.md +++ b/modules/multiauth/docs/multiauth.md @@ -36,12 +36,14 @@ authentication source: 'es' => 'Entrar usando un SP SAML', ), 'css-class' => 'SAML', + 'AuthnContextClassRef' => array('urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI', 'urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract'), ), 'example-admin' => array( 'text' => array( 'en' => 'Log in using the admin password', 'es' => 'Entrar usando la contraseña de administrador', ), + 'AuthnContextClassRef' => 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport', ), ), ), @@ -77,7 +79,7 @@ compatible fashion so both cases should work. Each source in the sources array has a key and a value. As mentioned above the key is the authsource identifier and the value -is another array with two optional keys: 'text' and 'css-class'. +is another array with optional keys: 'text', 'css-class', 'help', and 'AuthnContextClassRef'. The text element is another array with localized strings for one or more languages. These texts will be shown in the selectsource.php view. Note that you should at least enter the text in the default @@ -87,7 +89,14 @@ the <li> element in the selectsource.php view. By default the authtype of the authsource is used as the css class with colons replaced by dashes. So in the previous example, the css class used in the 'example-admin' authentication source would be -'core-AdminPassword'. +'core-AdminPassword'. The help element is another array with localized +strings for one or more languages. These texts will be shown in the +selectsource.php view. The AuthnContextClassRef is either a string or +an array of strings containing [context class ref names](https://docs.oasis-open.org/security/saml/v2.0/saml-authn-context-2.0-os.pdf). +If an SP sets AuthnContextClassRef the list of authsources will be +filtered to only those containing context class refs that are part of the list set by the SP. +If a single authsource results from this filtering the user will be taken directly to the +authentication page for that source, and will never be shown the multiauth select page. It is possible to add the parameter `source` to the calling URL, when accessing a service, to allow the user to preselect the diff --git a/modules/multiauth/lib/Auth/Source/MultiAuth.php b/modules/multiauth/lib/Auth/Source/MultiAuth.php index 3f93169cb..9e5273146 100644 --- a/modules/multiauth/lib/Auth/Source/MultiAuth.php +++ b/modules/multiauth/lib/Auth/Source/MultiAuth.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace SimpleSAML\Module\multiauth\Auth\Source; +use SAML2\Constants; use Exception; use SimpleSAML\Assert\Assert; use SimpleSAML\Auth; @@ -12,6 +13,7 @@ use SimpleSAML\Error; use SimpleSAML\Module; use SimpleSAML\Session; use SimpleSAML\Utils; +use SimpleSAML\Module\saml\Error\NoAuthnContext; /** * Authentication source which let the user chooses among a list of @@ -113,11 +115,22 @@ class MultiAuth extends Auth\Source } } + $class_ref = []; + if (array_key_exists('AuthnContextClassRef', $info)) { + $ref = $info['AuthnContextClassRef']; + if (is_string($ref)) { + $class_ref = [$ref]; + } else { + $class_ref = $ref; + } + } + $this->sources[] = [ 'source' => $source, 'text' => $text, 'help' => $help, 'css_class' => $css_class, + 'AuthnContextClassRef' => $class_ref, ]; } } @@ -144,6 +157,30 @@ class MultiAuth extends Auth\Source $state['multiauth:preselect'] = $this->preselect; } + if ( + !is_null($state['saml:RequestedAuthnContext']) + && array_key_exists('AuthnContextClassRef', $state['saml:RequestedAuthnContext']) + ) { + $refs = array_values($state['saml:RequestedAuthnContext']['AuthnContextClassRef']); + $new_sources = []; + foreach ($this->sources as $source) { + if (count(array_intersect($source['AuthnContextClassRef'], $refs)) >= 1) { + $new_sources[] = $source; + } + } + $state[self::SOURCESID] = $new_sources; + + $number_of_sources = count($new_sources); + if ($number_of_sources === 0) { + throw new NoAuthnContext( + Constants::STATUS_RESPONDER, + 'No authentication sources exist for the requested AuthnContextClassRefs: ' . implode(', ', $refs) + ); + } else if ($number_of_sources === 1) { + MultiAuth::delegateAuthentication($new_sources[0]['source'], $state); + } + } + // Save the $state array, so that we can restore if after a redirect $id = Auth\State::saveState($state, self::STAGEID); -- GitLab