Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uml 3013 kick off one login process #2369

Merged
merged 26 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8abbac8
Initial landing page and route, structure behat test
MishNajam Oct 16, 2023
4055b7d
Add landing page with button and add test step
MishNajam Oct 16, 2023
74edb4e
One login page behind feature flag
MishNajam Oct 16, 2023
f1e89f7
Add one login form, submit button produces logger output
MishNajam Oct 16, 2023
fa8e69d
Fix linting errors
MishNajam Oct 18, 2023
267a783
Button returns 200 status - Link factory classes in configProvider, a…
MishNajam Oct 23, 2023
66028f6
AuthoriseOneLoginHandler sends request with en or cy
MishNajam Oct 23, 2023
260fdc5
use === instead of ==
MishNajam Oct 23, 2023
28da144
Revert me - adding Env to dockerifle
Oct 23, 2023
9420e2b
Add unit test coverage
MishNajam Oct 24, 2023
35a36c7
Add ui integration test step
MishNajam Oct 24, 2023
727ef36
Clicking button sends us to redirect pag, change login flag
MishNajam Oct 24, 2023
4434174
Add unit test for OneLoginForm and fix failing api-app test
MishNajam Oct 25, 2023
9fe9922
Add setup method to OneLoginFormTest
MishNajam Oct 25, 2023
c77218e
Add SecretsManagerFactoryTest
MishNajam Oct 25, 2023
079450f
add php linting fixes
MishNajam Oct 25, 2023
7ced99f
Add ui test coverage
MishNajam Oct 25, 2023
1a26695
Revert login page routing
MishNajam Oct 26, 2023
13d5fa4
Merge branch 'main' into UML-3013-kick-off-one-login-process
MishNajam Oct 30, 2023
f6d8779
Add frontend PR changes
MishNajam Oct 30, 2023
f12bd2f
Add backend pr changes - renaming classes and methods
MishNajam Oct 30, 2023
41a9721
Rename authorise classes and methods to authenticate
MishNajam Oct 30, 2023
dfc9b32
update routes file
MishNajam Oct 30, 2023
4c42e69
Rename behat tests
MishNajam Oct 30, 2023
cb412ad
Merge branch 'main' into UML-3013-kick-off-one-login-process
MishNajam Oct 30, 2023
d53a040
Merge branch 'main' into UML-3013-kick-off-one-login-process
MishNajam Oct 30, 2023
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
2 changes: 1 addition & 1 deletion docker-compose.dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,4 @@ services:
environment:
PUBLIC_URL: http://localhost:4013
INTERNAL_URL: http://mock-one-login:8080
REDIRECT_URL: http://localhost:9002/
REDIRECT_URL: http://localhost:9002/auth/redirect
4 changes: 2 additions & 2 deletions service-api/app/config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
use Mezzio\Application;
use Mezzio\MiddlewareFactory;
use Psr\Container\ContainerInterface;
use App\Handler\OneLoginAuthorisationRequestHandler;
use App\Handler\OneLoginAuthenticationRequestHandler;

