Skip to content
This repository was archived by the owner on Sep 19, 2022. It is now read-only.

Commit 4d9b52a

Browse files
author
Dominik František Bučík
authored
Merge pull request #206 from dBucik/acrs_modification
feat: 🎸 Added RestoreAcrs authproc filter, modify ACRs when MFA
2 parents 50848e9 + ae0e53d commit 4d9b52a

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\perun\Auth\Process;
6+
7+
use SimpleSAML\Auth\ProcessingFilter;
8+
use SimpleSAML\Logger;
9+
use SimpleSAML\Module\perun\Disco;
10+
11+
/**
12+
* Auth proc filter, which should be used if Proxy provides MFA, and wants to first pass the request to the upstream
13+
* IdP. The Perun Disco page, tries to add new ACRs to the request, if MFA has been requested, to prevent the IdP from
14+
* failing. As a result, we then get modified requested ACRs, which should be restored to the previous (original) state
15+
* using this authproc filter. It should be run on one of the first places of the IdP authproc chain.
16+
*/
17+
class MultifactorAcrs extends ProcessingFilter
18+
{
19+
public const CONFIG_FILE_NAME = 'module_perun.php';
20+
21+
private const DEBUG_PREFIX = 'perun:RestoreAcrs';
22+
23+
public function __construct($config, $reserved)
24+
{
25+
parent::__construct($config, $reserved);
26+
}
27+
28+
public function process(&$request)
29+
{
30+
$this->restoreAcrs($request);
31+
}
32+
33+
public static function addAndStoreAcrs(array &$state, array $acrsToAdd)
34+
{
35+
if (!empty($acrsToAdd)
36+
&& !empty($state[Disco::SAML_REQUESTED_AUTHN_CONTEXT][Disco::STATE_AUTHN_CONTEXT_CLASS_REF])
37+
&& sizeof($state[Disco::SAML_REQUESTED_AUTHN_CONTEXT][Disco::STATE_AUTHN_CONTEXT_CLASS_REF]) <= 1
38+
&& in_array(
39+
Disco::MFA_PROFILE,
40+
$state[Disco::SAML_REQUESTED_AUTHN_CONTEXT][Disco::STATE_AUTHN_CONTEXT_CLASS_REF],
41+
true
42+
)
43+
) {
44+
Logger::debug(
45+
self::DEBUG_PREFIX . ': Modifying ACRs list to pass regular authentication to the IdP to fallback to proxy MFA'
46+
);
47+
$original = [];
48+
$new = [];
49+
foreach ($state[Disco::SAML_REQUESTED_AUTHN_CONTEXT][Disco::STATE_AUTHN_CONTEXT_CLASS_REF] as $acr) {
50+
$original[] = $acr;
51+
$new[] = $acr;
52+
}
53+
foreach ($acrsToAdd as $acr) {
54+
$new[] = $acr;
55+
}
56+
$new = array_unique($new);
57+
58+
unset($state[Disco::SAML_REQUESTED_AUTHN_CONTEXT][Disco::STATE_AUTHN_CONTEXT_CLASS_REF]);
59+
if (isset($state[Disco::SAML_REQUESTED_AUTHN_CONTEXT_ORIGINAL])) {
60+
unset($state[Disco::SAML_REQUESTED_AUTHN_CONTEXT_ORIGINAL]);
61+
}
62+
Logger::debug(self::DEBUG_PREFIX . ': original ACRs: ' . join(',', $original));
63+
$state[Disco::SAML_REQUESTED_AUTHN_CONTEXT_ORIGINAL] = $original;
64+
Logger::debug(self::DEBUG_PREFIX . ': ACRs after modification: ' . join(',', $new));
65+
$state[Disco::SAML_REQUESTED_AUTHN_CONTEXT][Disco::STATE_AUTHN_CONTEXT_CLASS_REF] = $new;
66+
}
67+
}
68+
69+
private function restoreAcrs(&$request)
70+
{
71+
if (!empty($request[Disco::SAML_REQUESTED_AUTHN_CONTEXT_ORIGINAL])) {
72+
unset($request[Disco::SAML_REQUESTED_AUTHN_CONTEXT][Disco::STATE_AUTHN_CONTEXT_CLASS_REF]);
73+
$request[Disco::SAML_REQUESTED_AUTHN_CONTEXT][Disco::STATE_AUTHN_CONTEXT_CLASS_REF] =
74+
$request[Disco::SAML_REQUESTED_AUTHN_CONTEXT_ORIGINAL];
75+
unset($request[Disco::SAML_REQUESTED_AUTHN_CONTEXT_ORIGINAL]);
76+
Logger::debug(
77+
self::DEBUG_PREFIX . ': ACRS restored: '
78+
. join(',', $request[Disco::SAML_REQUESTED_AUTHN_CONTEXT][Disco::STATE_AUTHN_CONTEXT_CLASS_REF])
79+
);
80+
}
81+
}
82+
}

