Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/services/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ services:

OpenConext\EngineBlock\Service\Wayf\IdpSplitter:

OpenConext\EngineBlockBundle\Service\RememberChoiceDurationFormatter:
autowire: true
arguments:
$lifetimeInSeconds: '%wayf.remember_choice_per_idp_lifetime%'

OpenConext\EngineBlockBundle\Service\WayfViewModelFactory:
arguments:
$wayfExtension: '@OpenConext\EngineBlockBundle\Twig\Extensions\Extension\Wayf'
$durationFormatter: '@OpenConext\EngineBlockBundle\Service\RememberChoiceDurationFormatter'

OpenConext\EngineBlockBundle\Service\WayfRenderer:
autowire: true
Expand Down
5 changes: 5 additions & 0 deletions languages/messages.en.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@

// Remove cookies
'remember_choice' => 'Remember my choice',
'remember_choice_per_idp' => 'Remember my choice for this service for %duration%',
'remember_choice_duration_days' => '%count% days',
'remember_choice_duration_minutes' => '%count% minutes',
'remember_choice_tooltip_screenreader' => 'Why do we remember your choice?',
'remember_choice_per_idp_tooltip' => 'If you enable this option, you won\'t need to choose how you want to log into this service for the next %duration%. The login screen will then be skipped. <strong>Please note</strong>: do you sometimes use a different account to log in? If so, please disable this option. You can reverse this setting later at <a href="http://profile.surfconext.nl/">profile.surfconext.nl</a>.',
'cookie_removal_header' => 'Remove cookies',
'cookie_remove_button' => 'Remove',
'cookie_remove_all_button' => 'Remove all',
Expand Down
5 changes: 5 additions & 0 deletions languages/messages.nl.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@

// Remove cookies
'remember_choice' => 'Onthoud mijn keuze',
'remember_choice_per_idp' => 'Onthoud mijn keuze voor deze dienst voor %duration%',
'remember_choice_duration_days' => '%count% dagen',
'remember_choice_duration_minutes' => '%count% minuten',
'remember_choice_tooltip_screenreader' => 'Waarom onthouden we jouw keuze?',
'remember_choice_per_idp_tooltip' => 'Als je deze optie aanzet, hoef je de komende %duration% voor deze dienst niet meer te kiezen hoe je wilt inloggen. Het inlogscherm wordt dan overgeslagen. <strong>Let op</strong>: gebruik je soms een ander account om in te loggen? Zet de optie dan uit. Je kunt de keuze later weer terugdraaien op <a href="http://profile.surfconext.nl/">profile.surfconext.nl</a>.',
'cookie_removal_header' => 'Cookies verwijderen',
'cookie_remove_button' => 'Verwijderen',
'cookie_remove_all_button' => 'Alles verwijderen',
Expand Down
5 changes: 5 additions & 0 deletions languages/messages.pt.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@

