diff --git a/config/services_test.yaml b/config/services_test.yaml index 63a27b78..4ebae938 100644 --- a/config/services_test.yaml +++ b/config/services_test.yaml @@ -21,3 +21,9 @@ services: public: true arguments: $filePath: '/var/www/html/var/gssp_store.json' + + Surfnet\Tiqr\Features\Context\TiqrContext: + autowire: true + autoconfigure: true + arguments: + $store: '@surfnet_gssp.value_store.service' \ No newline at end of file diff --git a/src/Controller/AuthenticationController.php b/src/Controller/AuthenticationController.php index 635bf2b2..3174213c 100644 --- a/src/Controller/AuthenticationController.php +++ b/src/Controller/AuthenticationController.php @@ -30,6 +30,7 @@ use Surfnet\Tiqr\Exception\UserPermanentlyBlockedException; use Surfnet\Tiqr\Exception\UserTemporarilyBlockedException; use Surfnet\Tiqr\Service\SessionCorrelationIdService; +use Surfnet\Tiqr\Service\TrustedDeviceHelper; use Surfnet\Tiqr\Tiqr\AuthenticationRateLimitServiceInterface; use Surfnet\Tiqr\Tiqr\Exception\UserNotExistsException; use Surfnet\Tiqr\Tiqr\Response\AuthenticationResponse; @@ -42,6 +43,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Throwable; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -55,6 +57,7 @@ public function __construct( private readonly TiqrUserRepositoryInterface $userRepository, private readonly AuthenticationRateLimitServiceInterface $authenticationRateLimitService, private readonly SessionCorrelationIdService $correlationIdService, + private readonly TrustedDeviceHelper $trustedDeviceHelper, private readonly LoggerInterface $logger ) { } @@ -122,7 +125,10 @@ public function __invoke(Request $request): Response $logger->info('Authentication is finalized, returning to SP'); $this->authenticationService->authenticate(); - return $this->authenticationService->replyToServiceProvider(); + $response = $this->authenticationService->replyToServiceProvider(); + $this->trustedDeviceHelper->handleRegisterTrustedDevice($user->getNotificationAddress(), $response); + + return $response; } // Start authentication process. diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php index 69f7b1ad..308df571 100644 --- a/src/Controller/RegistrationController.php +++ b/src/Controller/RegistrationController.php @@ -26,20 +26,28 @@ use Surfnet\Tiqr\Attribute\RequiresActiveSession; use Surfnet\Tiqr\Exception\NoActiveAuthenrequestException; use Surfnet\Tiqr\Service\SessionCorrelationIdService; +use Surfnet\Tiqr\Service\TrustedDeviceHelper; use Surfnet\Tiqr\Tiqr\Legacy\TiqrService; use Surfnet\Tiqr\Tiqr\TiqrServiceInterface; +use Surfnet\Tiqr\Tiqr\TiqrUserRepositoryInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Throwable; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class RegistrationController extends AbstractController { public function __construct( private readonly RegistrationService $registrationService, + private readonly TiqrUserRepositoryInterface $userRepository, private readonly StateHandlerInterface $stateHandler, private readonly TiqrServiceInterface $tiqrService, private readonly SessionCorrelationIdService $correlationIdService, + private readonly TrustedDeviceHelper $trustedDeviceHelper, private readonly LoggerInterface $logger ) { } @@ -66,8 +74,19 @@ public function registration(Request $request): Response if ($this->tiqrService->enrollmentFinalized()) { $this->logger->info('Registration is finalized returning to service provider'); - $this->registrationService->register($this->tiqrService->getUserId()); - return $this->registrationService->replyToServiceProvider(); + $userId = $this->tiqrService->getUserId(); + $this->registrationService->register($userId); + + $response = $this->registrationService->replyToServiceProvider(); + + try { + $user = $this->userRepository->getUser($userId); + $this->trustedDeviceHelper->handleRegisterTrustedDevice($user->getNotificationAddress(), $response); + } catch (Throwable $e) { + $this->logger->warning(sprintf('Could not fetch registered user "%s"', $userId), ['exception' => $e]); + } + + return $response; } $this->logger->info('Registration is not finalized return QR code'); diff --git a/src/Controller/TiqrAppApiController.php b/src/Controller/TiqrAppApiController.php index cf8bc063..063b287d 100644 --- a/src/Controller/TiqrAppApiController.php +++ b/src/Controller/TiqrAppApiController.php @@ -22,12 +22,10 @@ use Exception; use Psr\Log\LoggerInterface; -use Surfnet\Tiqr\Service\TrustedDevice\TrustedDeviceService; use Surfnet\Tiqr\Service\UserAgentMatcherInterface; use Surfnet\Tiqr\Tiqr\AuthenticationRateLimitServiceInterface; use Surfnet\Tiqr\Tiqr\Exception\UserNotExistsException; use Surfnet\Tiqr\Tiqr\TiqrServiceInterface; -use Surfnet\Tiqr\Tiqr\TiqrUserInterface; use Surfnet\Tiqr\Tiqr\TiqrUserRepositoryInterface; use Surfnet\Tiqr\WithContextLogger; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -35,7 +33,6 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; -use Throwable; /** * The api that connects to the Tiqr app. @@ -52,7 +49,6 @@ public function __construct( private readonly TiqrUserRepositoryInterface $userRepository, private readonly AuthenticationRateLimitServiceInterface $authenticationRateLimitService, private readonly LoggerInterface $logger, - private readonly TrustedDeviceService $cookieService, ) { } @@ -249,15 +245,7 @@ private function registerAction( $logger->warning('Error finalizing enrollment', ['exception' => $e]); } - $okResponse = new Response('OK', Response::HTTP_OK); - - try { - $this->registerTrustedDevice($notificationAddress, $okResponse); - } catch (Throwable $e) { - $logger->warning('Could not register trusted device on registration', ['exception' => $e]); - } - - return $okResponse; + return new Response('OK', Response::HTTP_OK); } /** Handle login operation from the app, returns response for the app @@ -325,12 +313,6 @@ private function loginAction(Request $request, string $notificationType, string // Continue } - try { - $this->registerTrustedDevice($notificationAddress, $responseObject); - } catch (Throwable $e) { - $this->logger->warning('Could not create trusted device cookie.', ['exception' => $e]); - } - return $responseObject; } @@ -342,16 +324,4 @@ private function loginAction(Request $request, string $notificationType, string return new Response('AUTHENTICATION_FAILED', Response::HTTP_FORBIDDEN); } - - private function registerTrustedDevice( - string $notificationAddress, - Response $responseObject - ): void { - if (trim($notificationAddress) !== '') { - $this->cookieService->registerTrustedDevice( - $responseObject, - $notificationAddress - ); - } - } } diff --git a/src/Features/Context/TiqrContext.php b/src/Features/Context/TiqrContext.php index 206b8aa8..6212480d 100644 --- a/src/Features/Context/TiqrContext.php +++ b/src/Features/Context/TiqrContext.php @@ -24,7 +24,6 @@ use Assert\AssertionFailedException; use Behat\Behat\Context\Context; use Behat\Behat\Hook\Scope\BeforeScenarioScope; -use Behat\Behat\Tester\Exception\PendingException; use Behat\Gherkin\Node\TableNode; use Behat\Mink\Driver\BrowserKitDriver; use Behat\MinkExtension\Context\MinkContext; @@ -33,6 +32,7 @@ use OCRA; use RuntimeException; use stdClass; +use Surfnet\GsspBundle\Service\ValueStore; use Surfnet\SamlBundle\Exception\NotFound; use Surfnet\Tiqr\Dev\FileLogger; use Surfnet\Tiqr\Service\TrustedDevice\Crypto\HaliteCryptoHelper; @@ -86,6 +86,7 @@ public function __construct( private readonly FileLogger $fileLogger, private readonly KernelInterface $kernel, private readonly TrustedDeviceService $trustedDeviceService, + private readonly ValueStore $store, ) { } @@ -99,6 +100,7 @@ public function gatherContexts(BeforeScenarioScope $scope): void $environment = $scope->getEnvironment(); $this->minkContext = $environment->getContext(MinkContext::class); $this->minkContext->getSession()->setCookie('stepup_locale', 'en'); + $this->store->clear(); } /** @@ -745,4 +747,26 @@ public function theTrustedDeviceCookieIsCleared(): void { $this->minkContext->getSession()->getDriver()->getClient()->getCookieJar()->expire('tiqr-trusted-device'); } + + /** + * @Then /^I should see the trusted device cookie$/ + * @Then /^I should see the trusted device cookie for address "([^"]*)"$/ + */ + public function iShouldSeeTheCookie(?string $notificationAddress = null): void + { + $session = $this->minkContext->getMink()->getSession(); + $trustedDeviceCookie = $session->getCookie('tiqr-trusted-device'); + + if (!is_string($trustedDeviceCookie) || trim($trustedDeviceCookie) === '') { + throw new RuntimeException('The expected trusted device cookie is not present'); + } + + if ($notificationAddress !== null) { + $request = new Request(); + $request->cookies->set('tiqr-trusted-device', $trustedDeviceCookie); + $cookieValue = $this->trustedDeviceService->read($request); + Assertion::isInstanceOf($cookieValue, CookieValue::class); + Assertion::true($this->trustedDeviceService->isTrustedDevice($cookieValue, $notificationAddress)); + } + } } diff --git a/src/Features/Context/WebContext.php b/src/Features/Context/WebContext.php index 1d300f2d..82dd1011 100644 --- a/src/Features/Context/WebContext.php +++ b/src/Features/Context/WebContext.php @@ -22,6 +22,7 @@ use Behat\Behat\Context\Context; use Behat\Behat\Hook\Scope\BeforeScenarioScope; +use Behat\Behat\Tester\Exception\PendingException; use Behat\MinkExtension\Context\MinkContext; use Exception; use RobRichards\XMLSecLibs\XMLSecurityKey; diff --git a/src/Features/authentication.feature b/src/Features/authentication.feature index 770336be..e6870826 100644 --- a/src/Features/authentication.feature +++ b/src/Features/authentication.feature @@ -4,7 +4,7 @@ Feature: When an user needs to authenticate Background: Given the registration QR code is scanned - When the user registers the service + When the user registers the service with notification type "APNS" address: "0000000000111111111122222222223333333333" Then we have a registered user And I clear the logs @@ -32,6 +32,7 @@ Feature: When an user needs to authenticate # Service provider AuthNRequest response page Then I should see "urn:oasis:names:tc:SAML:2.0:status:Success" + And I should see the trusted device cookie for address "0000000000111111111122222222223333333333" And the logs are: | level | message | sari | @@ -91,6 +92,7 @@ Feature: When an user needs to authenticate | info | Authentication is finalized, returning to SP | present | | notice | Application authenticates the user | present | | notice | Created redirect response for sso return endpoint "/saml/sso_return" | present | + | notice | /Writing a trusted-device cookie with fingerprint .*/ | present | | info | User made a request with a session cookie. | present | | info | Created new session. | | | info | User has a session. | present | @@ -105,7 +107,7 @@ Feature: When an user needs to authenticate | info | User session matches the session cookie. | | | info | /SAMLResponse with id ".*" was not signed at root level, not attempting to verify the signature of the reponse itself/ | | | info | /Verifying signature of Assertion with id ".*"/ | | - + | notice | /Reading a trusted-device cookie with fingerprint .*/ | | Scenario: When an user cancels it's authentication # Service provider demo page @@ -200,6 +202,7 @@ Feature: When an user needs to authenticate # Service provider AuthNRequest response page Then I should see "urn:oasis:names:tc:SAML:2.0:status:Success" + And I should see the trusted device cookie And the logs are: | level | message | sari | @@ -256,6 +259,7 @@ Feature: When an user needs to authenticate | info | Authentication is finalized, returning to SP | present | | notice | Application authenticates the user | present | | notice | Created redirect response for sso return endpoint "/saml/sso_return" | present | + | notice | /Writing a trusted-device cookie with fingerprint .*/ | present | | info | User made a request with a session cookie. | present | | info | Created new session. | | | info | User has a session. | present | @@ -271,3 +275,4 @@ Feature: When an user needs to authenticate | info | /SAMLResponse with id ".*" was not signed at root level, not attempting to verify the signature of the reponse itself/ | | | info | /Verifying signature of Assertion with id ".*"/ | | + diff --git a/src/Features/mfaFatigueMitigation.feature b/src/Features/mfaFatigueMitigation.feature index 899d093a..ea7f4be2 100644 --- a/src/Features/mfaFatigueMitigation.feature +++ b/src/Features/mfaFatigueMitigation.feature @@ -6,22 +6,9 @@ Feature: When an user needs to authenticate Given the registration QR code is scanned And the user registers the service with notification type "APNS" address: "0000000000111111111122222222223333333333" Then we have a registered user - And the logs should mention: Writing a trusted-device cookie with fingerprint And I clear the logs And the trusted device cookie is cleared - Scenario: When a user authenticates using a qr code it should set a trusted cookie - Given I am on "/demo/sp" - And I fill in "NameID" with my identifier - When I press "authenticate" - Then I should see "Log in with tiqr" - And I should be on "/authentication" - - Then I scan the tiqr authentication qrcode - And the app authenticates to the service with notification type "APNS" address: "0000000000111111111122222222223333333333" - Then we have a authenticated app - And we have a trusted cookie for address: "0000000000111111111122222222223333333333" - Scenario: When a user authenticates without a trusted cookie, a push notification should not be sent Given I am on "/demo/sp" And I fill in "NameID" with my identifier diff --git a/src/Features/registration.feature b/src/Features/registration.feature index 72ebed16..169748cf 100644 --- a/src/Features/registration.feature +++ b/src/Features/registration.feature @@ -21,85 +21,91 @@ Feature: When an user needs to register for a new token Then I press "Submit" Then I should see "urn:oasis:names:tc:SAML:2.0:status:Success" + And I should see the trusted device cookie for address "0000000000111111111122222222223333333333" And the logs are: - | level | message | sari | - - | info | User made a request without a session cookie. | present | - | info | Created new session. | | - | info | User made a request without a session cookie. | present | - | info | Created new session. | | - | info | User made a request without a session cookie. | present | - | notice | Received sso request | | - | warning | There is already state present, clear previous state | | - | info | Processing AuthnRequest | | - | notice | /AuthnRequest processing complete, received AuthnRequest from "https:\/\/tiqr\.dev\.openconext\.local\/saml\/metadata", request ID: ".*"/ | | - | info | AuthnRequest stored in state | | - | notice | Redirect user to the application registration route /registration | | - | info | Created new session. | | - | info | User made a request without a session cookie. | present | - | info | Verifying if there is a pending registration from SP | present | - | info | There is a pending registration | present | - | info | Verifying if registration is finalized | present | - | info | Created new session. | | - | notice | Unable to retrieve the state storage value, file not found | present | - | info | Registration is not finalized return QR code | present | - | info | Generating enrollment key | present | - | notice | /Starting new enrollment session with sessionId .* and userId .*/ | present | - | info | /Setting SARI '.*' for identifier '.*'/ | present | - | info | User made a request with a session cookie. | present | - | info | Created new session. | | - | info | User has a session. | present | - | info | User session matches the session cookie. | present | - | info | Request for registration QR img | present | - | info | Returning registration QR response | present | - | info | User made a request with a session cookie. | present | - | info | Created new session. | | - | info | User has a session. | present | - | info | User session matches the session cookie. | present | - | info | Using "plain" as UserSecretStorage encryption type | present | - | info | Using "plain" as UserSecretStorage encryption type | present | - | notice | Got GET request to metadata endpoint with enrollment key | present | - | info | /Setting SARI '.*' for identifier '.*'/ | present | - | notice | Returned metadata response | present | - | info | User made a request with a session cookie. | present | - | info | Created new session. | | - | info | User has a session. | present | - | info | User session matches the session cookie. | present | - | info | Using "plain" as UserSecretStorage encryption type | present | - | info | Using "plain" as UserSecretStorage encryption type | present | - | notice | Got POST with registration response | present | - | notice | Received register action from client with User-Agent "Behat UA" and version "" | present | - | info | Start validating enrollment secret | present | - | info | Setting user secret and notification type and address | present | - | info | Finalizing enrollment | present | - | notice | Enrollment finalized | present | - | notice | /Writing a trusted-device cookie with fingerprint .*/ | present | - | info | User made a request with a session cookie. | present | - | info | Created new session. | | - | info | User has a session. | present | - | info | User session matches the session cookie. | present | - | info | Verifying if there is a pending registration from SP | present | - | info | There is a pending registration | present | - | info | Verifying if registration is finalized | present | - | info | Registration is finalized returning to service provider | present | - | notice | /Application sets the subject nameID to .*/ | present | - | notice | Created redirect response for sso return endpoint "/saml/sso_return" | present | - | info | User made a request with a session cookie. | present | - | info | Created new session. | | - | info | User has a session. | present | - | info | User session matches the session cookie. | present | - | notice | Received sso return request | | - | info | Create sso response | | - | notice | /Saml response created with id ".*", request ID: ".*"/ | | - | notice | Invalidate current state and redirect user to service provider assertion consumer url "https://tiqr.dev.openconext.local/demo/sp/acs" | | - | info | User made a request with a session cookie. | | - | info | Created new session. | | - | info | User has a session. | | - | info | User session matches the session cookie. | | - | info | /SAMLResponse with id ".*?" was not signed at root level, not attempting to verify the signature of the reponse itself/ | | - | info | /Verifying signature of Assertion with id ".*"/ | | + | level | message | sari | + | info | User made a request without a session cookie. | | + | info | Created new session. | | + | info | User made a request without a session cookie. | | + | info | Created new session. | | + | info | User made a request without a session cookie. | | + | notice | Received sso request | | + | info | Processing AuthnRequest | | + | notice | /AuthnRequest processing complete, received AuthnRequest from "https:\/\/tiqr\.dev\.openconext\.local\/saml\/metadata", request ID: ".*"/ | | + | info | AuthnRequest stored in state | | + | notice | Redirect user to the application registration route /registration | | + | info | Created new session. | | + | info | User made a request without a session cookie. | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Verifying if there is a pending registration from SP | present | + | info | There is a pending registration | present | + | info | Verifying if registration is finalized | present | + | info | Created new session. | | + | notice | Unable to retrieve the state storage value, file not found | present | + | info | Registration is not finalized return QR code | present | + | info | Generating enrollment key | present | + | notice | /Starting new enrollment session with sessionId .* and userId .*/ | present | + | info | /Setting SARI '.*' for identifier '.*'/ | present | + | info | User made a request with a session cookie. | present | + | info | Created new session. | | + | info | User has a session. | present | + | info | User session matches the session cookie. | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Request for registration QR img | present | + | info | Returning registration QR response | present | + | info | User made a request with a session cookie. | present | + | info | Created new session. | | + | info | User has a session. | present | + | info | User session matches the session cookie. | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | notice | Got GET request to metadata endpoint with enrollment key | present | + | info | /Setting SARI '.*' for identifier '.*'/ | present | + | notice | Returned metadata response | present | + | info | User made a request with a session cookie. | present | + | info | Created new session. | | + | info | User has a session. | present | + | info | User session matches the session cookie. | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | notice | Got POST with registration response | present | + | notice | Received register action from client with User-Agent "Behat UA" and version "" | present | + | info | Start validating enrollment secret | present | + | info | Setting user secret and notification type and address | present | + | info | Finalizing enrollment | present | + | notice | Enrollment finalized | present | + | info | User made a request with a session cookie. | present | + | info | Created new session. | | + | info | User has a session. | present | + | info | User session matches the session cookie. | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Verifying if there is a pending registration from SP | present | + | info | There is a pending registration | present | + | info | Verifying if registration is finalized | present | + | info | Registration is finalized returning to service provider | present | + | notice | /Application sets the subject nameID to .*/ | present | + | notice | Created redirect response for sso return endpoint "/saml/sso_return" | present | + | notice | /Writing a trusted-device cookie with fingerprint .*/ | present | + | info | User made a request with a session cookie. | present | + | info | Created new session. | | + | info | User has a session. | present | + | info | User session matches the session cookie. | present | + | notice | Received sso return request | | + | info | Create sso response | | + | notice | /Saml response created with id ".*", request ID: ".*"/ | | + | notice | Invalidate current state and redirect user to service provider assertion consumer url "https://tiqr.dev.openconext.local/demo/sp/acs" | | + | info | User made a request with a session cookie. | | + | info | Created new session. | | + | info | User has a session. | | + | info | User session matches the session cookie. | | + | info | /SAMLResponse with id ".*" was not signed at root level, not attempting to verify the signature of the reponse itself/ | | + | info | /Verifying signature of Assertion with id ".*"/ | | + | notice | /Reading a trusted-device cookie with fingerprint .*/ | | Scenario: When an user needs to register for a new token but is unable to scan the QR code Given I am on "/demo/sp" @@ -132,6 +138,8 @@ Feature: When an user needs to register for a new token | notice | Redirect user to the application registration route /registration | | | info | Created new session. | | | info | User made a request without a session cookie. | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Using "plain" as UserSecretStorage encryption type | present | | info | Verifying if there is a pending registration from SP | present | | info | There is a pending registration | present | | info | Verifying if registration is finalized | present | @@ -162,17 +170,19 @@ Feature: When an user needs to register for a new token | info | Setting user secret and notification type and address | present | | info | Finalizing enrollment | present | | notice | Enrollment finalized | present | - | notice | /Writing a trusted-device cookie with fingerprint .*/ | present | | info | User made a request with a session cookie. | present | | info | Created new session. | | | info | User has a session. | present | | info | User session matches the session cookie. | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Using "plain" as UserSecretStorage encryption type | present | | info | Verifying if there is a pending registration from SP | present | | info | There is a pending registration | present | | info | Verifying if registration is finalized | present | | info | Registration is finalized returning to service provider | present | | notice | /Application sets the subject nameID to .*/ | present | | notice | Created redirect response for sso return endpoint "/saml/sso_return" | present | + | notice | /Writing a trusted-device cookie with fingerprint .*/ | present | | info | User made a request with a session cookie. | present | | info | Created new session. | | | info | User has a session. | present | @@ -220,6 +230,8 @@ Feature: When an user needs to register for a new token | notice | Redirect user to the application registration route /registration | | | info | Created new session. | | | info | User made a request without a session cookie. | present | + | info | Using "plain" as UserSecretStorage encryption type | present | + | info | Using "plain" as UserSecretStorage encryption type | present | | info | Verifying if there is a pending registration from SP | present | | info | There is a pending registration | present | | info | Verifying if registration is finalized | present | @@ -243,7 +255,7 @@ Feature: When an user needs to register for a new token | info | User session matches the session cookie. | present | | notice | Received sso return request | | | info | Create sso response | | - | notice | /Saml response created with id ".*?", request ID: ".*?"/ | | + | notice | /Saml response created with id ".*", request ID: ".*"/ | | | notice | Invalidate current state and redirect user to service provider assertion consumer url "https://tiqr.dev.openconext.local/demo/sp/acs" | | | info | User made a request with a session cookie. | | | info | Created new session. | | diff --git a/src/Service/TrustedDeviceHelper.php b/src/Service/TrustedDeviceHelper.php new file mode 100644 index 00000000..cad58933 --- /dev/null +++ b/src/Service/TrustedDeviceHelper.php @@ -0,0 +1,54 @@ +cookieService->registerTrustedDevice( + $responseObject, + $notificationAddress + ); + } catch (Throwable $e) { + $this->logger->warning('Could not register trusted device on registration', ['exception' => $e]); + } + } +}