Skip to content
Snippets Groups Projects
Commit c7a10f57 authored by Pavel Břoušek's avatar Pavel Břoušek
Browse files

chore: merge branch 'entropy' into 'main'

feat: user without sfa with mfa must perform mfa, without either will be redirected to new page

See merge request perun-proxy-aai/simplesamlphp/simplesamlphp-module-authswitcher!56
parents 13ba653b b07c5a3a
No related branches found
No related tags found
1 merge request!56feat: user without sfa with mfa must perform mfa, without either will be redirected to new page
Pipeline #283090 passed with warnings with stages
in 13 minutes and 34 seconds
......@@ -239,6 +239,8 @@ In configuration, you just need to add a `custom_attrs` option which contains a
## Password entropy check
Without check, it is assumed that user password fulfill REFEDS SFA. If the check should be performed, set `check_entropy` to `true`. Also set `sfa_alphabet_attr` and `sfa_len_attr` configuration options, which represent names of attributes in `$state`.
It is assumed that MFA always satisfies REFEDS SFA, that it contains TOTP, WebAuthn, or similar user verification. Therefore, there are two scenarios for a user with weak password. User with configured MFA must perform MFA, and user without configured MFA will be redirected to a page with additional information and with configurable button which leads to a password reset.
Use `change_weak_password_urls` to configure redirect button.
`sfa_alphabet` represents number of characters which can be used in password
......@@ -251,6 +253,10 @@ Without check, it is assumed that user password fulfill REFEDS SFA. If the check
'check_entropy' => true,
'sfa_alphabet_attr' => 'sfa_alphabet',
'sfa_len_attr' => 'sfa_len'
'change_weak_password_urls' => [
'en' => 'https://example.org/en/change_password',
'cs' => 'https://example.org/cs/zmena_hesla',
],
//...
]
]
......
{
"setup_mfa_text": {
"en": "To access service, you have to configure multi-factor authentication token.",
"cs": "K přístupu ke službě nejprve musíte nastavit token pro vícefázové ověření."
"en": "To access service, you have to configure multi-factor authentication token."
},
"manage_tokens_button": {
"en": "Continue",
"cs": "Pokračovat"
"en": "Continue"
}
}
{
"setup_mfa_text": {
"cs": "K přístupu ke službě nejprve musíte nastavit token pro vícefázové ověření."
},
"manage_tokens_button": {
"cs": "Pokračovat"
}
}
{
"change_weak_password_text": {
"en": "Your password is not strong enough and does not fulfill service requirements according to specification <a href=\"https://refeds.org/profile/sfa\">REFEDS SFA</a>."
},
"change_weak_password_button": {
"en": "Change password"
}
}
{
"change_weak_password_text": {
"cs": "Vaše heslo není dostatečně silné a nesplňuje požadavky služby podle specifikace <a href=\"https://refeds.org/profile/sfa\">REFEDS SFA</a>."
},
"change_weak_password_button": {
"cs": "Změnit heslo"
}
}
......@@ -64,9 +64,11 @@ class SwitchAuth extends \SimpleSAML\Auth\ProcessingFilter
private $check_entropy = false;
private $sfa_alphabet_attr;
private $sfa_alphabet_attr = 'sfaAlphabet';
private $sfa_len_attr;
private $sfa_len_attr = 'sfaLen';
private $change_weak_password_urls;
private $entityID;
......@@ -110,6 +112,8 @@ class SwitchAuth extends \SimpleSAML\Auth\ProcessingFilter
$this->mfa_excluded_sps = $config->getArray('mfa_excluded_sps', []);
$this->setup_mfa_redirect_url = $config->getString('setup_mfa_redirect_url', "");
$this->change_weak_password_urls = $config->getArray('change_weak_password_urls', []);
list($this->password_contexts, $this->mfa_contexts, $password_contexts_patterns, $mfa_contexts_patterns)
= ContextSettings::parseConfig($config);
......@@ -147,17 +151,21 @@ class SwitchAuth extends \SimpleSAML\Auth\ProcessingFilter
ProxyHelper::recoverSPRequestedContexts($state);
}
$weak_password = false;
$this->supported_requested_contexts = $this->authnContextHelper->getSupportedRequestedContexts(
$usersCapabilities,
$state,
$upstreamContext,
!$this->check_entropy || $this->checkSfaEntropy($state['Attributes'])
!$this->check_entropy || $this->checkSfaEntropy($state['Attributes']),
$weak_password,
$this->change_weak_password_urls
);
self::info('supported requested contexts: ' . json_encode($this->supported_requested_contexts));
$mustPerformMFA = $this->authnContextHelper->MFAin([$upstreamContext]) ? false
: ($mfaEnforced || empty($this->supported_requested_contexts));
: ($weak_password || $mfaEnforced || empty($this->supported_requested_contexts));
$shouldPerformMFA = $this->authnContextHelper->MFAin([$upstreamContext]) ? false
: ($mustPerformMFA || $this->authnContextHelper->isMFAprefered($this->supported_requested_contexts));
......@@ -246,10 +254,10 @@ class SwitchAuth extends \SimpleSAML\Auth\ProcessingFilter
return false;
}
if ($attributes[$this->sfa_alphabet_attr] >= 52 && $attributes[$this->sfa_len_attr] >= 12) {
if ($attributes[$this->sfa_alphabet_attr][0] >= 52 && $attributes[$this->sfa_len_attr][0] >= 12) {
return true;
}
if ($attributes[$this->sfa_alphabet_attr] >= 72 && $attributes[$this->sfa_len_attr] >= 8) {
if ($attributes[$this->sfa_alphabet_attr][0] >= 72 && $attributes[$this->sfa_len_attr][0] >= 8) {
return true;
}
......
......@@ -7,13 +7,23 @@ namespace SimpleSAML\Module\authswitcher;
use SAML2\Constants;
use SimpleSAML\Auth\State;
use SimpleSAML\Logger;
use SimpleSAML\Module;
use SimpleSAML\Module\saml\Error\NoAuthnContext;
use SimpleSAML\Utils\HTTP;
/**
* Authentication context handling.
*/
class AuthnContextHelper
{
private const WEAK_PASSWORD_URL = 'authswitcher:setup-mfa-tpl.php';
public const WEAK_PASSWORD_TPL_URL = 'authswitcher/weakPassword.php';
public const PARAM_CHANGE_WEAK_PASSWORD_URL = 'change_weak_password_url';
public const CHANGE_WEAK_PASSWORD_URL_ENABLED = 'change_weak_password_url_enabled';
public function __construct(
$password_contexts,
$mfa_contexts,
......@@ -46,7 +56,9 @@ class AuthnContextHelper
$usersCapabilities,
$state,
$upstreamContext,
$sfaEntropy
$sfaEntropy,
&$mustPerformMfa,
$change_weak_password_urls
) {
$requestedContexts = $state['saml:RequestedAuthnContext']['AuthnContextClassRef'] ?? null;
if (empty($requestedContexts)) {
......@@ -58,11 +70,26 @@ class AuthnContextHelper
$requestedContexts = $this->default_requested_contexts;
}
$supportedRequestedContexts = array_values(array_intersect($requestedContexts, $this->supported_contexts));
if (!$sfaEntropy) {
$supportedRequestedContexts = array_diff($supportedRequestedContexts, [Authswitcher::REFEDS_SFA]);
if (!$sfaEntropy && in_array(AuthSwitcher::REFEDS_SFA, $supportedRequestedContexts)) {
Logger::info(
'authswitcher: SFA password entropy level isn\'t satisfied. Remove SFA from SupportedRequestedContext.'
'authswitcher: SFA password entropy level is not satisfied.'
);
$supportedRequestedContexts = array_diff($supportedRequestedContexts, AuthSwitcher::PASSWORD_CONTEXTS);
if (empty($supportedRequestedContexts) && !$this->MFAin($usersCapabilities)) {
Logger::info(
'authswitcher: Redirecting user to info page with redirect to change password.'
);
$url = Module::getModuleURL(self::WEAK_PASSWORD_URL);
$state[self::PARAM_CHANGE_WEAK_PASSWORD_URL] = $change_weak_password_urls;
$stateId = State::saveState($state, 'authswitcher:authncontexthelper');
HTTP::redirectTrustedURL($url, ['stateId' => $stateId]);
} elseif ($this->MFAin($usersCapabilities)) {
$supportedRequestedContexts[] = AuthSwitcher::REFEDS_SFA;
$mustPerformMfa = true;
Logger::info(
'authswitcher: User must perform MFA.'
);
}
}
if (
......@@ -82,7 +109,7 @@ class AuthnContextHelper
$upstreamContext
);
if ($requestResult === null) {
Logger::info('authswitcher: comparsion type maxium is not supported');
Logger::info('authswitcher: comparsion type maximum is not supported');
self::noAuthnContextRequester($state);
} elseif ($requestResult === false) {
return [];
......
......@@ -12,9 +12,9 @@ class ContextSettings
$password_contexts = $config->getArray('password_contexts', AuthSwitcher::PASSWORD_CONTEXTS);
$mfa_contexts = $config->getArray('mfa_contexts', AuthSwitcher::MFA_CONTEXTS);
if ($contexts_regex) {
$password_contexts_patterns = array_filter(self::isRegex, $password_contexts);
$password_contexts_patterns = array_filter($password_contexts, self::isRegex);
$password_contexts = array_diff($password_contexts, $password_contexts_patterns);
$mfa_contexts_patterns = array_filter(self::isRegex, $mfa_contexts);
$mfa_contexts_patterns = array_filter($mfa_contexts, self::isRegex);
$mfa_contexts = array_diff($mfa_contexts, $mfa_contexts_patterns);
} else {
$password_contexts_patterns = [];
......
<?php
declare(strict_types=1);
use SimpleSAML\Module\authswitcher\AuthnContextHelper;
$this->includeAtTemplateBase('includes/header.php');
?>
<div class="row">
<div>
<p><?php echo $this->t('{authswitcher:password:change_weak_password_text}'); ?></p>
<?php if (!empty($this->data[AuthnContextHelper::CHANGE_WEAK_PASSWORD_URL_ENABLED])) { ?>
<a class="btn btn-lg btn-block btn-primary"
href="<?php echo $this->t(AuthnContextHelper::PARAM_CHANGE_WEAK_PASSWORD_URL); ?>">
<?php echo $this->t('{authswitcher:password:change_weak_password_button}'); ?>
</a>
<?php } ?>
</div>
</div>
<?php
$this->includeAtTemplateBase('includes/footer.php');
?>
<?php
declare(strict_types=1);
use SimpleSAML\Auth\State;
use SimpleSAML\Configuration;
use SimpleSAML\Module\authswitcher\AuthnContextHelper;
use SimpleSAML\XHTML\Template;
$config = Configuration::getInstance();
$t = new Template($config, AuthnContextHelper::WEAK_PASSWORD_TPL_URL);
if (empty($_GET['stateId'])) {
exit;
}
$t->data[AuthnContextHelper::CHANGE_WEAK_PASSWORD_URL_ENABLED] = false;
$state = State::loadState($_GET['stateId'], 'authswitcher:authncontexthelper', true);
$change_password_urls = $state[AuthnContextHelper::PARAM_CHANGE_WEAK_PASSWORD_URL];
if (!empty($change_password_urls)) {
$t->includeInlineTranslation(AuthnContextHelper::PARAM_CHANGE_WEAK_PASSWORD_URL, $change_password_urls);
$t->data[AuthnContextHelper::CHANGE_WEAK_PASSWORD_URL_ENABLED] = true;
}
$t->show();
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