// Remove cookies
'remember_choice' => 'Relembrar a minha escolha',
'remember_choice_per_idp' => 'Relembrar a minha escolha para este serviço durante %duration%',
'remember_choice_duration_days' => '%count% dias',
'remember_choice_duration_minutes' => '%count% minutos',
'remember_choice_tooltip_screenreader' => 'Porque é que relembramos a sua escolha?',
'remember_choice_per_idp_tooltip' => 'Se ativar esta opção, não terá de escolher como pretende iniciar sessão nos próximos %duration%. O ecrã de início de sessão será então ignorado. Atenção: por vezes utiliza uma conta diferente para iniciar sessão? Nesse caso, desative esta opção. Pode reverter esta opção mais tarde em <a href="http://profile.surfconext.nl/">profile.surfconext.nl</a>.',
'cookie_removal_header' => 'Remover cookies',
'cookie_remove_button' => 'Remover',
'cookie_remove_all_button' => 'Remover todos',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/**
* Copyright 2026 SURFnet B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

declare(strict_types=1);

namespace OpenConext\EngineBlockBundle\Service;

use Symfony\Contracts\Translation\TranslatorInterface;

final class RememberChoiceDurationFormatter
{
private const SECONDS_PER_MINUTE = 60;
private const SECONDS_PER_DAY = 86400;

public function __construct(
private readonly TranslatorInterface $translator,
private readonly int $lifetimeInSeconds,
) {
}

public function format(): string
{
if ($this->lifetimeInSeconds < self::SECONDS_PER_DAY) {
$minutes = intdiv($this->lifetimeInSeconds, self::SECONDS_PER_MINUTE);
return $this->translator->trans('remember_choice_duration_minutes', ['%count%' => $minutes]);
}

$days = intdiv($this->lifetimeInSeconds, self::SECONDS_PER_DAY);
return $this->translator->trans('remember_choice_duration_days', ['%count%' => $days]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class WayfViewModelFactory
{
public function __construct(
private readonly Wayf $wayfExtension,
private readonly RememberChoiceDurationFormatter $durationFormatter,
) {
}

Expand Down Expand Up @@ -70,6 +71,7 @@ public function create(
regularIdpList: $regularIdpList,
preferredIdpList: $preferredIdpList,
rememberChoicePerIdp: $rememberChoicePerIdp,
rememberChoiceDuration: $rememberChoicePerIdp ? $this->durationFormatter->format() : '',
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public function __construct(
/** @var WayfIdp[] */
public array $preferredIdpList,
public bool $rememberChoicePerIdp = false,
public string $rememberChoiceDuration = '',
) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public function wayfAction(Request $request)
$displayUnconnectedIdpsWayf = $request->query->getBoolean('displayUnconnectedIdpsWayf');
$addDiscoveries = $request->query->getBoolean('addDiscoveries', true);
$rememberChoiceFeature = $request->query->getBoolean('rememberChoiceFeature');
$rememberChoicePerIdp = $request->query->getBoolean('rememberChoicePerIdp');
$cutoffPointForShowingUnfilteredIdps = $request->query->getInt('cutoffPointForShowingUnfilteredIdps', 100);
$showIdPBanner = $request->query->getBoolean('showIdPBanner', true);
$defaultIdpEntityId = $request->query->get('defaultIdpEntityId', '');
Expand All @@ -70,6 +71,7 @@ public function wayfAction(Request $request)
showRequestAccess: $displayUnconnectedIdpsWayf,
requestId: 'bogus-request-id',
serviceProvider: TestEntitySeeder::buildSp(),
rememberChoicePerIdp: $rememberChoicePerIdp,
);

return new Response($output);
Expand Down
27 changes: 27 additions & 0 deletions tests/e2e/cypress/integration/skeune/wayf/wayf.general.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
matchSelector,
noResultSectionSelector,
remainingIdpSelector,
rememberChoicePerIdpClass,
rememberChoiceTooltipToggleSelector,
rememberChoiceTooltipValueSelector,
searchFieldSelector,
searchResetSelector,
searchSubmitSelector,
Expand Down Expand Up @@ -202,6 +205,30 @@ context('WAYF behaviour not tied to mouse / keyboard navigation', () => {
});
});

describe('Should show the per-SP remember my choice option with a tooltip', () => {
it('Renders the per-SP checkbox with a tooltip toggle', () => {
cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=true');
cy.get(`.${rememberChoicePerIdpClass}`).should('exist');
cy.get(rememberChoiceTooltipToggleSelector).should('exist');
});

it('Hides the tooltip content until the toggle is activated', () => {
cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=true');
cy.get(rememberChoiceTooltipValueSelector).should('not.be.visible');
cy.get(rememberChoiceTooltipToggleSelector).click({force: true});
cy.get(rememberChoiceTooltipValueSelector).should('be.visible');
cy.get(rememberChoiceTooltipToggleSelector).click({force: true});
cy.get(rememberChoiceTooltipValueSelector).should('not.be.visible');
});

it('Does not show the tooltip toggle for the global (non per-SP) variant', () => {
cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?connectedIdps=5&rememberChoiceFeature=true&rememberChoicePerIdp=false');
cy.get(`.${rememberChoicePerIdpClass}`).should('not.exist');
cy.get(rememberChoiceTooltipToggleSelector).should('not.exist');
cy.onPage('Remember my choice');
});
});

