Skip to content

Commit

Permalink
MLPAB-2122: Allow donor ID mismatch updates (#1327)
Browse files Browse the repository at this point in the history
  • Loading branch information
acsauk authored Jul 8, 2024
1 parent e82646e commit 2add8dd
Show file tree
Hide file tree
Showing 23 changed files with 736 additions and 200 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ up: ##@build Builds and brings the app up
up-dev: ##@build Builds the app and brings up via Air hot reload with Delve debugging enabled using amd binaries
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 DOCKER_DEFAULT_PLATFORM=linux/$(shell go env GOARCH) docker compose -f docker/docker-compose.yml -f docker/docker-compose.dev.yml up -d --build --force-recreate --remove-orphans app

pull-latest-mock-onelogin: ## @build logs in to management AWS account and pulls the latest mock-onelogin image (assumes ~/.aws/config contains a profile called management)
aws-vault exec management -- aws ecr get-login-password --region eu-west-1 | docker login --username AWS --password-stdin 311462405659.dkr.ecr.eu-west-1.amazonaws.com
docker compose -f docker/docker-compose.yml pull mock-onelogin

run-cypress: ##@testing Runs cypress e2e tests. To run a specific spec file pass in spec e.g. make run-cypress spec=start
ifdef spec
yarn run cypress:run --spec "cypress/e2e/$(spec).cy.js"
Expand Down
109 changes: 108 additions & 1 deletion cypress/e2e/donor/confirm-your-identity-and-sign.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('Confirm your identity and sign', () => {
cy.contains('label', 'Sam Smith (donor)').click();
cy.contains('button', 'Continue').click();

cy.url().should('contain', '/one-login/callback');
cy.url().should('contain', '/onelogin-identity-details');
cy.checkA11yApp();

cy.contains('Sam');
Expand Down Expand Up @@ -283,4 +283,111 @@ describe('Confirm your identity and sign', () => {
cy.contains('Withdrawn');
})
})

describe('when identity details do not match LPA', () => {
it('can update LPA details', () => {
cy.visit('/fixtures?redirect=/task-list&progress=payForTheLpa');
cy.contains('li', "Confirm your identity and sign")
.should('contain', 'Not started')
.find('a')
.click();

cy.url().should('contain', '/how-to-confirm-your-identity-and-sign');
cy.checkA11yApp();

cy.contains('h1', 'How to confirm your identity and sign the LPA');
cy.contains('a', 'Continue').click();

cy.url().should('contain', '/prove-your-identity');
cy.checkA11yApp();
cy.contains('a', 'Continue').click();

cy.contains('label', 'Charlie Cooper (certificate provider)').click();
cy.contains('button', 'Continue').click();

cy.url().should('contain', '/onelogin-identity-details');
cy.checkA11yApp();

cy.contains('dd', 'Sam').parent().contains('span', 'Does not match');
cy.contains('dd', 'Smith').parent().contains('span', 'Does not match');
cy.contains('dd', '2 January 2000').parent().contains('span', 'Does not match');

cy.contains('label', 'Yes').click();
cy.contains('button', 'Continue').click();

cy.url().should('contain', '/onelogin-identity-details');
cy.checkA11yApp();

cy.contains('Your LPA details have been updated to match your confirmed identity')
cy.get('main').should('not.contain', 'Sam');
cy.get('main').should('not.contain', 'Smith');
cy.get('main').should('not.contain', '2 January 2000');
})

it('can withdraw LPA', () => {
cy.visit('/fixtures?redirect=/task-list&progress=payForTheLpa');
cy.contains('li', "Confirm your identity and sign")
.should('contain', 'Not started')
.find('a')
.click();

cy.url().should('contain', '/how-to-confirm-your-identity-and-sign');
cy.checkA11yApp();

cy.contains('h1', 'How to confirm your identity and sign the LPA');
cy.contains('a', 'Continue').click();

cy.url().should('contain', '/prove-your-identity');
cy.checkA11yApp();
cy.contains('a', 'Continue').click();

cy.contains('label', 'Charlie Cooper (certificate provider)').click();
cy.contains('button', 'Continue').click();

cy.url().should('contain', '/onelogin-identity-details');
cy.checkA11yApp();

cy.contains('dd', 'Sam').parent().contains('span', 'Does not match');
cy.contains('dd', 'Smith').parent().contains('span', 'Does not match');
cy.contains('dd', '2 January 2000').parent().contains('span', 'Does not match');

cy.contains('label', 'No').click();
cy.contains('button', 'Continue').click();

cy.url().should('contain', '/withdraw-this-lpa');
cy.checkA11yApp();
})

it('errors when option not selected', () => {
cy.visit('/fixtures?redirect=/task-list&progress=payForTheLpa');
cy.contains('li', "Confirm your identity and sign")
.should('contain', 'Not started')
.find('a')
.click();

cy.url().should('contain', '/how-to-confirm-your-identity-and-sign');
cy.checkA11yApp();

cy.contains('h1', 'How to confirm your identity and sign the LPA');
cy.contains('a', 'Continue').click();

cy.url().should('contain', '/prove-your-identity');
cy.checkA11yApp();
cy.contains('a', 'Continue').click();

cy.contains('label', 'Charlie Cooper (certificate provider)').click();
cy.contains('button', 'Continue').click();

cy.url().should('contain', '/onelogin-identity-details');
cy.checkA11yApp();

cy.contains('button', 'Continue').click();

cy.get('.govuk-error-summary').within(() => {
cy.contains('Select yes if you would like to update your details');
});

cy.contains('.govuk-error-message', 'Select yes if you would like to update your details');
});
});
});
4 changes: 2 additions & 2 deletions internal/actor/donor_provided_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ func TestGenerateHash(t *testing.T) {
}

