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-3056 initial code and working test #2360

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f69f404
UML-3056 initial code and working test
MishNajam Oct 4, 2023
db49c38
UML-3056 fix php linting
MishNajam Oct 4, 2023
002ce7e
Merge branch 'main' into UML-3056-implement-service-api-endpoint-to-h…
MishNajam Oct 4, 2023
13e66cc
get mock onelogin from ecr and spin that up locally and in CI
nickdavis2001 Oct 4, 2023
059d423
comment our test for now
nickdavis2001 Oct 4, 2023
1be79d2
tell docker where image is
nickdavis2001 Oct 4, 2023
920a0c5
move ecr login earlier as its needed to pull down mock login container
nickdavis2001 Oct 4, 2023
ee72d44
use variable for registry
nickdavis2001 Oct 4, 2023
feb16f3
add docker logs
nickdavis2001 Oct 4, 2023
9f2202e
force a rebuild
nickdavis2001 Oct 4, 2023
573b863
docker inspect to get ip addr of mock
nickdavis2001 Oct 4, 2023
855ff53
try explicit port
nickdavis2001 Oct 4, 2023
86de2d9
ecr login
nickdavis2001 Oct 5, 2023
86bbc86
add jwks to authService and test, and check ui_locale set to en or cy
MishNajam Oct 5, 2023
5cec7df
Test setting test as acceptance test
MishNajam Oct 5, 2023
4d8a2a6
Test setting test as integration test
MishNajam Oct 5, 2023
4c7d815
Fix linting error
MishNajam Oct 5, 2023
2469e34
Confirm test runs in pipeline
MishNajam Oct 5, 2023
7935932
revert to unit test
MishNajam Oct 5, 2023
8027ab4
Turn AuthenticationServiceTest into unit test
MishNajam Oct 5, 2023
62db8b7
Update catch Exception type
MishNajam Oct 10, 2023
65dab71
rename classes, methods and test names
MishNajam Oct 10, 2023
498af55
amend handler name
MishNajam Oct 10, 2023
ba00d22
Remove try catch in createAuthorisationRequest
MishNajam Oct 10, 2023
a2d6e5d
Merge branch 'main' into UML-3056-implement-service-api-endpoint-to-h…
MishNajam Oct 10, 2023
91c037a
Fix lint error
MishNajam Oct 10, 2023
94f1772
Merge branch 'main' into UML-3056-implement-service-api-endpoint-to-h…
MishNajam Oct 10, 2023
51f1958
Merge branch 'main' into UML-3056-implement-service-api-endpoint-to-h…
Lbagg1 Oct 11, 2023
c074f90
Merge branch 'main' into UML-3056-implement-service-api-endpoint-to-h…
Lbagg1 Oct 11, 2023
709c994
remove superfluous comments and move redirect_uri from the client met…
MishNajam Oct 11, 2023
f93f7ba
Merge branch 'main' into UML-3056-implement-service-api-endpoint-to-h…
nickdavis2001 Oct 11, 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
25 changes: 16 additions & 9 deletions .github/workflows/_build-and-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,19 @@ jobs:
matrix.run_unit_tests == 'true' &&
(inputs.specific_path == 'all' || inputs.specific_path == matrix.svc_prefix)

- name: ecr login
id: login_ecr
uses: aws-actions/amazon-ecr-login@b5f9031d829ac39b7fd26092019aeca3ee6b3ebd # [email protected]
with:
registries: 311462405659
if: |
inputs.push_to_ecr == 'true' &&
(inputs.specific_path == 'all' || inputs.specific_path == matrix.svc_prefix)

- name: docker integration and acceptance tests
env:
SVC_PREFIX: ${{ matrix.svc_prefix }}
ECR_REGISTRY: ${{ steps.login_ecr.outputs.registry }}
run: |
if [[ "${SVC_PREFIX}" = "api" ]]; then
docker run -d --rm --name lpa-codes-pact-mock pactfoundation/pact-cli:latest mock-service -p "80" --host "0.0.0.0" \
Expand All @@ -157,10 +167,15 @@ jobs:
--pact-dir /tmp/pacts --consumer use_a_lasting_power_of_attorney --provider api-gateway
docker run -d --rm --name iap-images-mock pactfoundation/pact-cli:latest mock-service -p "80" --host "0.0.0.0" \
--pact-dir /tmp/pacts --consumer use_a_lasting_power_of_attorney --provider iap-api-gateway
docker run -d --rm --name mock-one-login $ECR_REGISTRY/use_an_lpa/mock_onelogin_app