describe('Preferred IdPs section heading', () => {
it('Should show the preferred IdPs section with the correct heading when preferred IdPs are configured', () => {
cy.visit('https://engine.dev.openconext.local/functional-testing/wayf?preferredIdpEntityIds%5B%5D=https%3A%2F%2Fexample.com%2FentityId%2F1');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

/**
* Copyright 2026 SURFnet B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

declare(strict_types=1);

namespace Tests\OpenConext\EngineBlockBundle\Service;

use Mockery as m;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
use OpenConext\EngineBlockBundle\Service\RememberChoiceDurationFormatter;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;

class RememberChoiceDurationFormatterTest extends TestCase
{
use MockeryPHPUnitIntegration;

#[DataProvider('lifetimeProvider')]
public function testFormat(int $lifetimeInSeconds, string $expectedKey, array $expectedParameters): void
{
$translator = m::mock(TranslatorInterface::class);
$translator->shouldReceive('trans')
->once()
->with($expectedKey, $expectedParameters)
->andReturn('formatted-duration');

$formatter = new RememberChoiceDurationFormatter($translator, $lifetimeInSeconds);

$this->assertSame('formatted-duration', $formatter->format());
}

public static function lifetimeProvider(): array
{
return [
'ninety days (default lifetime)' => [7776000, 'remember_choice_duration_days', ['%count%' => 90]],
'exactly one day' => [86400, 'remember_choice_duration_days', ['%count%' => 1]],
'one second under a day' => [86399, 'remember_choice_duration_minutes', ['%count%' => 1439]],
'forty-five minutes' => [2700, 'remember_choice_duration_minutes', ['%count%' => 45]],
'zero seconds' => [0, 'remember_choice_duration_minutes', ['%count%' => 0]],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

/**
* Copyright 2026 SURFnet B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

declare(strict_types=1);

namespace Tests\OpenConext\EngineBlockBundle\Service;

use OpenConext\EngineBlock\Metadata\Entity\ServiceProvider;
use OpenConext\EngineBlockBundle\Service\RememberChoiceDurationFormatter;
use OpenConext\EngineBlockBundle\Service\WayfViewModelFactory;
use OpenConext\EngineBlockBundle\Twig\Extensions\Extension\ConnectedIdps;
use OpenConext\EngineBlockBundle\Twig\Extensions\Extension\Wayf;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;

class WayfViewModelFactoryTest extends TestCase
{
public function testRememberChoiceDurationIsTakenFromTheFormatter(): void
{
$wayfExtension = $this->createMock(Wayf::class);
$wayfExtension->method('getConnectedIdps')->willReturn(new ConnectedIdps([], []));

$translator = $this->createStub(TranslatorInterface::class);
$translator->method('trans')->willReturn('90 days');
$durationFormatter = new RememberChoiceDurationFormatter($translator, 7776000);

$factory = new WayfViewModelFactory($wayfExtension, $durationFormatter);

$viewModel = $factory->create(
idpList: [],
regularIdpList: [],
preferredIdpList: [],
showPreferredIdps: false,
action: '/sso',
greenHeader: 'SP',
helpLink: '/help',
backLink: false,
cutoffPointForShowingUnfilteredIdps: 100,
showIdPBanner: false,
rememberChoiceFeature: true,
showRequestAccess: false,
requestId: 'req-1',
serviceProvider: $this->createStub(ServiceProvider::class),
rememberChoicePerIdp: true,
);

$this->assertSame('90 days', $viewModel->rememberChoiceDuration);
}

public function testRememberChoiceDurationIsEmptyWhenNotPerIdp(): void
{
$wayfExtension = $this->createMock(Wayf::class);
$wayfExtension->method('getConnectedIdps')->willReturn(new ConnectedIdps([], []));

$translator = $this->createStub(TranslatorInterface::class);
$translator->method('trans')->willReturn('90 days');
$durationFormatter = new RememberChoiceDurationFormatter($translator, 7776000);

$factory = new WayfViewModelFactory($wayfExtension, $durationFormatter);

$viewModel = $factory->create(
idpList: [],
regularIdpList: [],
preferredIdpList: [],
showPreferredIdps: false,
action: '/sso',
greenHeader: 'SP',
helpLink: '/help',
backLink: false,
cutoffPointForShowingUnfilteredIdps: 100,
showIdPBanner: false,
rememberChoiceFeature: true,
showRequestAccess: false,
requestId: 'req-1',
serviceProvider: $this->createStub(ServiceProvider::class),
rememberChoicePerIdp: false,
);

$this->assertSame('', $viewModel->rememberChoiceDuration);
}
}
Loading
Loading