// DO change this value to match the updates
const modified uint64 = 0xb4795f8254204619
const modified uint64 = 0xe8ee03e19c4313a1

// DO NOT change these initial hash values. If a field has been added/removed
// you will need to handle the version gracefully by modifying
// (*DonorProvidedDetails).HashInclude and adding another testcase for the new
// version.
testcases := map[uint8]uint64{
0: 0xc907539bbd47eeda,
0: 0x240e672086c6a594,
}

for version, initial := range testcases {
Expand Down
12 changes: 7 additions & 5 deletions internal/identity/user_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/ministryofjustice/opg-modernising-lpa/internal/date"
"github.com/ministryofjustice/opg-modernising-lpa/internal/place"
)

// https://www.icao.int/publications/Documents/9303_p3_cons_en.pdf
Expand Down Expand Up @@ -111,11 +112,12 @@ var charmap = map[rune][]rune{
}

type UserData struct {
Status Status
FirstNames string
LastName string
DateOfBirth date.Date
RetrievedAt time.Time
Status Status
FirstNames string
LastName string
DateOfBirth date.Date
RetrievedAt time.Time
CurrentAddress place.Address
}

func (u UserData) MatchName(firstNames, lastName string) bool {
Expand Down
2 changes: 1 addition & 1 deletion internal/onelogin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (c *Client) AuthCodeURL(state, nonce, locale string, identity bool) (string

if identity {
q.Add("vtr", `["Cl.Cm.P2"]`)
q.Add("claims", `{"userinfo":{"https://vocab.account.gov.uk/v1/coreIdentityJWT": null,"https://vocab.account.gov.uk/v1/returnCode": null}}`)
q.Add("claims", `{"userinfo":{"https://vocab.account.gov.uk/v1/coreIdentityJWT": null,"https://vocab.account.gov.uk/v1/returnCode": null,"https://vocab.account.gov.uk/v1/address": null}}`)
}

endpoint, err := c.openidConfiguration.AuthorizationEndpoint()
Expand Down
2 changes: 1 addition & 1 deletion internal/onelogin/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestAuthCodeURL(t *testing.T) {
}

func TestAuthCodeURLForIdentity(t *testing.T) {
expected := "http://auth?claims=%7B%22userinfo%22%3A%7B%22https%3A%2F%2Fvocab.account.gov.uk%2Fv1%2FcoreIdentityJWT%22%3A+null%2C%22https%3A%2F%2Fvocab.account.gov.uk%2Fv1%2FreturnCode%22%3A+null%7D%7D&client_id=123&nonce=nonce&redirect_uri=http%3A%2F%2Fredirect&response_type=code&scope=openid+email&state=state&ui_locales=cy&vtr=%5B%22Cl.Cm.P2%22%5D"
expected := "http://auth?claims=%7B%22userinfo%22%3A%7B%22https%3A%2F%2Fvocab.account.gov.uk%2Fv1%2FcoreIdentityJWT%22%3A+null%2C%22https%3A%2F%2Fvocab.account.gov.uk%2Fv1%2FreturnCode%22%3A+null%2C%22https%3A%2F%2Fvocab.account.gov.uk%2Fv1%2Faddress%22%3A+null%7D%7D&client_id=123&nonce=nonce&redirect_uri=http%3A%2F%2Fredirect&response_type=code&scope=openid+email&state=state&ui_locales=cy&vtr=%5B%22Cl.Cm.P2%22%5D"

c := &Client{
redirectURL: "http://redirect",
Expand Down
67 changes: 54 additions & 13 deletions internal/onelogin/user_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/ministryofjustice/opg-modernising-lpa/internal/date"
"github.com/ministryofjustice/opg-modernising-lpa/internal/identity"
"github.com/ministryofjustice/opg-modernising-lpa/internal/place"
)

var ErrMissingCoreIdentityJWT = errors.New("UserInfo missing CoreIdentityJWT property")

type UserInfo struct {
Sub string `json:"sub"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
Phone string `json:"phone"`
PhoneVerified bool `json:"phone_verified"`
UpdatedAt int `json:"updated_at"`
CoreIdentityJWT string `json:"https://vocab.account.gov.uk/v1/coreIdentityJWT"`
ReturnCodes []ReturnCodeInfo `json:"https://vocab.account.gov.uk/v1/returnCode,omitempty"`
Sub string `json:"sub"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
Phone string `json:"phone"`
PhoneVerified bool `json:"phone_verified"`
UpdatedAt int `json:"updated_at"`
CoreIdentityJWT string `json:"https://vocab.account.gov.uk/v1/coreIdentityJWT"`
ReturnCodes []ReturnCodeInfo `json:"https://vocab.account.gov.uk/v1/returnCode,omitempty"`
Addresses []credentialAddress `json:"https://vocab.account.gov.uk/v1/address,omitempty"`
}

type ReturnCodeInfo struct {
Expand Down Expand Up @@ -82,6 +84,36 @@ type CredentialBirthDate struct {
Value date.Date `json:"value"`
}

type credentialAddress struct {
UPRN string `json:"uprn"`
SubBuildingName string `json:"subBuildingName"`
BuildingName string `json:"buildingName"`
BuildingNumber string `json:"buildingNumber"`
DependentStreetName string `json:"dependentStreetName"`
StreetName string `json:"streetName"`
DoubleDependentAddressLocality string `json:"doubleDependentAddressLocality"`
DependentAddressLocality string `json:"dependentAddressLocality"`
AddressLocality string `json:"addressLocality"`
PostalCode string `json:"postalCode"`
AddressCountry string `json:"addressCountry"`
ValidFrom string `json:"validFrom"`
ValidUntil string `json:"validUntil"`
}

func (a credentialAddress) transformToAddress() place.Address {
ad := place.AddressDetails{
SubBuildingName: a.SubBuildingName,
BuildingName: a.BuildingName,
BuildingNumber: a.BuildingNumber,
ThoroughFareName: a.StreetName,
DependentLocality: a.DependentAddressLocality,
Town: a.AddressLocality,
Postcode: a.PostalCode,
}

return ad.TransformToAddress()
}

type NamePart struct {
Value string `json:"value"`

Expand Down Expand Up @@ -177,11 +209,20 @@ func (c *Client) ParseIdentityClaim(ctx context.Context, u UserInfo) (identity.U
return identity.UserData{Status: identity.StatusFailed}, nil
}

var currentAddress credentialAddress
for _, a := range u.Addresses {
if a.ValidUntil == "" {
currentAddress = a
break
}
}

return identity.UserData{
Status: identity.StatusConfirmed,
FirstNames: strings.Join(givenName, " "),
LastName: strings.Join(familyName, " "),
DateOfBirth: birthDates[0].Value,
RetrievedAt: claims.IssuedAt.Time,
Status: identity.StatusConfirmed,
FirstNames: strings.Join(givenName, " "),
LastName: strings.Join(familyName, " "),
DateOfBirth: birthDates[0].Value,
RetrievedAt: claims.IssuedAt.Time,
CurrentAddress: currentAddress.transformToAddress(),
}, nil
}
Loading

0 comments on commit 2add8dd

Please sign in to comment.