diff --git a/development/idp-local/config/authsources.php b/development/idp-local/config/authsources.php index 2fb95e1b..a35db772 100644 --- a/development/idp-local/config/authsources.php +++ b/development/idp-local/config/authsources.php @@ -11,7 +11,7 @@ 'core:AdminPassword', ], - + // Set up example users for testing expirychecker module. 'example-userpass' => [ 'exampleauth:UserPass', @@ -448,6 +448,13 @@ 'last_used_utc' => null, 'data' => [ // Response from "POST /webauthn/login" MFA API call. + "id" => 88, + "label" => "My Webauthn Key", + "last_used_utc" => null, + "created_utc" => "2022-12-15 19:42:37", + "publicKey" => [ + "challenge" => "xxxxxxx", + ], ], ], ] diff --git a/features/bootstrap/MfaContext.php b/features/bootstrap/MfaContext.php index 988f1fb6..664b1590 100644 --- a/features/bootstrap/MfaContext.php +++ b/features/bootstrap/MfaContext.php @@ -208,7 +208,7 @@ protected function submitMfaValue($mfaValue) public function iSubmitACorrectBackupCode() { if (! $this->pageContainsElementWithText('h1', 'Printable code')) { - // find image of the backup code option presented in other_mfas.php + // find image of the backup code option presented in other_mfas.twig $printableCodeOption = $this->session->getPage()->find('css', 'img[src=mfa-backupcode\002Esvg]'); $printableCodeOption->click(); } diff --git a/modules/material/themes/material/default/other_mfas.twig b/modules/material/themes/material/default/other_mfas.twig new file mode 100644 index 00000000..f333cddf --- /dev/null +++ b/modules/material/themes/material/default/other_mfas.twig @@ -0,0 +1,27 @@ +{% if otherOptions|length > 0 %} +
+ {# used type=button to avoid form submission on click since this is just used to display the ul #} + + +
+{% endif %} diff --git a/modules/material/themes/material/mfa/other_mfas.php b/modules/material/themes/material/mfa/other_mfas.php deleted file mode 100644 index 7d466a59..00000000 --- a/modules/material/themes/material/mfa/other_mfas.php +++ /dev/null @@ -1,34 +0,0 @@ -data['otherOptions']; -if (count($otherOptions) > 0) { -?> -
- - - -
- diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-backupcode.php b/modules/material/themes/material/mfa/prompt-for-mfa-backupcode.php deleted file mode 100644 index 7520467a..00000000 --- a/modules/material/themes/material/mfa/prompt-for-mfa-backupcode.php +++ /dev/null @@ -1,92 +0,0 @@ - - - - <?= $this->t('{material:mfa:title}') ?> - - - - -
-
-
- - t('{material:mfa:header}') ?> - -
-
-
-
-
-
- <?= $this->t('{material:mfa:backupcode_icon}') ?> -
- -
-

- t('{material:mfa:backupcode_header}') ?> -

-
- -
-

- t('{material:mfa:backupcode_reminder}') ?> -

-
- -
-
- - -
-
- - data['errorMessage']; - - if (! empty($message)) { - ?> -
-

- error - - - - -

-
- - - - -
- - -
- - -
- -
- -
-
-
-
- - diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-backupcode.twig b/modules/material/themes/material/mfa/prompt-for-mfa-backupcode.twig new file mode 100644 index 00000000..a43b1783 --- /dev/null +++ b/modules/material/themes/material/mfa/prompt-for-mfa-backupcode.twig @@ -0,0 +1,83 @@ + + + + {{ '{mfa:title}'|trans }} + + {% include 'header.twig' %} + + +
+
+
+ + {{ '{mfa:header}'|trans }} + +
+
+
+
+
+
+ {{ '{mfa:backupcode_icon}'|trans }} +
+ +
+

+ {{ '{mfa:backupcode_header}'|trans }} +

+
+ +
+

+ {{ '{mfa:backupcode_reminder}'|trans }} +

+
+ +
+
+ + +
+
+ + {% if not errorMessage is empty %} +
+

+ error + + + {{ errorMessage|e }} + +

+
+ + + {% endif %} + +
+ + +
+ + {% include 'other_mfas.twig' %} +
+ +
+ +
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-manager.php b/modules/material/themes/material/mfa/prompt-for-mfa-manager.php deleted file mode 100644 index e9899947..00000000 --- a/modules/material/themes/material/mfa/prompt-for-mfa-manager.php +++ /dev/null @@ -1,89 +0,0 @@ - - - - <?= $this->t('{material:mfa:title}') ?> - - - - -
-
-
- - t('{material:mfa:header}') ?> - -
-
-
-
-
-
- <?= $this->t('{material:mfa:manager_icon}') ?> -
- -
-

- t('{material:mfa:manager_header}') ?> -

-
- -
-

- t('{material:mfa:manager_sent}', ['{managerEmail}' => $this->data['managerEmail']]) ?> -

-
- -
-
- - -
-
- - data['errorMessage']; - - if (! empty($message)) { - ?> -
-

- error - - - - -

-
- - - - -
- - -
- - -
- -
- -
-
-
-
- - diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-manager.twig b/modules/material/themes/material/mfa/prompt-for-mfa-manager.twig new file mode 100644 index 00000000..dfe30757 --- /dev/null +++ b/modules/material/themes/material/mfa/prompt-for-mfa-manager.twig @@ -0,0 +1,83 @@ + + + + {{ '{mfa:title}'|trans }} + + {% include 'header.twig' %} + + +
+
+
+ + {{ '{mfa:header}'|trans }} + +
+
+
+
+
+
+ {{ '{mfa:manager_icon}'|trans }} +
+ +
+

+ {{ '{mfa:manager_header}'|trans }} +

+
+ +
+

+ {{ '{mfa:manager_sent}'|trans({'{managerEmail': managerEmail}) }} +

+
+ +
+
+ + +
+
+ + {% if not errorMessage is empty %} +
+

+ error + + + {{ errorMessage|e }} + +

+
+ + + {% endif %} + +
+ + +
+ + {% include 'other_mfas.twig' %} +
+ +
+ +
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-totp.php b/modules/material/themes/material/mfa/prompt-for-mfa-totp.php deleted file mode 100644 index b0d10248..00000000 --- a/modules/material/themes/material/mfa/prompt-for-mfa-totp.php +++ /dev/null @@ -1,93 +0,0 @@ - - - - <?= $this->t('{material:mfa:title}') ?> - - - - -
-
-
- - t('{material:mfa:header}') ?> - -
-
-
-
-
-
- <?= $this->t('{material:mfa:totp_icon}') ?> -
- -
-

- t('{material:mfa:totp_header}') ?> -

-
- -
- configuration->getValue('idp_display_name', $this->configuration->getValue('idp_name', '—'))); - echo $this->t('{material:mfa:account}', ['{idpName}' => $idpName]); - ?> -
- -
-
- - -
-
- - data['errorMessage']; - - if (! empty($message)) { - ?> -
-

- error - - - - -

-
- - - - -
- - -
- - -
- -
- -
-
-
-
- - diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-totp.twig b/modules/material/themes/material/mfa/prompt-for-mfa-totp.twig new file mode 100644 index 00000000..7fd9a92e --- /dev/null +++ b/modules/material/themes/material/mfa/prompt-for-mfa-totp.twig @@ -0,0 +1,81 @@ + + + + {{ '{mfa:title}'|trans }} + + {% include 'header.twig' %} + + +
+
+
+ + {{ '{mfa:header}'|trans }} + +
+
+
+
+
+
+ {{ '{mfa:totp_icon}'|trans }} +
+ +
+

+ {{ '{mfa:totp_header}'|trans }} +

+
+ +
+ {{ '{mfa:account}'|trans({'%idpName%': idpName}) }} +
+ +
+
+ + +
+
+ + {% if not errorMessage is empty %} +
+

+ error + + + {{ errorMessage|e }} + +

+
+ + + {% endif %} + +
+ + +
+ + {% include 'other_mfas.twig' %} +
+ +
+ +
+
+
+
+ + diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php b/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php deleted file mode 100644 index 02fa6ff4..00000000 --- a/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.php +++ /dev/null @@ -1,153 +0,0 @@ - - - - <?= $this->t('{material:mfa:title}') ?> - - - - - - - - -data['supportsWebAuthn']; ?> - - -
-
-
- - t('{material:mfa:header}') ?> - -
-
- -
-
-
-
- <?= $this->t('{material:mfa:webauthn_icon}') ?> -
- -
-

- t('{material:mfa:webauthn_header}') ?> -

-
- - -
-

- t('{material:mfa:webauthn_instructions}') ?> -

-
- -
-

- t('{material:mfa:webauthn_unsupported}') ?> -

-
- - - data['errorMessage']; - if (! empty($message)) { - ?> - - -
-

- error - - - - -

-
- -
- - - - -
- - -
- -
- -
-
-
-
- - diff --git a/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.twig b/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.twig new file mode 100644 index 00000000..327d5ba1 --- /dev/null +++ b/modules/material/themes/material/mfa/prompt-for-mfa-webauthn.twig @@ -0,0 +1,148 @@ + + + + {{ '{mfa:title}'|trans }} + + {% include 'header.twig' %} + + + + + + + +
+
+
+ + {{ '{mfa:header}'|trans }} + +
+
+ +
+
+
+
+ {{ '{mfa:webauthn_icon}'|trans }} +
+ +
+

+ {{ '{mfa:webauthn_header}'|trans }} +

+
+ + {% if supportsWebAuthn %} +
+

+ {{ '{mfa:webauthn_instructions}'|trans }} +

+
+ {% else %} +
+

+ {{ '{mfa:webauthn_unsupported}'|trans|raw }} +

+
+ {% endif %} + + {% if not errorMessage is empty %} + + {% endif %} +
+

+ error + + + {{ errorMessage|e }} + +

+
+ +
+ + + + +
+ + {% include 'other_mfas.twig' %} +
+ +
+ +
+
+
+
+ + diff --git a/modules/mfa/public/prompt-for-mfa.php b/modules/mfa/public/prompt-for-mfa.php index 53b8bd32..c3a52f96 100644 --- a/modules/mfa/public/prompt-for-mfa.php +++ b/modules/mfa/public/prompt-for-mfa.php @@ -53,7 +53,7 @@ 'event' => 'MFA ID missing in URL. Choosing one and doing a redirect.', 'employeeId' => $state['employeeId'], ])); - + // Pick an MFA ID and do a redirect to put that into the URL. $mfaOption = Mfa::getMfaOptionToUse($mfaOptions, $userAgent); $moduleUrl = SimpleSAML\Module::getModuleURL('mfa/prompt-for-mfa.php', [ @@ -75,7 +75,7 @@ } $rememberMe = filter_input(INPUT_POST, 'rememberMe') ?? false; - + // NOTE: This will only return if validation fails. $errorMessage = Mfa::validateMfaSubmission( $mfaId, @@ -87,7 +87,7 @@ $mfaOption['type'], $state['rpOrigin'] ); - + $logger->warning(json_encode([ 'event' => 'MFA validation result: failed', 'employeeId' => $state['employeeId'], @@ -122,6 +122,7 @@ $t = new Template($globalConfig, $mfaTemplateToUse); $t->data['errorMessage'] = $errorMessage ?? null; $t->data['mfaOption'] = $mfaOption; +$t->data['mfaOptionData'] = json_encode($mfaOption['data']); $t->data['mfaOptions'] = $mfaOptions; $t->data['stateId'] = $stateId; $t->data['supportsWebAuthn'] = LoginBrowser::supportsWebAuthn($userAgent); @@ -129,7 +130,8 @@ $t->data['browserJsPath'] = '/module.php/mfa/simplewebauthn/browser.js?v=' . $browserJsHash; $t->data['managerEmail'] = $state['managerEmail']; $t->data['otherOptions'] = $otherOptions; -$t->show(); +$t->data['idpName'] = $globalConfig->getString('idp_display_name'); +$t->send(); $logger->info(json_encode([ 'event' => 'Prompted user for MFA', diff --git a/modules/mfa/src/Auth/Process/Mfa.php b/modules/mfa/src/Auth/Process/Mfa.php index 8e6415ac..51c2ce60 100644 --- a/modules/mfa/src/Auth/Process/Mfa.php +++ b/modules/mfa/src/Auth/Process/Mfa.php @@ -297,10 +297,10 @@ public static function getNumBackupCodesUserHad(array $mfaOptions): int public static function getTemplateFor(string $mfaType): string { $mfaOptionTemplates = [ - 'backupcode' => 'mfa:prompt-for-mfa-backupcode.php', - 'totp' => 'mfa:prompt-for-mfa-totp.php', - 'webauthn' => 'mfa:prompt-for-mfa-webauthn.php', - 'manager' => 'mfa:prompt-for-mfa-manager.php', + 'backupcode' => 'mfa:prompt-for-mfa-backupcode', + 'totp' => 'mfa:prompt-for-mfa-totp', + 'webauthn' => 'mfa:prompt-for-mfa-webauthn', + 'manager' => 'mfa:prompt-for-mfa-manager', ]; $template = $mfaOptionTemplates[$mfaType] ?? null;