From 12d5353db4322d046149ec9d9b31fe091ac248aa Mon Sep 17 00:00:00 2001 From: Kiran Randhawa Kukar Date: Fri, 11 Oct 2024 17:13:39 +0100 Subject: [PATCH 1/8] PRSD-NONE: mock one login endpoint --- .../webapp/config/CustomSecurityConfig.kt | 6 + .../local/api/oneLoginMock/JWTBuilder.kt | 59 +++++ .../api/oneLoginMock/LastReceivedNonce.kt | 7 + .../oneLoginMock/MockOneLoginController.kt | 207 ++++++++++++++++++ src/main/resources/application-local.yml | 7 +- src/main/resources/application.yml | 24 +- 6 files changed, 301 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/JWTBuilder.kt create mode 100644 src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/LastReceivedNonce.kt create mode 100644 src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/MockOneLoginController.kt 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 a5cba4e37..62e2dec3b 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,9 +33,15 @@ class CustomSecurityConfig { .permitAll() .requestMatchers("/register-as-a-landlord") .permitAll() + .requestMatchers("/one-login-local/**") + .permitAll() .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/oneLoginMock/JWTBuilder.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/JWTBuilder.kt new file mode 100644 index 000000000..246bb38c3 --- /dev/null +++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/JWTBuilder.kt @@ -0,0 +1,59 @@ +package uk.gov.communities.prsdb.webapp.local.api.oneLoginMock + +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 java.time.Instant +import java.util.Date + +class JWTBuilder { + companion object { + val ecKey = generateSecretKey() + + private fun generateSecretKey(): ECKey { + val ecKey = + ECKeyGenerator(Curve.P_256) + .keyUse(KeyUse.SIGNATURE) + .keyID("6a4bc1e3-9530-4d5b-90c5-10dcf3ffccd0") + .algorithm(Algorithm("ES256")) + .generate() + + return ecKey + } + } + + fun getIdToken(): String { + val headerBuilder: JWSHeader.Builder = + JWSHeader + .Builder(JWSAlgorithm.ES256) + .type(JOSEObjectType.JWT) + .jwk(ecKey.toPublicJWK()) + + val claimSetBBuilder: JWTClaimsSet.Builder = + JWTClaimsSet + .Builder() + .subject("urn:fdc:gov.uk:2022:ABCDE") + .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.nonce) + .claim("vtm", "http://localhost:8080/one-login-local/trustmark") + .claim("sid", "Nzk0M2NiNWUtYWZhNC00ZjZmLThiOTItNzUxNjcyNjUwOGNl") + + val signedJwt: 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/oneLoginMock/LastReceivedNonce.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/LastReceivedNonce.kt new file mode 100644 index 000000000..ce4143283 --- /dev/null +++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/LastReceivedNonce.kt @@ -0,0 +1,7 @@ +package uk.gov.communities.prsdb.webapp.local.api.oneLoginMock + +class LastReceivedNonce { + companion object { + var nonce: String? = null + } +} diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/MockOneLoginController.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/MockOneLoginController.kt new file mode 100644 index 000000000..1198a74f3 --- /dev/null +++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/MockOneLoginController.kt @@ -0,0 +1,207 @@ +package uk.gov.communities.prsdb.webapp.local.api.oneLoginMock + +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 java.net.URI + +@Profile("local") +@RestController +@RequestMapping("/one-login-local") +class MockOneLoginController( + private var jwtBuilder: JWTBuilder = JWTBuilder(), +) { + @GetMapping("/.well-known/openid-configuration") + fun example(): String = + "{\n" + + "\"authorization_endpoint\": \"http://localhost:8080/one-login-local/authorize\",\n" + + "\"token_endpoint\": \"http://localhost:8080/one-login-local/token\",\n" + + "\"registration_endpoint\": \"http://localhost:8080/one-login-local/connect/register\",\n" + + "\"issuer\": \"http://localhost:8080/one-login-local/\",\n" + + "\"jwks_uri\": \"http://localhost:8080/one-login-local/.well-known/jwks.json\",\n" + + "\"scopes_supported\": [\n" + + "\"openid\",\n" + + "\"email\",\n" + + "\"phone\",\n" + + "\"offline_access\"\n" + + "],\n" + + "\"response_types_supported\": [\n" + + "\"code\"\n" + + "],\n" + + "\"grant_types_supported\": [\n" + + "\"authorization_code\"\n" + + "],\n" + + "\"token_endpoint_auth_methods_supported\": [\n" + + "\"private_key_jwt\",\n" + + "\"client_secret_post\"\n" + + "],\n" + + "\"token_endpoint_auth_signing_alg_values_supported\": [\n" + + "\"RS256\",\n" + + "\"RS384\",\n" + + "\"RS512\",\n" + + "\"PS256\",\n" + + "\"PS384\",\n" + + "\"PS512\"\n" + + "],\n" + + "\"ui_locales_supported\": [\n" + + "\"en\",\n" + + "\"cy\"\n" + + "],\n" + + "\"service_documentation\": \"https://docs.sign-in.service.gov.uk/\",\n" + + "\"op_policy_uri\": \"https://signin.integration.account.gov.uk/privacy-notice\",\n" + + "\"op_tos_uri\": \"https://signin.integration.account.gov.uk/terms-and-conditions\",\n" + + "\"request_parameter_supported\": true,\n" + + "\"trustmarks\": \"http://localhost:8080/one-login-local/trustmark\",\n" + + "\"subject_types_supported\": [\n" + + "\"public\",\n" + + "\"pairwise\"\n" + + "],\n" + + "\"userinfo_endpoint\": \"http://localhost:8080/one-login-local/userinfo\",\n" + + "\"end_session_endpoint\": \"http://localhost:8080/one-login-local/logout\",\n" + + "\"id_token_signing_alg_values_supported\": [\n" + + "\"ES256\",\n" + + "\"RS256\"\n" + + "],\n" + + "\"claim_types_supported\": [\n" + + "\"normal\"\n" + + "],\n" + + "\"claims_supported\": [\n" + + "\"sub\",\n" + + "\"email\",\n" + + "\"email_verified\",\n" + + "\"phone_number\",\n" + + "\"phone_number_verified\",\n" + + "\"wallet_subject_id\",\n" + + "\"https://vocab.account.gov.uk/v1/passport\",\n" + + "\"https://vocab.account.gov.uk/v1/socialSecurityRecord\",\n" + + "\"https://vocab.account.gov.uk/v1/drivingPermit\",\n" + + "\"https://vocab.account.gov.uk/v1/coreIdentityJWT\",\n" + + "\"https://vocab.account.gov.uk/v1/address\",\n" + + "\"https://vocab.account.gov.uk/v1/inheritedIdentityJWT\",\n" + + "\"https://vocab.account.gov.uk/v1/returnCode\"\n" + + "],\n" + + "\"request_uri_parameter_supported\": false,\n" + + "\"backchannel_logout_supported\": true,\n" + + "\"backchannel_logout_session_supported\": false\n" + + "}\n" + + @GetMapping("/.well-known/jwks.json") + fun jwksjson(): String = + "{\n" + + "\"keys\": [\n" + + "{\n" + + "\"kty\": \"EC\",\n" + + "\"use\": \"sig\",\n" + + "\"crv\": \"P-256\",\n" + + "\"kid\": \"6a4bc1e3-9530-4d5b-90c5-10dcf3ffccd0\",\n" + + "\"x\": \"${JWTBuilder.ecKey.toPublicJWK().x}\",\n" + + "\"y\": \"${JWTBuilder.ecKey.toPublicJWK().y}\",\n" + + "\"alg\": \"ES256\"\n" + + "},\n" + + "{\n" + + "\"kty\": \"EC\",\n" + + "\"use\": \"sig\",\n" + + "\"crv\": \"P-256\",\n" + + "\"kid\": \"644af598b780f54106ca0f3c017341bc230c4f8373f35f32e18e3e40cc7acff6\",\n" + + "\"x\": \"5URVCgH4HQgkg37kiipfOGjyVft0R5CdjFJahRoJjEw\",\n" + + "\"y\": \"QzrvsnDy3oY1yuz55voaAq9B1M5tfhgW3FBjh_n_F0U\",\n" + + "\"alg\": \"ES256\"\n" + + "},\n" + + "{\n" + + "\"kty\": \"EC\",\n" + + "\"use\": \"sig\",\n" + + "\"crv\": \"P-256\",\n" + + "\"kid\": \"e1f5699d068448882e7866b49d24431b2f21bf1a8f3c2b2dde8f4066f0506f1b\",\n" + + "\"x\": \"BJnIZvnzJ9D_YRu5YL8a3CXjBaa5AxlX1xSeWDLAn9k\",\n" + + "\"y\": \"x4FU3lRtkeDukSWVJmDuw2nHVFVIZ8_69n4bJ6ik4bQ\",\n" + + "\"alg\": \"ES256\"\n" + + "}\n" + + "]\n" + + "}" + + @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 = nonce + val authorizationCode = "SplxlOBeZQQYbYS6WxSbIA" + val locationURI: URI = URI.create("$redirect_uri?code=$authorizationCode&state=$state") + + 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 { + // TODO data send to the jwtbuilder - subject: useridentifier, sid: session identifier + + val idToken: String = jwtBuilder.getIdToken() + + val responseBody = + "{\n" + + "\"access_token\": \"SlAV32hkKG\",\n" + + "\"token_type\": \"Bearer\",\n" + + "\"expires_in\": 180,\n" + + "\"id_token\": \"$idToken\"\n" + + "}" + + val responseBuild = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(responseBody) + + return responseBuild + } + + @GetMapping("/connect/register") + fun example4(): String = "Hello World" + + @GetMapping("/trustmark") + fun example5(): String = + "{\n" + + "\"idp\": \"http://localhost:8080//one-login-local/\",\n" + + "\"trustmark_provider\": \"http://localhost:8080//one-login-local/\",\n" + + "\"C\": [\n" + + "\"Cl\",\n" + + "\"Cl.Cm\"\n" + + "],\n" + + "\"P\": [\n" + + "\"P0\",\n" + + "\"PCL200\",\n" + + "\"PCL250\",\n" + + "\"P1\",\n" + + "\"P2\"\n" + + "]\n" + + "}" + + @GetMapping("/logout") + fun example6(): String = "Hello World" + + @GetMapping("/userinfo") + fun example7(): ResponseEntity { + val responseBody = + "{\n" + + " \"sub\": \"urn:fdc:gov.uk:2022:ABCDE\",\n" + + " \"email\": \"test@example.com\",\n" + + " \"email_verified\": true,\n" + + " \"phone_number\": \"01406946277\",\n" + + " \"phone_number_verified\": true\n" + + "}" + + val responseBuild = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(responseBody) + + return responseBuild + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 699d5b02f..1d4b01a48 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -45,9 +45,14 @@ spring: 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: - issuer-uri: https://oidc.integration.account.gov.uk/ + 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" one-login: jwt: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index decbe0ae8..8ba4aec6a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -23,6 +23,22 @@ spring: port: ${ELASTICACHE_PORT} password: ${ELASTICACHE_PASSWORD} +one-login: + jwt: + public.key: ${ONE_LOGIN_PUBLIC_KEY} + private.key: ${ONE_LOGIN_PRIVATE_KEY} + +server: + error: + path: /error + +--- + +spring: + config: + activate: + on-profile: default + security: oauth2: client: @@ -36,11 +52,3 @@ spring: one-login: issuer-uri: ${ONE_LOGIN_ISSUER_URL} -one-login: - jwt: - public.key: ${ONE_LOGIN_PUBLIC_KEY} - private.key: ${ONE_LOGIN_PRIVATE_KEY} - -server: - error: - path: /error \ No newline at end of file From d68dd04678d6a5f0bcf63ff4cd9c30123bb07957 Mon Sep 17 00:00:00 2001 From: Kiran Randhawa Kukar Date: Mon, 14 Oct 2024 14:35:16 +0100 Subject: [PATCH 2/8] PRSD refactor mock one login endpoint --- .../mockOneLogin/MockOneLoginController.kt | 63 +++++++ .../MockOneLoginHelper.kt} | 171 ++++++++---------- .../local/api/oneLoginMock/JWTBuilder.kt | 59 ------ .../api/oneLoginMock/LastReceivedNonce.kt | 7 - src/main/resources/data-local.sql | 9 +- 5 files changed, 149 insertions(+), 160 deletions(-) create mode 100644 src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginController.kt rename src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/{oneLoginMock/MockOneLoginController.kt => mockOneLogin/MockOneLoginHelper.kt} (59%) delete mode 100644 src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/JWTBuilder.kt delete mode 100644 src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/LastReceivedNonce.kt diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginController.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginController.kt new file mode 100644 index 000000000..4967fddff --- /dev/null +++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginController.kt @@ -0,0 +1,63 @@ +package uk.gov.communities.prsdb.webapp.local.api.mockOneLogin + +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 java.net.URI + +@Profile("local") +@RestController +@RequestMapping("/one-login-local") +class MockOneLoginController( + private var helper: MockOneLoginHelper = MockOneLoginHelper(), +) { + @GetMapping("/.well-known/openid-configuration") + fun openidConfiguration(): String = helper.getOpenidConfigurationResponse() + + @GetMapping("/.well-known/jwks.json") + fun jwksJson(): String = helper.getJwksJsonResponse() + + @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 { + helper.lastReceivedNonce = nonce + val locationURI: URI = URI.create("$redirect_uri?code=${helper.authorizationCode}&state=$state") + + 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 = helper.getTokenResponse() + + val responseBuild = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(responseBody) + + return responseBuild + } + + @GetMapping("/userinfo") + fun userInfo(): ResponseEntity { + val responseBody = helper.getUserInfoResponse() + + val responseBuild = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(responseBody) + + return responseBuild + } +} diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/MockOneLoginController.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginHelper.kt similarity index 59% rename from src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/MockOneLoginController.kt rename to src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginHelper.kt index 1198a74f3..d615eb367 100644 --- a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/MockOneLoginController.kt +++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginHelper.kt @@ -1,23 +1,71 @@ -package uk.gov.communities.prsdb.webapp.local.api.oneLoginMock - -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 java.net.URI - -@Profile("local") -@RestController -@RequestMapping("/one-login-local") -class MockOneLoginController( - private var jwtBuilder: JWTBuilder = JWTBuilder(), -) { - @GetMapping("/.well-known/openid-configuration") - fun example(): String = +package uk.gov.communities.prsdb.webapp.local.api.mockOneLogin + +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 java.time.Instant +import java.util.Date + +class MockOneLoginHelper { + companion object { + val ecKey = generateSecretKey() + + private fun generateSecretKey(): ECKey { + val ecKey = + ECKeyGenerator(Curve.P_256) + .keyUse(KeyUse.SIGNATURE) + .keyID("6a4bc1e3-9530-4d5b-90c5-10dcf3ffccd0") + .algorithm(Algorithm("ES256")) + .generate() + + return ecKey + } + } + + private val userId = "urn:fdc:gov.uk:2022:PQRST" + private val userEmail = "julia.jones@hotmail.com" + private val userNumber = "07123456789" + + val authorizationCode = "SplxlOBeZQQYbYS6WxSbIA" + + var lastReceivedNonce: String? = null + + 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", "Nzk0M2NiNWUtYWZhNC00ZjZmLThiOTItNzUxNjcyNjUwOGNl") + + val signedJwt = SignedJWT(headerBuilder.build(), claimSetBBuilder.build()) + + signedJwt.sign(ECDSASigner(ecKey)) + + return signedJwt.serialize() + } + + fun getOpenidConfigurationResponse(): String = "{\n" + "\"authorization_endpoint\": \"http://localhost:8080/one-login-local/authorize\",\n" + "\"token_endpoint\": \"http://localhost:8080/one-login-local/token\",\n" + @@ -90,8 +138,7 @@ class MockOneLoginController( "\"backchannel_logout_session_supported\": false\n" + "}\n" - @GetMapping("/.well-known/jwks.json") - fun jwksjson(): String = + fun getJwksJsonResponse(): String = "{\n" + "\"keys\": [\n" + "{\n" + @@ -99,8 +146,8 @@ class MockOneLoginController( "\"use\": \"sig\",\n" + "\"crv\": \"P-256\",\n" + "\"kid\": \"6a4bc1e3-9530-4d5b-90c5-10dcf3ffccd0\",\n" + - "\"x\": \"${JWTBuilder.ecKey.toPublicJWK().x}\",\n" + - "\"y\": \"${JWTBuilder.ecKey.toPublicJWK().y}\",\n" + + "\"x\": \"${ecKey.toPublicJWK().x}\",\n" + + "\"y\": \"${ecKey.toPublicJWK().y}\",\n" + "\"alg\": \"ES256\"\n" + "},\n" + "{\n" + @@ -124,33 +171,8 @@ class MockOneLoginController( "]\n" + "}" - @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 = nonce - val authorizationCode = "SplxlOBeZQQYbYS6WxSbIA" - val locationURI: URI = URI.create("$redirect_uri?code=$authorizationCode&state=$state") - - 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 { - // TODO data send to the jwtbuilder - subject: useridentifier, sid: session identifier - - val idToken: String = jwtBuilder.getIdToken() + fun getTokenResponse(): String { + val idToken: String = getIdToken() val responseBody = "{\n" + @@ -160,48 +182,15 @@ class MockOneLoginController( "\"id_token\": \"$idToken\"\n" + "}" - val responseBuild = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(responseBody) - - return responseBuild + return responseBody } - @GetMapping("/connect/register") - fun example4(): String = "Hello World" - - @GetMapping("/trustmark") - fun example5(): String = + fun getUserInfoResponse(): String = "{\n" + - "\"idp\": \"http://localhost:8080//one-login-local/\",\n" + - "\"trustmark_provider\": \"http://localhost:8080//one-login-local/\",\n" + - "\"C\": [\n" + - "\"Cl\",\n" + - "\"Cl.Cm\"\n" + - "],\n" + - "\"P\": [\n" + - "\"P0\",\n" + - "\"PCL200\",\n" + - "\"PCL250\",\n" + - "\"P1\",\n" + - "\"P2\"\n" + - "]\n" + + " \"sub\": \"$userId\",\n" + + " \"email\": \"$userEmail\",\n" + + " \"email_verified\": true,\n" + + " \"phone_number\": \"$userNumber\",\n" + + " \"phone_number_verified\": true\n" + "}" - - @GetMapping("/logout") - fun example6(): String = "Hello World" - - @GetMapping("/userinfo") - fun example7(): ResponseEntity { - val responseBody = - "{\n" + - " \"sub\": \"urn:fdc:gov.uk:2022:ABCDE\",\n" + - " \"email\": \"test@example.com\",\n" + - " \"email_verified\": true,\n" + - " \"phone_number\": \"01406946277\",\n" + - " \"phone_number_verified\": true\n" + - "}" - - val responseBuild = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(responseBody) - - return responseBuild - } } diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/JWTBuilder.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/JWTBuilder.kt deleted file mode 100644 index 246bb38c3..000000000 --- a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/JWTBuilder.kt +++ /dev/null @@ -1,59 +0,0 @@ -package uk.gov.communities.prsdb.webapp.local.api.oneLoginMock - -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 java.time.Instant -import java.util.Date - -class JWTBuilder { - companion object { - val ecKey = generateSecretKey() - - private fun generateSecretKey(): ECKey { - val ecKey = - ECKeyGenerator(Curve.P_256) - .keyUse(KeyUse.SIGNATURE) - .keyID("6a4bc1e3-9530-4d5b-90c5-10dcf3ffccd0") - .algorithm(Algorithm("ES256")) - .generate() - - return ecKey - } - } - - fun getIdToken(): String { - val headerBuilder: JWSHeader.Builder = - JWSHeader - .Builder(JWSAlgorithm.ES256) - .type(JOSEObjectType.JWT) - .jwk(ecKey.toPublicJWK()) - - val claimSetBBuilder: JWTClaimsSet.Builder = - JWTClaimsSet - .Builder() - .subject("urn:fdc:gov.uk:2022:ABCDE") - .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.nonce) - .claim("vtm", "http://localhost:8080/one-login-local/trustmark") - .claim("sid", "Nzk0M2NiNWUtYWZhNC00ZjZmLThiOTItNzUxNjcyNjUwOGNl") - - val signedJwt: 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/oneLoginMock/LastReceivedNonce.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/LastReceivedNonce.kt deleted file mode 100644 index ce4143283..000000000 --- a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/oneLoginMock/LastReceivedNonce.kt +++ /dev/null @@ -1,7 +0,0 @@ -package uk.gov.communities.prsdb.webapp.local.api.oneLoginMock - -class LastReceivedNonce { - companion object { - var nonce: String? = null - } -} diff --git a/src/main/resources/data-local.sql b/src/main/resources/data-local.sql index 3bb68b73a..0099d65a9 100644 --- a/src/main/resources/data-local.sql +++ b/src/main/resources/data-local.sql @@ -1,14 +1,17 @@ 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:KLMNO', 'Ford Prefect', 'Ford.Prefect@hotmail.com', '10/07/24', '10/07/24'), + ('urn:fdc:gov.uk:2022:PQRST', 'Julia Jones', 'julia.jones@hotmail.com', '10/14/24', '10/14/24'); 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:FGHIJ', '07811111111', '11/23/98', '09/13/24', '09/13/24'), + ('urn:fdc:gov.uk:2022:PQRST', '07123456789', '06/16/84', '10/14/24', '10/14/24'); INSERT INTO local_authority (name, created_date, last_modified_date) 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') \ No newline at end of file +VALUES ('urn:fdc:gov.uk:2022:KLMNO',true, 1,'10/07/24', '10/07/24'), + ('urn:fdc:gov.uk:2022:PQRST', true, 1, '10/14/24', '10/14/24'); \ No newline at end of file From 39f2f29e4abd3a4d7777ebe2b78bb6a45a0815b3 Mon Sep 17 00:00:00 2001 From: Kiran Randhawa Kukar Date: Mon, 14 Oct 2024 15:05:03 +0100 Subject: [PATCH 3/8] PRSD added mock one login info to README --- ReadMe.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ReadMe.md b/ReadMe.md index fe55eefb0..ba38bd2a2 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -80,4 +80,13 @@ Database migrations run at deployment time for all non-local depl the `flywayMigrate` Gradle task. When developing locally using the `local` profile the migrations will run at application start up. If you are using the `local` launch profile in IntelliJ, this will also run the `flywayClean` task 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. \ No newline at end of file +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` profile this will be available, when you attempt to login, This 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. From 227ffca85dcaf1461172e45d466e61b5eeba6364 Mon Sep 17 00:00:00 2001 From: Kiran Randhawa Kukar Date: Tue, 15 Oct 2024 11:46:53 +0100 Subject: [PATCH 4/8] PRSD-NONE: review mark ups --- .../MockOneLoginController.kt | 3 ++- .../api/{mockOneLogin => helper}/MockOneLoginHelper.kt | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) rename src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/{mockOneLogin => controller}/MockOneLoginController.kt (94%) rename src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/{mockOneLogin => helper}/MockOneLoginHelper.kt (97%) diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginController.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/controller/MockOneLoginController.kt similarity index 94% rename from src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginController.kt rename to src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/controller/MockOneLoginController.kt index 4967fddff..4d1c3c55b 100644 --- a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginController.kt +++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/controller/MockOneLoginController.kt @@ -1,4 +1,4 @@ -package uk.gov.communities.prsdb.webapp.local.api.mockOneLogin +package uk.gov.communities.prsdb.webapp.local.api.controller import org.springframework.context.annotation.Profile import org.springframework.http.MediaType @@ -8,6 +8,7 @@ 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 uk.gov.communities.prsdb.webapp.local.api.helper.MockOneLoginHelper import java.net.URI @Profile("local") diff --git a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginHelper.kt b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/helper/MockOneLoginHelper.kt similarity index 97% rename from src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginHelper.kt rename to src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/helper/MockOneLoginHelper.kt index d615eb367..14050fa99 100644 --- a/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/mockOneLogin/MockOneLoginHelper.kt +++ b/src/main/kotlin/uk/gov/communities/prsdb/webapp/local/api/helper/MockOneLoginHelper.kt @@ -1,4 +1,4 @@ -package uk.gov.communities.prsdb.webapp.local.api.mockOneLogin +package uk.gov.communities.prsdb.webapp.local.api.helper import com.nimbusds.jose.Algorithm import com.nimbusds.jose.JOSEObjectType @@ -13,16 +13,19 @@ import com.nimbusds.jwt.JWTClaimsSet import com.nimbusds.jwt.SignedJWT import java.time.Instant import java.util.Date +import java.util.UUID class MockOneLoginHelper { companion object { + val keyId = UUID.randomUUID().toString() + val ecKey = generateSecretKey() private fun generateSecretKey(): ECKey { val ecKey = ECKeyGenerator(Curve.P_256) .keyUse(KeyUse.SIGNATURE) - .keyID("6a4bc1e3-9530-4d5b-90c5-10dcf3ffccd0") + .keyID(keyId) .algorithm(Algorithm("ES256")) .generate() @@ -145,7 +148,7 @@ class MockOneLoginHelper { "\"kty\": \"EC\",\n" + "\"use\": \"sig\",\n" + "\"crv\": \"P-256\",\n" + - "\"kid\": \"6a4bc1e3-9530-4d5b-90c5-10dcf3ffccd0\",\n" + + "\"kid\": \"$keyId\",\n" + "\"x\": \"${ecKey.toPublicJWK().x}\",\n" + "\"y\": \"${ecKey.toPublicJWK().y}\",\n" + "\"alg\": \"ES256\"\n" + From e3975cfc8cc8313e6185c302fc6d687ccc3ccfe0 Mon Sep 17 00:00:00 2001 From: Kiran Randhawa Kukar Date: Fri, 18 Oct 2024 18:45:02 +0100 Subject: [PATCH 5/8] PRSD-NONE: review mark ups --- .run/local-with-auth.run.xml | 19 ++++ .run/local.run.xml | 7 +- ReadMe.md | 6 +- .../local/api/helper/MockOneLoginHelper.kt | 103 ++++++++---------- src/main/resources/application-local.yml | 41 +++++-- src/main/resources/data-local.sql | 6 +- 6 files changed, 110 insertions(+), 72 deletions(-) create mode 100644 .run/local-with-auth.run.xml 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 79c2fc010..b71e3b587 100644 --- a/.run/local.run.xml +++ b/.run/local.run.xml @@ -1,9 +1,14 @@ -