/**
* Setup routes with a single request method:
Expand Down Expand Up @@ -135,7 +135,7 @@

$app->patch('/v1/auth', AuthHandler::class, 'user.auth');

$app->get('/v1/auth-one-login', OneLoginAuthorisationRequestHandler::class, 'user.auth-one-login');
$app->get('/v1/auth-one-login', OneLoginAuthenticationRequestHandler::class, 'user.auth-one-login');

$app->post('/v1/email-user/{emailTemplate}', NotifyHandler::class, 'lpa.user.notify');
};
9 changes: 8 additions & 1 deletion service-api/app/src/App/src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace App;

use App\DataAccess\ApiGateway\InstructionsAndPreferencesImagesFactory;
use Aws;
use Http;
use Facile;
use Laminas;
use Psr;

Expand Down Expand Up @@ -56,12 +56,16 @@ public function getDependencies(): array
DataAccess\Repository\LpasInterface::class => DataAccess\ApiGateway\Lpas::class,
DataAccess\Repository\InstructionsAndPreferencesImagesInterface::class
=> DataAccess\ApiGateway\InstructionsAndPreferencesImages::class,

//One Login
Facile\OpenIDClient\Issuer\IssuerBuilderInterface::class => Facile\OpenIDClient\Issuer\IssuerBuilder::class
],

'factories' => [
// Services
Aws\Sdk::class => Service\Aws\SdkFactory::class,
Aws\DynamoDb\DynamoDbClient::class => Service\Aws\DynamoDbClientFactory::class,
Aws\SecretsManager\SecretsManagerClient::class => Service\Aws\SecretsManagerFactory::class,
Service\ApiClient\Client::class => Service\ApiClient\ClientFactory::class,
Service\Email\EmailClient::class => Service\Email\EmailClientFactory::class,

Expand Down Expand Up @@ -92,6 +96,9 @@ public function getDependencies(): array
Laminas\Stratigility\Middleware\ErrorHandler::class => [
Service\Log\LogStderrListenerDelegatorFactory::class,
],
Laminas\Cache\Storage\AdapterPluginManager::class => [
Laminas\Cache\Storage\Adapter\Apcu\AdapterPluginManagerDelegatorFactory::class,
],
],
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace App\Handler;

use App\Exception\BadRequestException;
use App\Service\Authentication\OneLoginAuthorisationRequestService;
use App\Service\Authentication\OneLoginAuthenticationRequestService;
use Exception;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
Expand All @@ -15,10 +15,10 @@
/**
* @codeCoverageIgnore
*/
class OneLoginAuthorisationRequestHandler implements RequestHandlerInterface
class OneLoginAuthenticationRequestHandler implements RequestHandlerInterface
{
public function __construct(
private OneLoginAuthorisationRequestService $authorisationRequestService,
private OneLoginAuthenticationRequestService $authenticationRequestService,
) {
}

Expand All @@ -29,19 +29,17 @@ public function __construct(
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$params = $request->getParsedBody();
$requestData = $request->getQueryParams();

if (empty($params['ui_locale'])) {
if (empty($requestData['ui_locale'])) {
throw new BadRequestException('Ui locale must be provided');
}

$ui_locale = strtolower($params['ui_locale']);
$ui_locale = strtolower($requestData['ui_locale']);
if ($ui_locale !== 'en' and $ui_locale !== 'cy') {
throw new BadRequestException('ui_locale is not set to en or cy');
}

$authorisationUri = $this->authorisationRequestService->createAuthorisationRequest($params['ui_locale']);

return new JsonResponse($authorisationUri);
return new JsonResponse($this->authenticationRequestService->createAuthenticationRequest($ui_locale));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@

use function Facile\OpenIDClient\base64url_encode;

class OneLoginAuthorisationRequestService
class OneLoginAuthenticationRequestService
{
public function __construct(
private JWKFactory $jwkFactory,
private IssuerBuilderInterface $issuerBuilder,
private CacheFactory $cacheFactory
private CacheFactory $cacheFactory,
) {
}

public function createAuthorisationRequest(string $uiLocale): string
public function createAuthenticationRequest(string $uiLocale): array
{

$cachedBuilder = new MetadataProviderBuilder();
$cachedBuilder->setCache(($this->cacheFactory)('cache'))
$cachedBuilder->setCache(($this->cacheFactory)('one-login'))
->setCacheTtl(3600);

$issuer = $this->issuerBuilder
Expand All @@ -52,17 +52,25 @@ public function createAuthorisationRequest(string $uiLocale): string

$authorisationService = (new AuthorizationServiceBuilder())->build();

return $authorisationService->getAuthorizationUri(
$state = base64url_encode(random_bytes(12));
$nonce = openssl_digest(random_bytes(24), 'sha256');
$authorisationRequestUrl = $authorisationService->getAuthorizationUri(
$client,
[
'scope' => 'openid email',
'state' => base64url_encode(random_bytes(12)),
'redirect_uri' => '/lpa/dashboard',
'nonce' => openssl_digest(base64url_encode(random_bytes(12)), 'sha256'),
'state' => $state,
'redirect_uri' => 'http://localhost:9002/auth/redirect', //TODO: use dynamic domain UML-3121
'nonce' => $nonce,
'vtr' => '["Cl.Cm.P2"]',
'ui_locales' => $uiLocale,
'claims' => '{"userinfo":{"https://vocab.account.gov.uk/v1/coreIdentityJWT": null}}',
]
);

return [
'state' => $state,
'nonce' => $nonce,
'url' => $authorisationRequestUrl,
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\Service\Aws;

use Aws\Sdk;
use Psr\Container\ContainerInterface;

/**
* Builds a configured instance of the AWS Secrets Manager Client
*/
class SecretsManagerFactory
{
public function __invoke(ContainerInterface $container)
{
return $container->get(Sdk::class)->createSecretsManager();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,20 @@

namespace AppTest\Service\Authentication;

use App\Service\Authentication\OneLoginAuthorisationRequestService;
use App\Service\Authentication\OneLoginAuthenticationRequestService;
use App\Service\Authentication\JWKFactory;
use App\Service\Cache\CacheFactory;
use App\Service\Authentication\IssuerBuilder;
use Facile\OpenIDClient\Issuer\IssuerBuilderInterface;
use Facile\OpenIDClient\Issuer\IssuerInterface;
use Facile\OpenIDClient\Issuer\Metadata\IssuerMetadataInterface;
use Facile\OpenIDClient\Issuer\Metadata\Provider\MetadataProviderBuilder;
use Interop\Container\Containerinterface;
use Jose\Component\Core\JWK;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\SimpleCache\CacheInterface;

class OneLoginAuthorisationRequestServiceTest extends TestCase
class OneLoginAuthenticationRequestServiceTest extends TestCase
{
use ProphecyTrait;

Expand All @@ -43,24 +40,28 @@ public function setup(): void
$issuerMetaData->getAuthorizationEndpoint()->willReturn('fake endpoint');
$this->issuerBuilder->setMetadataProviderBuilder(Argument::any())->willReturn($this->issuerBuilder);
$this->issuerBuilder->build('http://mock-one-login:8080/.well-known/openid-configuration')->willReturn($issuer);
$this->cacheFactory->__invoke('cache')->willReturn($cacheInterface);
$this->cacheFactory->__invoke('one-login')->willReturn($cacheInterface);
}

/**
* @test
*/
public function create_authorisation_request(): void
public function create_authentication_request(): void
{
$authorisationRequestService = new OneLoginAuthorisationRequestService(
$authorisationRequestService = new OneLoginAuthenticationRequestService(
$this->jwkFactory->reveal(),
$this->issuerBuilder->reveal(),
$this->cacheFactory->reveal(),
);
$authorisationRequest = $authorisationRequestService->createAuthorisationRequest('en');
$this->assertStringContainsString('client_id=client-id', $authorisationRequest);
$this->assertStringContainsString('scope=openid+email', $authorisationRequest);
$this->assertStringContainsString('vtr=%5B%22Cl.Cm.P2%22%5D', $authorisationRequest);
$this->assertStringContainsString('ui_locales=en', $authorisationRequest);
$this->assertStringContainsString('redirect_uri=%2Flpa%2Fdashboard', $authorisationRequest);
$authorisationRequest = $authorisationRequestService->createAuthenticationRequest('en');
$authorisationRequestUrl = $authorisationRequest['url'];
$this->assertStringContainsString('client_id=client-id', $authorisationRequestUrl);
$this->assertStringContainsString('scope=openid+email', $authorisationRequestUrl);
$this->assertStringContainsString('vtr=["Cl.Cm.P2"]', urldecode($authorisationRequestUrl));
$this->assertStringContainsString('ui_locales=en', $authorisationRequestUrl);
$this->assertStringContainsString(
'redirect_uri=http://localhost:9002/auth/redirect',
urldecode($authorisationRequestUrl)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace AppTest\Service\Aws;

use App\Service\Aws\SecretsManagerFactory;
use Aws\Sdk;
use Aws\SecretsManager\SecretsManagerClient;
use Monolog\Test\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Container\ContainerInterface;

class SecretsManagerFactoryTest extends TestCase
{
use ProphecyTrait;

/** @test */
public function it_creates_a_SecretsManager(): void
{
$containerProphecy = $this->prophesize(ContainerInterface::class);

$containerProphecy->get(Sdk::class)
->willReturn(new Sdk([
'region' => 'eu-west-1',
'version' => 'latest',
]));

$factory = new SecretsManagerFactory();
$client = $factory($containerProphecy->reveal());

$this->assertInstanceOf(SecretsManagerClient::class, $client);
}
}
10 changes: 9 additions & 1 deletion service-front/app/config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,18 @@
$actorRoutes = function (Application $app, MiddlewareFactory $factory, ContainerInterface $container): void {
$USE_OLDER_LPA_JOURNEY = 'use_older_lpa_journey';
$DELETE_LPA_FEATURE = 'delete_lpa_feature';
$ALLOW_GOV_ONE_LOGIN = 'allow_gov_one_login';

$defaultNotFoundPage = Actor\Handler\LpaDashboardHandler::class;

$app->route('/home', Actor\Handler\ActorTriagePageHandler::class, ['GET', 'POST'], 'home');
$app->route('/home', [
new ConditionalRoutingMiddleware(
$container,
$ALLOW_GOV_ONE_LOGIN,
Actor\Handler\AuthenticateOneLoginHandler::class,
Actor\Handler\ActorTriagePageHandler::class
)
], ['GET', 'POST'], 'home');
$app->route('/', Actor\Handler\ActorTriagePageHandler::class, ['GET', 'POST'], 'home-trial');
$app->get('/healthcheck', Common\Handler\HealthcheckHandler::class, 'healthcheck');
$app->get('/stats', Actor\Handler\StatsPageHandler::class, 'actor-stats');
Expand Down
2 changes: 1 addition & 1 deletion service-front/app/features/bootstrap/behat.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
'delete_lpa_feature' => true,
'dont_send_lpas_registered_after_sep_2019_to_cleansing_team' => true,
'instructions_and_preferences' => true,
'allow_gov_one_login' => true,
'allow_gov_one_login' => false,
],
'notify' => [
'api' => [
Expand Down
50 changes: 50 additions & 0 deletions service-front/app/features/context/UI/AccountContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use PHPUnit\Framework\Assert;
use Psr\Http\Message\RequestInterface;

use function PHPUnit\Framework\assertArrayHasKey;

/**
* @property string userEmail
* @property string userPassword
Expand Down Expand Up @@ -2029,4 +2031,52 @@ public function iReceiveAnEmailAndShownUniqueInstructionsOnHowToResetMyPassword(
$this->ui->assertPageAddress('/reset-password');
$this->ui->assertPageContainsText('We\'ve emailed a link to ');
}

/**
* @Given /^I am on the temporary one login page$/
*/
public function iAmOnTheTemporaryOneLoginPage(): void
{
$this->ui->visit('/home');
$this->ui->assertPageAddress('/home');
$this->ui->assertElementContainsText('button[name=sign-in-one-login]', 'Sign in via One Login');
}

/**
* @When /^I click the one login button$/
*/
public function iClickTheOneLoginButton(): void
{
$this->apiFixtures->append(
ContextUtilities::newResponse(
StatusCodeInterface::STATUS_OK,
json_encode(
[
'state' => 'fakestate',
'nonce' => 'fakenonce',
'url' => 'http://fake.url/authorize',
]
),
self::USER_SERVICE_REQUEST_PASSWORD_RESET
)
);

$this->iDoNotFollowRedirects();
$this->ui->pressButton('Sign in via One Login');
$this->iDoFollowRedirects();

$request = $this->apiFixtures->getLastRequest();
$params = $request->getUri()->getQuery();
Assert::assertStringContainsString('ui_locale=en', $params);
}

/**
* @Then /^I am redirected to the redirect page$/
*/
public function iAmRedirectedToTheRedirectPage(): void
{
$locationHeader = $this->ui->getSession()->getResponseHeader('Location');
assert::assertTrue(isset($locationHeader));
assert::assertEquals($locationHeader, 'http://fake.url/authorize');
}
}
8 changes: 8 additions & 0 deletions service-front/app/features/one-login.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@onelogin
Feature: Authenticate One Login

@ui @actor @ff:allow_gov_one_login:true
Scenario: I initiate authentication via one login
Given I am on the temporary one login page
When I click the one login button
Then I am redirected to the redirect page
Loading
Loading