export DOCKER_REMOTE_LPA_PACT_IP="$(docker inspect --format='{{.NetworkSettings.IPAddress}}' lpa-codes-pact-mock)"
export DOCKER_REMOTE_API_PACT_IP="$(docker inspect --format='{{.NetworkSettings.IPAddress}}' api-gateway-pact-mock)"
export DOCKER_REMOTE_IAP_PACT_IP="$(docker inspect --format='{{.NetworkSettings.IPAddress}}' iap-images-mock)"
export DOCKER_REMOTE_MOCK_ONELOGIN_IP="$(docker inspect --format='{{.NetworkSettings.IPAddress}}' mock-one-login)"

curl -XGET $DOCKER_REMOTE_MOCK_ONELOGIN_IP:8080/token
docker logs mock-one-login

docker run -d --name behattests \
--add-host lpa-codes-pact-mock:$DOCKER_REMOTE_LPA_PACT_IP \
Expand All @@ -174,6 +189,7 @@ jobs:
docker stop lpa-codes-pact-mock
docker stop api-gateway-pact-mock
docker stop iap-images-mock
docker stop mock-one-login
elif [[ "${SVC_PREFIX}" = "front" ]]; then
docker run -d --name behattests front-app:latest

Expand Down Expand Up @@ -256,15 +272,6 @@ jobs:
with:
sarif_file: 'trivy-results.sarif'

- name: ecr login
id: login_ecr
uses: aws-actions/amazon-ecr-login@b5f9031d829ac39b7fd26092019aeca3ee6b3ebd # [email protected]
with:
registries: 311462405659
if: |
inputs.push_to_ecr == 'true' &&
(inputs.specific_path == 'all' || inputs.specific_path == matrix.svc_prefix)

- name: tag and push container
env:
ECR_REGISTRY: ${{ steps.login_ecr.outputs.registry }}
Expand Down
83 changes: 55 additions & 28 deletions service-api/app/config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,34 @@

declare(strict_types=1);

use App\Handler\AccessForAllLpaConfirmationHandler;
use App\Handler\AccessForAllLpaValidationHandler;
use App\Handler\AddLpaConfirmationHandler;
use App\Handler\AddLpaValidationHandler;
use App\Handler\AuthHandler;
use App\Handler\CanPasswordResetHandler;
use App\Handler\CanResetEmailHandler;
use App\Handler\ChangePasswordHandler;
use App\Handler\CompleteChangeEmailHandler;
use App\Handler\CompleteDeleteAccountHandler;
use App\Handler\CompletePasswordResetHandler;
use App\Handler\HealthcheckHandler;
use App\Handler\LpasCollectionHandler;
use App\Handler\LpasResourceCodesCollectionHandler;
use App\Handler\LpasResourceHandler;
use App\Handler\LpasResourceImagesCollectionHandler;
use App\Handler\NotifyHandler;
use App\Handler\RequestChangeEmailHandler;
use App\Handler\RequestCleanseHandler;
use App\Handler\RequestPasswordResetHandler;
use App\Handler\UserActivateHandler;
use App\Handler\UserHandler;
use App\Handler\ViewerCodeFullHandler;
use App\Handler\ViewerCodeSummaryHandler;
use Mezzio\Application;
use Mezzio\MiddlewareFactory;
use Psr\Container\ContainerInterface;
use App\Handler\OneLoginAuthorisationRequestHandler;

/**
* Setup routes with a single request method:
Expand Down Expand Up @@ -33,82 +58,84 @@
* );
*/
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
$app->get('/healthcheck', App\Handler\HealthcheckHandler::class, 'healthcheck');
$app->get('/healthcheck', HealthcheckHandler::class, 'healthcheck');

$app->get('/v1/lpas', App\Handler\LpasCollectionHandler::class, 'lpa.collection');
$app->get('/v1/lpas', LpasCollectionHandler::class, 'lpa.collection');
$app->post(
'/v1/older-lpa/validate',
App\Handler\AccessForAllLpaValidationHandler::class,
AccessForAllLpaValidationHandler::class,
'lpa.older.validate'
);
$app->patch(
'/v1/older-lpa/confirm',
App\Handler\AccessForAllLpaConfirmationHandler::class,
AccessForAllLpaConfirmationHandler::class,
'lpa.older.confirm'
);

$app->post(
'/v1/older-lpa/cleanse',
App\Handler\RequestCleanseHandler::class,
RequestCleanseHandler::class,
'lpa.older.cleanse'
);

