Skip to content

Commit d31ca41

Browse files
committed
Introduce a second factor interface
This will allow the token fallback without the need of an entity.
1 parent 3767e04 commit d31ca41

File tree

14 files changed

+277
-179
lines changed

14 files changed

+277
-179
lines changed

src/Surfnet/StepupGateway/GatewayBundle/Controller/SecondFactorController.php

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@
3636
use Surfnet\StepupGateway\GatewayBundle\Form\Type\VerifySmsChallengeType;
3737
use Surfnet\StepupGateway\GatewayBundle\Form\Type\VerifyYubikeyOtpType;
3838
use Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext;
39+
use Surfnet\StepupGateway\GatewayBundle\Service\SecondFactor\SecondFactorInterface;
3940
use Surfnet\StepupGateway\GatewayBundle\Service\SecondFactorService;
4041
use Surfnet\StepupGateway\GatewayBundle\Sso2fa\CookieService;
4142
use Surfnet\StepupGateway\SamlStepupProviderBundle\Controller\SamlProxyController;
43+
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Service\Gateway\SecondfactorGsspFallback;
4244
use Symfony\Component\Form\FormError;
4345
use Symfony\Component\Form\FormInterface;
4446
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -123,7 +125,7 @@ public function selectSecondFactorForVerification(
123125
// Now read the SSO cookie
124126
$ssoCookie = $this->getCookieService()->read($request);
125127
// Test if the SSO cookie can satisfy the second factor authentication requirements
126-
if ($this->getCookieService()->maySkipAuthentication($requiredLoa->getLevel(), $identityNameId, $ssoCookie)) {
128+
if ($this->getCookieService()->maySkipAuthentication($requiredLoa->getLevel(), $identityNameId, $ssoCookie, $context)) {
127129
$logger->notice(
128130
'Skipping second factor authentication. Required LoA was met by the LoA recorded in the cookie',
129131
[
@@ -132,7 +134,7 @@ public function selectSecondFactorForVerification(
132134
],
133135
);
134136
// We use the SF from the cookie as the SF that was used for authenticating the second factor authentication
135-
$secondFactor = $this->getSecondFactorService()->findByUuid($ssoCookie->secondFactorId());
137+
$secondFactor = $this->getSecondFactorService()->findByUuid($ssoCookie->secondFactorId(), $context);
136138
$this->getResponseContext($authenticationMode)->saveSelectedSecondFactor($secondFactor);
137139
$this->getResponseContext($authenticationMode)->markSecondFactorVerified();
138140
$this->getResponseContext($authenticationMode)->markVerifiedBySsoOn2faCookie(
@@ -168,9 +170,8 @@ public function selectSecondFactorForVerification(
168170
// var_dump($requiredLoa);
169171
// die();
170172

171-
$secondFactor = SecondFactor::create('gssp_fallback', 'azuremfa', '');
173+
$secondFactor = SecondfactorGsspFallback::create('azuremfa', $request->getLocale());
172174
return $this->selectAndRedirectTo($secondFactor, $context, $authenticationMode);
173-
174175
}
175176

176177
return $this->forward(
@@ -356,25 +357,7 @@ public function verifyGssf(Request $request): Response
356357

357358
/** @var SecondFactorService $secondFactorService */
358359
$secondFactorService = $this->get('gateway.service.second_factor_service');
359-
/** @var SecondFactor $secondFactor */
360-
361-
if ($selectedSecondFactor === 'gssp_fallback') {
362-
// Also send the response context service id, as later we need to know if this is regular SSO or SFO authn.
363-
$responseContextServiceId = $context->getResponseContextServiceId();
364-
365-
return $this->forward(
366-
SamlProxyController::class . '::sendSecondFactorVerificationAuthnRequest',
367-
[
368-
'provider' => 'azuremfa',
369-
'subjectNameId' => '[email protected]',
370-
'responseContextServiceId' => $responseContextServiceId,
371-
'relayState' => $context->getRelayState(),
372-
],
373-
);
374-
375-
}
376-
377-
$secondFactor = $secondFactorService->findByUuid($selectedSecondFactor);
360+
$secondFactor = $secondFactorService->findByUuid($selectedSecondFactor, $context);
378361
if (!$secondFactor) {
379362
throw new RuntimeException(
380363
sprintf(
@@ -390,8 +373,8 @@ public function verifyGssf(Request $request): Response
390373
return $this->forward(
391374
SamlProxyController::class . '::sendSecondFactorVerificationAuthnRequest',
392375
[
393-
'provider' => $secondFactor->secondFactorType,
394-
'subjectNameId' => $secondFactor->secondFactorIdentifier,
376+
'provider' => $secondFactor->getSecondFactorType(),
377+
'subjectNameId' => $secondFactor->getSecondFactorIdentifier(),
395378
'responseContextServiceId' => $responseContextServiceId,
396379
'relayState' => $context->getRelayState(),
397380
],
@@ -412,20 +395,13 @@ public function gssfVerified(Request $request): Response
412395

413396
$selectedSecondFactor = $this->getSelectedSecondFactor($context, $logger);
414397

415-
if ($selectedSecondFactor === 'gssp_fallback') {
416-
// $this->getAuthenticationLogger()->logSecondFactorAuthentication($originalRequestId, $authenticationMode);
417-
$context->markSecondFactorVerified();
418-
419-
$logger->info(sprintf(
420-
'Marked GSSF "%s" as verified, forwarding to Gateway controller to respond',
421-
$selectedSecondFactor,
422-
));
423-
424-
return $this->forward($context->getResponseAction());
398+
if (!$context->isSecondFactorFallback()) {
399+
/** @var SecondFactor $secondFactor */
400+
$secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid($selectedSecondFactor);
401+
} else {
402+
$secondFactor = SecondfactorGsspFallback::create('azuremfa', $context->getSelectedLocale());
425403
}
426404

427-
/** @var SecondFactor $secondFactor */
428-
$secondFactor = $this->get('gateway.service.second_factor_service')->findByUuid($selectedSecondFactor);
429405
if (!$secondFactor) {
430406
throw new RuntimeException(
431407
sprintf(
@@ -743,16 +719,16 @@ private function getSelectedSecondFactor(ResponseContext $context, LoggerInterfa
743719
}
744720

745721
private function selectAndRedirectTo(
746-
SecondFactor $secondFactor,
722+
SecondFactorInterface $secondFactor,
747723
ResponseContext $context,
748724
$authenticationMode,
749725
): RedirectResponse {
750726
$context->saveSelectedSecondFactor($secondFactor);
751727

752-
$this->getStepupService()->clearSmsVerificationState($secondFactor->secondFactorId);
728+
$this->getStepupService()->clearSmsVerificationState($secondFactor->getSecondFactorId());
753729

754730
$secondFactorTypeService = $this->get('surfnet_stepup.service.second_factor_type');
755-
$secondFactorType = new SecondFactorType($secondFactor->secondFactorType);
731+
$secondFactorType = new SecondFactorType($secondFactor->getSecondFactorType());
756732

757733
$route = 'gateway_verify_second_factor_';
758734
if ($secondFactorTypeService->isGssf($secondFactorType)) {

src/Surfnet/StepupGateway/GatewayBundle/Entity/SecondFactor.php

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Surfnet\StepupBundle\Value\Loa;
2424
use Surfnet\StepupBundle\Value\SecondFactorType;
2525
use Surfnet\StepupBundle\Value\VettingType;
26+
use Surfnet\StepupGateway\GatewayBundle\Service\SecondFactor\SecondFactorInterface;
2627

2728
/**
2829
* WARNING: Any schema change made to this entity should also be applied to the Middleware SecondFactor entity!
@@ -37,7 +38,7 @@
3738
* }
3839
* )
3940
*/
40-
class SecondFactor
41+
class SecondFactor implements SecondFactorInterface
4142
{
4243
/**
4344
* @var int
@@ -118,15 +119,6 @@ final private function __construct()
118119
{
119120
}
120121

121-
public static function create(string $id, string $type, string $displayLocale)
122-
{
123-
$sf = new self();
124-
$sf->secondFactorId = $id;
125-
$sf->secondFactorType = $type;
126-
$sf->displayLocale = $displayLocale;
127-
return $sf;
128-
}
129-
130122
public function canSatisfy(Loa $loa, SecondFactorTypeService $service): bool
131123
{
132124
$secondFactorType = new SecondFactorType($this->secondFactorType);
@@ -153,4 +145,29 @@ private function determineVettingType(bool $identityVetted): VettingType
153145
}
154146
return new VettingType(VettingType::TYPE_SELF_ASSERTED_REGISTRATION);
155147
}
148+
149+
public function getSecondFactorId(): string
150+
{
151+
return $this->secondFactorId;
152+
}
153+
154+
public function getSecondFactorType(): string
155+
{
156+
return $this->secondFactorType;
157+
}
158+
159+
public function getDisplayLocale(): string
160+
{
161+
return $this->displayLocale;
162+
}
163+
164+
public function getSecondFactorIdentifier(): string
165+
{
166+
return $this->secondFactorIdentifier;
167+
}
168+
169+
public function getInstitution(): string
170+
{
171+
return $this->institution;
172+
}
156173
}

src/Surfnet/StepupGateway/GatewayBundle/EventListener/SecondFactorLocaleListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function getLocaleFromSelectedSecondFactor()
5858
return;
5959
}
6060

61-
$secondFactor = $this->secondFactorService->findByUuid($secondFactorId);
61+
$secondFactor = $this->secondFactorService->findByUuid($secondFactorId, $this->responseContext);
6262
if ($secondFactor) {
6363
return $secondFactor->displayLocale;
6464
}

src/Surfnet/StepupGateway/GatewayBundle/Monolog/Logger/AuthenticationLogger.php

Lines changed: 33 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -20,75 +20,36 @@
2020

2121
use DateTime;
2222
use Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger;
23-
use Surfnet\StepupBundle\Service\LoaResolutionService;
24-
use Surfnet\StepupBundle\Service\SecondFactorTypeService;
25-
use Surfnet\StepupBundle\Value\Loa;
2623
use Surfnet\StepupGateway\GatewayBundle\Exception\InvalidArgumentException;
27-
use Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler;
24+
use Surfnet\StepupGateway\GatewayBundle\Saml\ResponseContext;
2825
use Surfnet\StepupGateway\GatewayBundle\Service\SecondFactorService;
2926

3027
class AuthenticationLogger
3128
{
32-
/**
33-
* @var ProxyStateHandler
34-
*/
35-
private $ssoProxyStateHandler;
36-
37-
/**
38-
* @var ProxyStateHandler
39-
*/
40-
private $sfoProxyStateHandler;
4129

4230
/**
4331
* @var SecondFactorService
4432
*/
4533
private $secondFactorService;
4634

47-
/**
48-
* @var LoaResolutionService
49-
*/
50-
private $loaResolutionService;
51-
5235
/**
5336
* @var SamlAuthenticationLogger
5437
*/
5538
private $authenticationChannelLogger;
39+
private ResponseContext $sfoResponseContext;
40+
private ResponseContext $ssoResponseContext;
5641

57-
/**
58-
* @var SecondFactorTypeService
59-
*/
60-
private $secondFactorTypeService;
6142

6243
public function __construct(
63-
LoaResolutionService $loaResolutionService,
64-
ProxyStateHandler $ssoProxyStateHandler,
65-
ProxyStateHandler $sfoProxyStateHandler,
6644
SecondFactorService $secondFactorService,
6745
SamlAuthenticationLogger $authenticationChannelLogger,
68-
SecondFactorTypeService $service
46+
ResponseContext $sfoResponseContext,
47+
ResponseContext $ssoResponseContext,
6948
) {
70-
$this->loaResolutionService = $loaResolutionService;
71-
$this->ssoProxyStateHandler = $ssoProxyStateHandler;
72-
$this->sfoProxyStateHandler = $sfoProxyStateHandler;
7349
$this->secondFactorService = $secondFactorService;
7450
$this->authenticationChannelLogger = $authenticationChannelLogger;
75-
$this->secondFactorTypeService = $service;
76-
}
77-
78-
/**
79-
* @param string $requestId The SAML authentication request ID of the original request (not the proxy request).
80-
*/
81-
public function logIntrinsicLoaAuthentication($requestId): void
82-
{
83-
$context = [
84-
'second_factor_id' => '',
85-
'second_factor_type' => '',
86-
'institution' => '',
87-
'authentication_result' => 'NONE',
88-
'resulting_loa' => (string) $this->loaResolutionService->getLoaByLevel(Loa::LOA_1),
89-
];
90-
91-
$this->log('Intrinsic Loa Requested', $context, $requestId);
51+
$this->sfoResponseContext = $sfoResponseContext;
52+
$this->ssoResponseContext = $ssoResponseContext;
9253
}
9354

9455
/**
@@ -97,62 +58,53 @@ public function logIntrinsicLoaAuthentication($requestId): void
9758
*/
9859
public function logSecondFactorAuthentication(string $requestId, string $authenticationMode): void
9960
{
100-
$stateHandler = $this->getStateHandler($authenticationMode);
101-
$secondFactor = $this->secondFactorService->findByUuid($stateHandler->getSelectedSecondFactorId());
102-
$loa = $this->loaResolutionService->getLoaByLevel($secondFactor->getLoaLevel($this->secondFactorTypeService));
103-
104-
$context = [
105-
'second_factor_id' => $secondFactor->secondFactorId,
106-
'second_factor_type' => $secondFactor->secondFactorType,
107-
'institution' => $secondFactor->institution,
108-
'authentication_result' => $stateHandler->isSecondFactorVerified() ? 'OK' : 'FAILED',
61+
$context = $this->getResponseContext($authenticationMode);
62+
63+
$secondFactor = $this->secondFactorService->findByUuid($context->getSelectedSecondFactor(), $context);
64+
$loa = $this->secondFactorService->getLoaLevel($secondFactor);
65+
66+
$data = [
67+
'second_factor_id' => $secondFactor->getSecondFactorId(),
68+
'second_factor_type' => $secondFactor->getSecondFactorType(),
69+
'institution' => $secondFactor->getInstitution(),
70+
'authentication_result' => $context->isSecondFactorVerified() ? 'OK' : 'FAILED',
10971
'resulting_loa' => (string) $loa,
110-
'sso' => $stateHandler->isVerifiedBySsoOn2faCookie() ? 'YES': 'NO',
72+
'sso' => $context->isVerifiedBySsoOn2faCookie() ? 'YES': 'NO',
11173
];
11274

113-
if ($stateHandler->isVerifiedBySsoOn2faCookie()) {
114-
$context['sso_cookie_id'] = $stateHandler->getSsoOn2faCookieFingerprint();
75+
if ($context->isVerifiedBySsoOn2faCookie()) {
76+
$context['sso_cookie_id'] = $context->getSsoOn2faCookieFingerprint();
11577
}
11678

117-
$this->log('Second Factor Authenticated', $context, $requestId);
79+
$this->log('Second Factor Authenticated', $data, $requestId, $authenticationMode);
11880
}
11981

12082
/**
12183
* @param string $message
122-
* @param array $context
84+
* @param array $data
12385
* @param string $requestId
12486
*/
125-
private function log($message, array $context, $requestId): void
87+
private function log(string $message, array $data, string $requestId, string $authenticationMode): void
12688
{
127-
if (!is_string($requestId)) {
128-
throw InvalidArgumentException::invalidType('string', 'requestId', $requestId);
129-
}
130-
// Regardless of authentication type, the authentication mode can be retrieved from any state handler
131-
// given you provide the request id
132-
$authenticationMode = $this->getStateHandler('sso')->getAuthenticationModeForRequestId($requestId);
133-
$stateHandler = $this->getStateHandler($authenticationMode);
89+
$context = $this->getResponseContext($authenticationMode);
13490

135-
$context['identity_id'] = $stateHandler->getIdentityNameId();
136-
$context['authenticating_idp'] = $stateHandler->getAuthenticatingIdp();
137-
$context['requesting_sp'] = $stateHandler->getRequestServiceProvider();
138-
$context['datetime'] = (new DateTime())->format('Y-m-d\\TH:i:sP');
91+
$data['identity_id'] = $context->getIdentityNameId();
92+
$data['authenticating_idp'] = $context->getAuthenticatingIdp();
93+
$data['requesting_sp'] = $context->getRequestServiceProvider();
94+
$data['datetime'] = (new DateTime())->format('Y-m-d\\TH:i:sP');
13995

140-
$this->authenticationChannelLogger->forAuthentication($requestId)->notice($message, $context);
96+
$this->authenticationChannelLogger->forAuthentication($requestId)->notice($message, $data);
14197
}
14298

143-
/**
144-
* @param string $authenticationMode
145-
* @return ProxyStateHandler
146-
*/
147-
private function getStateHandler($authenticationMode)
99+
private function getResponseContext(string $authenticationMode): ResponseContext
148100
{
149101
if ($authenticationMode === 'sfo') {
150-
return $this->sfoProxyStateHandler;
102+
return $this->sfoResponseContext;
151103
} elseif ($authenticationMode === 'sso') {
152-
return $this->ssoProxyStateHandler;
104+
return $this->ssoResponseContext;
153105
}
154106
throw new InvalidArgumentException(
155-
sprintf('Retrieving a state handler for authentication type %s is not supported', $authenticationMode)
107+
sprintf('Retrieving a response context for authentication type %s is not supported', $authenticationMode)
156108
);
157109
}
158110
}

src/Surfnet/StepupGateway/GatewayBundle/Resources/config/services.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ services:
7373
class: Surfnet\StepupGateway\GatewayBundle\Service\SecondFactorService
7474
arguments:
7575
- "@gateway.repository.second_factor"
76+
- "@surfnet_stepup.service.loa_resolution"
77+
- "@surfnet_stepup.service.second_factor_type"
7678

7779
gateway.service.response_proxy:
7880
class: Surfnet\StepupGateway\GatewayBundle\Service\ProxyResponseService
@@ -172,12 +174,10 @@ services:
172174
gateway.authentication_logger:
173175
class: Surfnet\StepupGateway\GatewayBundle\Monolog\Logger\AuthenticationLogger
174176
arguments:
175-
- "@surfnet_stepup.service.loa_resolution"
176-
- "@gateway.proxy.sso.state_handler"
177-
- "@gateway.proxy.sfo.state_handler"
178177
- "@gateway.service.second_factor_service"
179178
- "@gateway.authentication_logger.logger"
180-
- "@surfnet_stepup.service.second_factor_type"
179+
- "@second_factor_only.response_context"
180+
- "@gateway.proxy.response_context"
181181

182182
gateway.authentication_logger.logger:
183183
class: Surfnet\SamlBundle\Monolog\SamlAuthenticationLogger

0 commit comments

Comments
 (0)