lib/Disco.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use SimpleSAML\Logger;
1111
use SimpleSAML\Module;
1212
use SimpleSAML\Module\discopower\PowerIdPDisco;
13+
use SimpleSAML\Module\perun\Auth\Process\MultifactorAcrs;
1314
use SimpleSAML\Module\perun\model\WarningConfiguration;
1415
use SimpleSAML\Utils\HTTP;
1516

@@ -30,6 +31,8 @@ class Disco extends PowerIdPDisco
3031

3132
public const DEFAULT_THEME = 'perun';
3233

34+
public const MFA_PROFILE = 'https://refeds.org/profile/mfa';
35+
3336
// ROOT CONFIGURATION ENTRY
3437
public const WAYF = 'wayf_config';
3538

@@ -48,6 +51,8 @@ class Disco extends PowerIdPDisco
4851

4952
public const DISPLAY_SP = 'display_sp_name';
5053

54+
public const ADD_AUTHN_CONTEXT_CLASSES_FOR_MFA = 'add_authn_context_classes_for_mfa';
55+
5156
// CONFIGURATION ENTRIES IDP BLOCKS
5257
public const IDP_BLOCKS = 'idp_blocks_config';
5358

@@ -123,6 +128,8 @@ class Disco extends PowerIdPDisco
123128

124129
public const SAML_REQUESTED_AUTHN_CONTEXT = 'saml:RequestedAuthnContext';
125130

131+
public const SAML_REQUESTED_AUTHN_CONTEXT_ORIGINAL = 'saml:RequestedAuthnContext:original';
132+
126133
public const STATE_AUTHN_CONTEXT_CLASS_REF = 'AuthnContextClassRef';
127134

128135
public const SAML_SP_SSO = 'saml:sp:sso';
@@ -186,6 +193,7 @@ public function __construct(array $metadataSets, $instance)
186193
$this->originalAuthnContextClassRef = $state[self::SAML_REQUESTED_AUTHN_CONTEXT][self::AUTHN_CONTEXT_CLASS_REF];
187194

188195
$this->removeAuthContextClassRefWithPrefixes($state);
196+
$this->prepareAcrsForMfa($state);
189197
if (isset($state['IdPMetadata']['entityid'])) {
190198
$this->proxyIdpEntityId = $state['IdPMetadata']['entityid'];
191199
}
@@ -980,4 +988,10 @@ private function fillSpNameForSaml($t)
980988
$this->spName = $t->translate->getTranslation($this->originalsp[self::NAME]);
981989
}
982990
}
991+
992+
private function prepareAcrsForMfa(array &$state)
993+
{
994+
$contextsToAdd = $this->wayfConfiguration->getArray(self::ADD_AUTHN_CONTEXT_CLASSES_FOR_MFA, []);
995+
MultifactorAcrs::addAndStoreAcrs($state, $contextsToAdd);
996+
}
983997
}

0 commit comments

Comments
 (0)