$app->get('/v1/lpas/{user-lpa-actor-token:[0-9a-f\-]+}', App\Handler\LpasResourceHandler::class, 'lpa.resource');
$app->delete('/v1/lpas/{user-lpa-actor-token:[0-9a-f\-]+}', App\Handler\LpasResourceHandler::class, 'lpa.remove');
$app->get('/v1/lpas/{user-lpa-actor-token:[0-9a-f\-]+}', LpasResourceHandler::class, 'lpa.resource');
$app->delete('/v1/lpas/{user-lpa-actor-token:[0-9a-f\-]+}', LpasResourceHandler::class, 'lpa.remove');
$app->post(
'/v1/lpas/{user-lpa-actor-token:[0-9a-f\-]+}/codes',
App\Handler\LpasResourceCodesCollectionHandler::class,
LpasResourceCodesCollectionHandler::class,
'lpa.create.code'
);
$app->get(
'/v1/lpas/{user-lpa-actor-token:[0-9a-f\-]+}/codes',
App\Handler\LpasResourceCodesCollectionHandler::class,
LpasResourceCodesCollectionHandler::class,
'lpa.get.codes'
);
$app->put(
'/v1/lpas/{user-lpa-actor-token:[0-9a-f\-]+}/codes',
App\Handler\LpasResourceCodesCollectionHandler::class,
LpasResourceCodesCollectionHandler::class,
'lpa.cancel.code'
);
$app->get(
'/v1/lpas/{user-lpa-actor-token:[0-9a-f\-]+}/images',
App\Handler\LpasResourceImagesCollectionHandler::class,
LpasResourceImagesCollectionHandler::class,
'lpa.get.images'
);

$app->post('/v1/add-lpa/validate', App\Handler\AddLpaValidationHandler::class, 'lpa.add.validate');
$app->post('/v1/add-lpa/confirm', App\Handler\AddLpaConfirmationHandler::class, 'lpa.add.confirm');
$app->post('/v1/add-lpa/validate', AddLpaValidationHandler::class, 'lpa.add.validate');
$app->post('/v1/add-lpa/confirm', AddLpaConfirmationHandler::class, 'lpa.add.confirm');

$app->post('/v1/viewer-codes/summary', App\Handler\ViewerCodeSummaryHandler::class, 'lpa.viewer-code.summary');
$app->post('/v1/viewer-codes/full', App\Handler\ViewerCodeFullHandler::class, 'lpa.viewer-code.full');
$app->post('/v1/viewer-codes/summary', ViewerCodeSummaryHandler::class, 'lpa.viewer-code.summary');
$app->post('/v1/viewer-codes/full', ViewerCodeFullHandler::class, 'lpa.viewer-code.full');

$app->get('/v1/user', App\Handler\UserHandler::class, 'user.get');
$app->post('/v1/user', App\Handler\UserHandler::class, 'user.create');
$app->patch('/v1/user-activation', App\Handler\UserActivateHandler::class, 'user.activate');
$app->get('/v1/user', UserHandler::class, 'user.get');
$app->post('/v1/user', UserHandler::class, 'user.create');
$app->patch('/v1/user-activation', UserActivateHandler::class, 'user.activate');

$app->patch('/v1/request-password-reset', App\Handler\RequestPasswordResetHandler::class, 'user.password-reset');
$app->get('/v1/can-password-reset', App\Handler\CanPasswordResetHandler::class, 'user.can-password-reset');
$app->patch('/v1/request-password-reset', RequestPasswordResetHandler::class, 'user.password-reset');
$app->get('/v1/can-password-reset', CanPasswordResetHandler::class, 'user.can-password-reset');
$app->patch(
'/v1/complete-password-reset',
App\Handler\CompletePasswordResetHandler::class,
CompletePasswordResetHandler::class,
'user.complete-password-reset'
);

$app->patch('/v1/request-change-email', App\Handler\RequestChangeEmailHandler::class, 'user.request-change-email');
$app->get('/v1/can-reset-email', App\Handler\CanResetEmailHandler::class, 'user.can-reset-email');
$app->patch('/v1/request-change-email', RequestChangeEmailHandler::class, 'user.request-change-email');
$app->get('/v1/can-reset-email', CanResetEmailHandler::class, 'user.can-reset-email');
$app->patch(
'/v1/complete-change-email',
App\Handler\CompleteChangeEmailHandler::class,
CompleteChangeEmailHandler::class,
'user.complete-change-email'
);
$app->patch('/v1/change-password', App\Handler\ChangePasswordHandler::class, 'user.change-password');
$app->patch('/v1/change-password', ChangePasswordHandler::class, 'user.change-password');
$app->delete(
'/v1/delete-account/{account-id:[0-9a-f\-]+}',
App\Handler\CompleteDeleteAccountHandler::class,
CompleteDeleteAccountHandler::class,
'user.delete-account'
);

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

