diff --git a/.run/local-with-auth.run.xml b/.run/local-with-auth.run.xml
new file mode 100644
index 000000000..b623653e5
--- /dev/null
+++ b/.run/local-with-auth.run.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/local.run.xml b/.run/local.run.xml
index 932fedfc0..addd36ea1 100644
--- a/.run/local.run.xml
+++ b/.run/local.run.xml
@@ -1,6 +1,6 @@
-
+
@@ -10,6 +10,11 @@
+
+
+
+
+
diff --git a/ReadMe.md b/ReadMe.md
index 7373494a9..73470f3e0 100644
--- a/ReadMe.md
+++ b/ReadMe.md
@@ -84,8 +84,21 @@ application start up. If you are using the `local` launch profile in IntelliJ, t
before running the migrations. After the migrations have run Spring Boot will then run the SQL in `data-local.sql` to
populate the database with seed data.
+### Mock One Login Oauth2
+
+For development, we've mocked elements of the governments one login system (that the web app will be using in deployment).
+When you start the app using the `local` run configuration, this will be available, when you attempt to login. It will automatically log you in as a user that has every role - and therefore can access all pages.
+
+If you are adding new roles please add the user with the `userId` set in `MockOneLoginHelper` to that new role/table.
+
+If you need to be able to login as a user that has specific roles then you can change the `userId` in `MockOneLoginHelper` to the id from the `one_login_user` table of a user that has the permissions you want.
+
+#### Disabling the mock One Login Oauth2
+
+If you need to disable the mock to run the app with One login's integration system, you start the app using the `local-with-auth` run configuration in `.run`.
+
### One Login accounts
-When you run the app and try to view pages, you will be prompted to sign in or create a One Login account.
+When you run the app without mocking the one login (e.g. using `local-with-auth run configurations) and try to view pages, you will be prompted to sign in or create a One Login account.
To view most pages, your account will need to have been added to the relevant database (e.g. LandlordUser,
LocalAuthorityUser) for you to be able to see the page. It checks the database on login (you can step through
@@ -108,4 +121,4 @@ logging in.
* When you hit the debug point, your one login id should be available in `subjectId`
(it should look like `urn:fdc:gov.uk:2022:string-of-characters`)
-If anyone knows a better way to do this please add it here!
\ No newline at end of file
+If anyone knows a better way to do this please add it here!
diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/config/CustomSecurityConfig.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/config/CustomSecurityConfig.kt
index 86204ebe5..95f0d356b 100644
--- a/src/main/kotlin/uk/gov/communities/prsdb/webapp/config/CustomSecurityConfig.kt
+++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/config/CustomSecurityConfig.kt
@@ -33,6 +33,8 @@ class CustomSecurityConfig {
.permitAll()
.requestMatchers("/register-as-a-landlord")
.permitAll()
+ .requestMatchers("/one-login-local/**")
+ .permitAll()
.requestMatchers("/lookup-an-address")
.permitAll()
.requestMatchers("/postcode")
@@ -40,6 +42,10 @@ class CustomSecurityConfig {
.anyRequest()
.authenticated()
}.oauth2Login(Customizer.withDefaults())
+ .csrf { requests ->
+ requests.ignoringRequestMatchers("/one-login-local/**")
+ }
+
return http.build()
}
diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/controller/MockOneLoginController.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/controller/MockOneLoginController.kt
new file mode 100644
index 000000000..18fad70b3
--- /dev/null
+++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/controller/MockOneLoginController.kt
@@ -0,0 +1,162 @@
+package uk.gov.communities.prsdb.webapp.local.api.controller
+
+import com.nimbusds.jose.Algorithm
+import com.nimbusds.jose.JOSEObjectType
+import com.nimbusds.jose.JWSAlgorithm
+import com.nimbusds.jose.JWSHeader
+import com.nimbusds.jose.crypto.ECDSASigner
+import com.nimbusds.jose.jwk.Curve
+import com.nimbusds.jose.jwk.ECKey
+import com.nimbusds.jose.jwk.KeyUse
+import com.nimbusds.jose.jwk.gen.ECKeyGenerator
+import com.nimbusds.jwt.JWTClaimsSet
+import com.nimbusds.jwt.SignedJWT
+import org.springframework.context.annotation.Profile
+import org.springframework.http.MediaType
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.util.UriComponentsBuilder
+import java.io.File
+import java.net.URI
+import java.time.Instant
+import java.util.Date
+import java.util.UUID
+
+@Profile("local")
+@RestController
+@RequestMapping("/one-login-local")
+class MockOneLoginController {
+ companion object {
+ val keyId = UUID.randomUUID().toString()
+
+ val ecKey = generateSecretKey()
+
+ private fun generateSecretKey(): ECKey {
+ val ecKey =
+ ECKeyGenerator(Curve.P_256)
+ .keyUse(KeyUse.SIGNATURE)
+ .keyID(keyId)
+ .algorithm(Algorithm("ES256"))
+ .generate()
+
+ return ecKey
+ }
+ }
+
+ private val userId = "urn:fdc:gov.uk:2022:UVWXY"
+
+ // These values are from One-Login's publicly available docs (https://docs.sign-in.service.gov.uk/integrate-with-integration-environment/authenticate-your-user/)
+ private val userEmail = "test@example.com"
+ private val userNumber = "01406946277"
+
+ var lastReceivedNonce: String? = null
+
+ @GetMapping("/.well-known/openid-configuration")
+ fun openidConfiguration(): String =
+ File("src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/openid-configuration.json")
+ .readText(Charsets.UTF_8)
+
+ @GetMapping("/.well-known/jwks.json")
+ fun jwksJson(): String =
+ File("src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/jwks.json")
+ .readText(Charsets.UTF_8)
+ .replace("keyId", keyId)
+ .replace(
+ "publicJWK_x",
+ ecKey
+ .toPublicJWK()
+ .x
+ .toString(),
+ ).replace(
+ "publicJWK_y",
+ ecKey
+ .toPublicJWK()
+ .y
+ .toString(),
+ )
+
+ @GetMapping("/authorize")
+ fun authorize(
+ @RequestParam response_type: String,
+ @RequestParam client_id: String,
+ @RequestParam scope: String,
+ @RequestParam state: String,
+ @RequestParam redirect_uri: String,
+ @RequestParam nonce: String,
+ ): ResponseEntity {
+ lastReceivedNonce = nonce
+ val locationURI: URI =
+ UriComponentsBuilder
+ .newInstance()
+ .uri(URI.create(redirect_uri))
+ .query("code=SplxlOBeZQQYbYS6WxSbIA")
+ .queryParam("state", state)
+ .build()
+ .toUri()
+
+ return ResponseEntity.status(302).location(locationURI).build()
+ }
+
+ @PostMapping("/token")
+ fun token(
+ @RequestParam grant_type: String,
+ @RequestParam redirect_uri: String,
+ @RequestParam client_assertion: String,
+ @RequestParam client_assertion_type: String,
+ @RequestParam code: String,
+ ): ResponseEntity {
+ val responseBody =
+ File("src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/token.json")
+ .readText(Charsets.UTF_8)
+ .replace("idToken", getIdToken())
+
+ val responseBuild = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(responseBody)
+
+ return responseBuild
+ }
+
+ @GetMapping("/userinfo")
+ fun userInfo(): ResponseEntity {
+ val responseBody =
+ File("src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/userInfo.json")
+ .readText(Charsets.UTF_8)
+ .replace("userId", userId)
+ .replace("userEmail", userEmail)
+ .replace("userNumber", userNumber)
+
+ val responseBuild = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(responseBody)
+
+ return responseBuild
+ }
+
+ private fun getIdToken(): String {
+ val headerBuilder: JWSHeader.Builder =
+ JWSHeader
+ .Builder(JWSAlgorithm.ES256)
+ .type(JOSEObjectType.JWT)
+ .jwk(ecKey.toPublicJWK())
+
+ val claimSetBBuilder: JWTClaimsSet.Builder =
+ JWTClaimsSet
+ .Builder()
+ .subject(userId)
+ .audience("l0AE7SbEHrEa8QeQCGdml9KQ4bk")
+ .issuer("http://localhost:8080/one-login-local/")
+ .issueTime(Date())
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("vot", "Cl.Cm")
+ .claim("nonce", lastReceivedNonce)
+ .claim("vtm", "http://localhost:8080/one-login-local/trustmark")
+ .claim("sid", "dX5xv0XgHh6yfD1xy-ss_1EDK0I")
+
+ val signedJwt = SignedJWT(headerBuilder.build(), claimSetBBuilder.build())
+
+ signedJwt.sign(ECDSASigner(ecKey))
+
+ return signedJwt.serialize()
+ }
+}
diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/jwks.json b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/jwks.json
new file mode 100644
index 000000000..e112d3479
--- /dev/null
+++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/jwks.json
@@ -0,0 +1,14 @@
+{
+ "keys":
+ [
+ {
+ "kty": "EC",
+ "use": "sig",
+ "crv": "P-256",
+ "kid": "keyId",
+ "x": "publicJWK_x",
+ "y": "publicJWK_y",
+ "alg": "ES256"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/openid-configuration.json b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/openid-configuration.json
new file mode 100644
index 000000000..148955862
--- /dev/null
+++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/openid-configuration.json
@@ -0,0 +1,71 @@
+{
+ "authorization_endpoint": "http://localhost:8080/one-login-local/authorize",
+ "token_endpoint": "http://localhost:8080/one-login-local/token",
+ "registration_endpoint": "http://localhost:8080/one-login-local/connect/register",
+ "issuer": "http://localhost:8080/one-login-local/",
+ "jwks_uri": "http://localhost:8080/one-login-local/.well-known/jwks.json",
+ "scopes_supported": [
+ "openid",
+ "email",
+ "phone",
+ "offline_access"
+ ],
+ "response_types_supported": [
+ "code"
+ ],
+ "grant_types_supported": [
+ "authorization_code"
+ ],
+ "token_endpoint_auth_methods_supported": [
+ "private_key_jwt",
+ "client_secret_post"
+ ],
+ "token_endpoint_auth_signing_alg_values_supported": [
+ "RS256",
+ "RS384",
+ "RS512",
+ "PS256",
+ "PS384",
+ "PS512"
+ ],
+ "ui_locales_supported": [
+ "en",
+ "cy"
+ ],
+ "service_documentation": "https://docs.sign-in.service.gov.uk/",
+ "op_policy_uri": "https://signin.integration.account.gov.uk/privacy-notice",
+ "op_tos_uri": "https://signin.integration.account.gov.uk/terms-and-conditions",
+ "request_parameter_supported": true,
+ "trustmarks": "http://localhost:8080/one-login-local/trustmark",
+ "subject_types_supported": [
+ "public",
+ "pairwise"
+ ],
+ "userinfo_endpoint": "http://localhost:8080/one-login-local/userinfo",
+ "end_session_endpoint": "http://localhost:8080/one-login-local/logout",
+ "id_token_signing_alg_values_supported": [
+ "ES256",
+ "RS256"
+ ],
+ "claim_types_supported": [
+ "normal"
+ ],
+ "claims_supported": [
+ "sub",
+ "email",
+ "email_verified",
+ "phone_number",
+ "phone_number_verified",
+ "wallet_subject_id",
+ "https://vocab.account.gov.uk/v1/passport",
+ "https://vocab.account.gov.uk/v1/socialSecurityRecord",
+ "https://vocab.account.gov.uk/v1/drivingPermit",
+ "https://vocab.account.gov.uk/v1/coreIdentityJWT",
+ "https://vocab.account.gov.uk/v1/address",
+ "https://vocab.account.gov.uk/v1/inheritedIdentityJWT",
+ "https://vocab.account.gov.uk/v1/returnCode"
+ ],
+ "request_uri_parameter_supported": false,
+ "backchannel_logout_supported": true,
+ "backchannel_logout_session_supported": false
+}
diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/token.json b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/token.json
new file mode 100644
index 000000000..eb54b5135
--- /dev/null
+++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/token.json
@@ -0,0 +1,6 @@
+{
+ "access_token": "SlAV32hkKG",
+ "token_type": "Bearer",
+ "expires_in": 180,
+ "id_token": "idToken"
+}
\ No newline at end of file
diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/userInfo.json b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/userInfo.json
new file mode 100644
index 000000000..5e35d068d
--- /dev/null
+++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLoginResponses/userInfo.json
@@ -0,0 +1,7 @@
+{
+ "sub": "userId",
+ "email": "userEmail",
+ "email_verified": true,
+ "phone_number": "userNumber",
+ "phone_number_verified": true
+}
\ No newline at end of file
diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml
index 3d070f154..4b1d313e4 100644
--- a/src/main/resources/application-local.yml
+++ b/src/main/resources/application-local.yml
@@ -36,19 +36,6 @@ spring:
port: 6379
password: notarealpassword
- security:
- oauth2:
- client:
- registration:
- one-login:
- client-id: l0AE7SbEHrEa8QeQCGdml9KQ4bk
- client-authentication-method: private_key_jwt
- authorization-grant-type: authorization_code
- scope: openid
- provider:
- one-login:
- issuer-uri: https://oidc.integration.account.gov.uk/
-
one-login:
jwt:
public.key: classpath:public_key.pem
@@ -68,6 +55,51 @@ local:
---
+spring:
+ config:
+ activate:
+ on-profile: local-no-auth
+
+ security:
+ oauth2:
+ client:
+ registration:
+ one-login:
+ client-id: l0AE7SbEHrEa8QeQCGdml9KQ4bk
+ client-authentication-method: private_key_jwt
+ authorization-grant-type: authorization_code
+ scope: openid
+ redirect-uri: http://localhost:8080/login/oauth2/code/one-login
+ provider:
+ one-login:
+ authorization-uri: http://localhost:8080/one-login-local/authorize
+ token-uri: http://localhost:8080/one-login-local/token
+ jwk-set-uri: http://localhost:8080/one-login-local/.well-known/jwks.json
+ user-info-uri: http://localhost:8080//one-login-local/userinfo
+ userNameAttribute: "sub"
+
+---
+
+spring:
+ config:
+ activate:
+ on-profile: local-auth
+
+ security:
+ oauth2:
+ client:
+ registration:
+ one-login:
+ client-id: l0AE7SbEHrEa8QeQCGdml9KQ4bk
+ client-authentication-method: private_key_jwt
+ authorization-grant-type: authorization_code
+ scope: openid
+ provider:
+ one-login:
+ issuer-uri: https://oidc.integration.account.gov.uk/
+
+---
+
spring:
config:
activate:
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 7953ddef2..b73476588 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -23,19 +23,6 @@ spring:
port: ${ELASTICACHE_PORT}
password: ${ELASTICACHE_PASSWORD}
- security:
- oauth2:
- client:
- registration:
- one-login:
- client-id: ${ONE_LOGIN_CLIENT_ID}
- client-authentication-method: private_key_jwt
- authorization-grant-type: authorization_code
- scope: openid
- provider:
- one-login:
- issuer-uri: ${ONE_LOGIN_ISSUER_URL}
-
one-login:
jwt:
public.key: ${ONE_LOGIN_PUBLIC_KEY}
@@ -50,4 +37,24 @@ os-places:
server:
error:
- path: /error
\ No newline at end of file
+ path: /error
+
+---
+
+spring:
+ config:
+ activate:
+ on-profile: default
+
+ security:
+ oauth2:
+ client:
+ registration:
+ one-login:
+ client-id: ${ONE_LOGIN_CLIENT_ID}
+ client-authentication-method: private_key_jwt
+ authorization-grant-type: authorization_code
+ scope: openid
+ provider:
+ one-login:
+ issuer-uri: ${ONE_LOGIN_ISSUER_URL}
diff --git a/src/main/resources/data-local.sql b/src/main/resources/data-local.sql
index edaff343b..264e5373e 100644
--- a/src/main/resources/data-local.sql
+++ b/src/main/resources/data-local.sql
@@ -2,6 +2,7 @@ INSERT INTO one_login_user (id, name, email, created_date, last_modified_date)
VALUES ('urn:fdc:gov.uk:2022:ABCDE', 'Bob T Builder', 'bobthebuilder@gmail.com', '09/13/24', '09/13/24'),
('urn:fdc:gov.uk:2022:FGHIJ', 'Anne Other', 'Anne.Other@hotmail.com', '09/13/24', '09/13/24'),
('urn:fdc:gov.uk:2022:KLMNO', 'Ford Prefect', 'Ford.Prefect@hotmail.com', '10/07/24', '10/07/24'),
+ ('urn:fdc:gov.uk:2022:UVWXY', 'Mock User', 'test@example.com', '10/14/24', '10/14/24');
('urn:fdc:gov.uk:2022:PQRST', 'Arthur Dent', 'Arthur.Dent@hotmail.com', '10/09/24', '10/09/24'),
('urn:fdc:gov.uk:2022:07lXHJeQwE0k5PZO7w_PQF425vT8T7e63MrvyPYNSoI', 'Jasmin Conterio', 'jasmin.conterio@softwire.com', '10/07/24', '10/07/24'),
('urn:fdc:gov.uk:2022:mGHDySEVfCsvfvc6lVWf6Qt9Dv0ZxPQWKoEzcjnBlUo','PRSDB Landlord', 'Team-PRSDB+landlord@softwire.com','10/15/24','10/15/24'),
@@ -11,6 +12,7 @@ VALUES ('urn:fdc:gov.uk:2022:ABCDE', 'Bob T Builder', 'bobthebuilder@gmail.com',
INSERT INTO landlord_user (subject_identifier, phone_number, date_of_birth, created_date, last_modified_date)
VALUES ('urn:fdc:gov.uk:2022:ABCDE', '07712345678', '01/01/00', '09/13/24', '09/13/24'),
('urn:fdc:gov.uk:2022:FGHIJ', '07811111111', '11/23/98', '09/13/24', '09/13/24'),
+ ('urn:fdc:gov.uk:2022:UVWXY', '01406946277', '06/16/84', '10/14/24', '10/14/24');
('urn:fdc:gov.uk:2022:07lXHJeQwE0k5PZO7w_PQF425vT8T7e63MrvyPYNSoI', '01223456789','02/01/00','10/09/24', '10/09/24'),
('urn:fdc:gov.uk:2022:mGHDySEVfCsvfvc6lVWf6Qt9Dv0ZxPQWKoEzcjnBlUo','01223456789','03/05/00','10/15/24', '10/09/24');
@@ -19,7 +21,8 @@ VALUES ('Betelgeuse','09/13/24', '09/13/24');
INSERT INTO local_authority_user (subject_identifier, is_manager, local_authority_id, created_date, last_modified_date)
VALUES ('urn:fdc:gov.uk:2022:KLMNO',true, 1,'10/07/24', '10/07/24'),
+ ('urn:fdc:gov.uk:2022:UVWXY', true, 1, '10/14/24', '10/14/24');
('urn:fdc:gov.uk:2022:PQRST',false, 1,'10/09/24', '10/09/24'),
('urn:fdc:gov.uk:2022:07lXHJeQwE0k5PZO7w_PQF425vT8T7e63MrvyPYNSoI',true,1,'10/09/24', '10/09/24'),
('urn:fdc:gov.uk:2022:n93slCXHsxJ9rU6-AFM0jFIctYQjYf0KN9YVuJT-cao',true,1,'10/15/24','10/15/24'),
- ('urn:fdc:gov.uk:2022:cgVX2oJWKHMwzm8Gzx25CSoVXixVS0rw32Sar4Om8vQ',false,1,'10/15/24','10/15/24');
\ No newline at end of file
+ ('urn:fdc:gov.uk:2022:cgVX2oJWKHMwzm8Gzx25CSoVXixVS0rw32Sar4Om8vQ',false,1,'10/15/24','10/15/24');