$app->post('/v1/email-user/{emailTemplate}', App\Handler\NotifyHandler::class, 'lpa.user.notify');
$app->get('/v1/auth-one-login', OneLoginAuthorisationRequestHandler::class, 'user.auth-one-login');

$app->post('/v1/email-user/{emailTemplate}', NotifyHandler::class, 'lpa.user.notify');
};
4 changes: 0 additions & 4 deletions service-api/app/src/App/src/Handler/AuthHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
use Psr\Http\Server\RequestHandlerInterface;

/**
* Class AuthHandler
*
* @package App\Handler
* @codeCoverageIgnore
*/
class AuthHandler implements RequestHandlerInterface
Expand All @@ -32,7 +29,6 @@ public function __construct(
* May call other collaborating code to generate the response.
*
* @param ServerRequestInterface $request
*
* @return ResponseInterface
* @throws Exception
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace App\Handler;

use App\Exception\BadRequestException;
use App\Service\Authentication\OneLoginAuthorisationRequestService;
use Exception;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
* @codeCoverageIgnore
*/
class OneLoginAuthorisationRequestHandler implements RequestHandlerInterface
{
public function __construct(
private OneLoginAuthorisationRequestService $authorisationRequestService,
) {
}

/**
* @param ServerRequestInterface $request
* @return ResponseInterface
* @throws Exception
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$params = $request->getParsedBody();

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

$ui_locale = strtolower($params['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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace App\Service\Authentication;

use Facile\OpenIDClient\Client\ClientBuilder;
use Facile\OpenIDClient\Client\Metadata\ClientMetadata;
use Facile\OpenIDClient\Issuer\IssuerBuilderInterface;
use Facile\OpenIDClient\Service\Builder\AuthorizationServiceBuilder;

use function Facile\OpenIDClient\base64url_encode;

class OneLoginAuthorisationRequestService
{
public function __construct(
private JWKFactory $JWKFactory,
private IssuerBuilderInterface $issuerBuilder,
) {
}

public function createAuthorisationRequest(string $uiLocale): string
{
//TODO UML-3080 Configure cache

$issuer = $this->issuerBuilder
->build('http://mock-one-login:8080/.well-known/openid-configuration');


$clientMetadata = ClientMetadata::fromArray([
'client_id' => 'client-id',
'client_secret' => 'my-client-secret',
'token_endpoint_auth_method' => 'private_key_jwt',
'jwks' => [
'keys' => [
($this->JWKFactory)(),
],
],
]);

$client = (new ClientBuilder())
->setIssuer($issuer)
->setClientMetadata($clientMetadata)
->build();

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

return $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'),
'vtr' => '["Cl.Cm.P2"]',
'ui_locales' => $uiLocale,
'claims' => '{"userinfo":{"https://vocab.account.gov.uk/v1/coreIdentityJWT": null}}',
]
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace AppTest\Service\Authentication;

use App\Service\Authentication\OneLoginAuthorisationRequestService;
use App\Service\Authentication\JWKFactory;
use Facile\OpenIDClient\Issuer\IssuerBuilder;
use Facile\OpenIDClient\Issuer\IssuerBuilderInterface;
use Facile\OpenIDClient\Issuer\IssuerInterface;
use Facile\OpenIDClient\Issuer\Metadata\IssuerMetadataInterface;
use Jose\Component\Core\JWK;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;

class OneLoginAuthorisationRequestServiceTest extends TestCase
{
use ProphecyTrait;

private ObjectProphecy|JWKFactory $JWKFactory;
private ObjectProphecy|IssuerBuilder $issuerBuilder;

public function setup(): void
{
$jwk = $this->prophesize(JWK::class);
$this->JWKFactory = $this->prophesize(JWKFactory::class);
$this->issuerBuilder = $this->prophesize(IssuerBuilderInterface::class);
$issuer = $this->prophesize(IssuerInterface::class);
$issuerMetaData = $this->prophesize(IssuerMetadataInterface::class);

$this->JWKFactory->__invoke()->willReturn($jwk);
$issuer->getMetadata()->willReturn($issuerMetaData);
$issuerMetaData->getAuthorizationEndpoint()->willReturn('fake endpoint');
$this->issuerBuilder->build('http://mock-one-login:8080/.well-known/openid-configuration')->willReturn($issuer);
}

/**
* @test
*/
public function create_authorisation_request(): void
{
$authorisationRequestService = new OneLoginAuthorisationRequestService(
$this->JWKFactory->reveal(),
$this->issuerBuilder->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);
}
}
Loading