From f800a41c2ac5225ee56f4d3bd2899bebe60ded71 Mon Sep 17 00:00:00 2001 From: Takashi Norimatsu Date: Fri, 14 Jun 2024 16:56:01 +0900 Subject: [PATCH] wip Signed-off-by: Takashi Norimatsu --- .../oid4vc/issuance/OID4VCIssuerEndpoint.java | 4 +- .../signing/OID4VCIssuerEndpointTest.java | 118 ++++++++++++++---- 2 files changed, 98 insertions(+), 24 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCIssuerEndpoint.java b/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCIssuerEndpoint.java index f32eecb28f19..ca9e7d0308e0 100644 --- a/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCIssuerEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCIssuerEndpoint.java @@ -264,8 +264,10 @@ private void checkScope(CredentialRequest credentialRequestVO) { if (vcIssuanceFlow == null || !vcIssuanceFlow.equals(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)) { // authz code flow ClientModel client = clientSession.getClient(); + client.getAttributes().forEach((i,j)->System.out.println("GGGGGGGGGG OID4VCIssuerEndpoint : key = " + i + " value = " + j)); String credentialIdentifier = credentialRequestVO.getCredentialIdentifier(); - String scope = client.getAttributes().get(credentialIdentifier); + String scope = client.getAttributes().get("vc." + credentialIdentifier + ".scope"); + System.out.println("GGGGGGGGGG OID4VCIssuerEndpoint : checkScope : credentialIdentifier = " + credentialIdentifier + " scope = " + scope); AccessToken accessToken = bearerTokenAuthenticator.authenticate().getToken(); if (Arrays.stream(accessToken.getScope().split(" ")).sequential().noneMatch(i->i.equals(scope))) { LOGGER.debugf("Scope check failure: credentialIdentifier = %s, required scope = %s, scope in access token = %s.", credentialIdentifier, scope, accessToken.getScope()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java index 75ba0ee6587e..ae9cd8d84992 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java @@ -59,8 +59,6 @@ import org.keycloak.protocol.oid4vc.model.CredentialRequest; import org.keycloak.protocol.oid4vc.model.CredentialResponse; import org.keycloak.protocol.oid4vc.model.CredentialsOffer; -import org.keycloak.protocol.oid4vc.model.ErrorResponse; -import org.keycloak.protocol.oid4vc.model.ErrorType; import org.keycloak.protocol.oid4vc.model.Format; import org.keycloak.protocol.oid4vc.model.OfferUriType; import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant; @@ -374,15 +372,6 @@ public void testRequestCredential() { })); } - public static ClientResource findClientByClientId(RealmResource realm, String clientId) { - for (ClientRepresentation c : realm.clients().findAll()) { - if (clientId.equals(c.getClientId())) { - return realm.clients().get(c.getId()); - } - } - return null; - } - // Tests the complete flow from // 1. Retrieving the credential-offer-uri // 2. Using the uri to get the actual credential offer @@ -457,27 +446,50 @@ public void testCredentialIssuance() throws Exception { }); } + private ClientResource findClientByClientId(RealmResource realm, String clientId) { + for (ClientRepresentation c : realm.clients().findAll()) { + if (clientId.equals(c.getClientId())) { + return realm.clients().get(c.getId()); + } + } + return null; + } - // Tests the AuthZCode complete flow without scope from - // 1. Get authorization code without scope specified by wallet - // 2. Using the code to get access token - // 3. Get the credential configuration id from issuer metadata at .wellKnown - // 4. With the access token, get the credential - @Test - public void testCredentialIssuanceWithAuthZCode() throws Exception { + private String registerOptionalClientScope(String scopeName) { ClientScopeRepresentation clientScope = new ClientScopeRepresentation(); - clientScope.setName("scope-test-credential"); + clientScope.setName(scopeName); clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); Response res = testRealm().clientScopes().create(clientScope); String scopeId = ApiUtil.getCreatedId(res); - getCleanup().addClientScopeId(scopeId); + getCleanup().addClientScopeId(scopeId); // automatically removed when a test method is finished. res.close(); + return scopeId; + } - ClientResource clientResource = findClientByClientId(testRealm(), "test-app"); + private void assignOptionalClientScopeToClient(String scopeId, String clientId) { + ClientResource clientResource = findClientByClientId(testRealm(), clientId); ClientRepresentation clientRepresentation = clientResource.toRepresentation(); clientRepresentation.getAttributes().put("test-credential","scope-test-credential"); clientResource.update(clientRepresentation); clientResource.addOptionalClientScope(scopeId); + } + + // Tests the AuthZCode complete flow without scope from + // 1. Get authorization code without scope specified by wallet + // 2. Using the code to get access token + // 3. Get the credential configuration id from issuer metadata at .wellKnown + // 4. With the access token, get the credential + @Test + public void testCredentialIssuanceWithAuthZCodeWithScopeMatched() throws Exception { + // use pre-registered client for this test class whose clientId is "did:web:test.org" by OID4VCTest.getTestClient + // This client registered credential identifier named "test-credential" + // Its scope value is "VerifiableCredential" + + // 1. register optional client scope + String scopeId = registerOptionalClientScope("VerifiableCredential"); + + // 2. assign registered optional client scope + assignOptionalClientScopeToClient(scopeId, "did:web:test.org"); // pre-registered client for this test class try (Client client = AdminClientUtil.createResteasyClient()) { UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT); @@ -486,7 +498,7 @@ public void testCredentialIssuanceWithAuthZCode() throws Exception { // 1. Get authoriZation code without scope specified by wallet // 2. Using the code to get accesstoken - String token = getBearerToken(oauth.openid(false).scope("scope-test-credential")); + String token = getBearerToken(oauth.clientId("did:web:test.org").openid(false).scope("VerifiableCredential")); // 3. Get the credential configuration id from issuer metadata at .wellKnown try (Response discoveryResponse = oid4vciDiscoveryTarget.request().get()) { @@ -525,7 +537,7 @@ public void testCredentialIssuanceWithAuthZCode() throws Exception { } // clean-up - clientResource.removeOptionalClientScope(scopeId); + //clientResource.removeOptionalClientScope(scopeId); } // Tests the AuthZCode complete flow without scope from @@ -589,6 +601,66 @@ public void testCredentialIssuanceWithAuthZCodeScopeUnmatched() throws Exception clientResource.removeOptionalClientScope(scopeId); } + // Tests the AuthZCode complete flow without scope from + // 1. Get authorization code without scope specified by wallet + // 2. Using the code to get access token + // 3. Get the credential configuration id from issuer metadata at .wellKnown + // 4. With the access token, get the credential + @Test + public void testCredentialIssuanceWithoutScope() throws Exception { + ClientScopeRepresentation clientScope = new ClientScopeRepresentation(); + clientScope.setName("scope-test-credential"); + clientScope.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + Response res = testRealm().clientScopes().create(clientScope); + String scopeId = ApiUtil.getCreatedId(res); + getCleanup().addClientScopeId(scopeId); + res.close(); + + ClientResource clientResource = findClientByClientId(testRealm(), "test-app"); + ClientRepresentation clientRepresentation = clientResource.toRepresentation(); + clientRepresentation.getAttributes().put("test-credential", "different-scope-test-credential"); + clientResource.update(clientRepresentation); + clientResource.addOptionalClientScope(scopeId); + + try (Client client = AdminClientUtil.createResteasyClient()) { + UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT); + URI oid4vciDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder).build(TEST_REALM_NAME, OID4VCIssuerWellKnownProviderFactory.PROVIDER_ID); + WebTarget oid4vciDiscoveryTarget = client.target(oid4vciDiscoveryUri); + + // 1. Get authoriZation code without scope specified by wallet + // 2. Using the code to get accesstoken + String token = getBearerToken(oauth.openid(false).scope(null)); + + // 3. Get the credential configuration id from issuer metadata at .wellKnown + try (Response discoveryResponse = oid4vciDiscoveryTarget.request().get()) { + CredentialIssuer oid4vciIssuerConfig = JsonSerialization.readValue(discoveryResponse.readEntity(String.class), CredentialIssuer.class); + assertEquals(200, discoveryResponse.getStatus()); + assertEquals(getRealmPath(TEST_REALM_NAME), oid4vciIssuerConfig.getCredentialIssuer()); + assertEquals(getBasePath(TEST_REALM_NAME) + "credential", oid4vciIssuerConfig.getCredentialEndpoint()); + + // 4. With the access token, get the credential + try (Client clientForCredentialRequest = AdminClientUtil.createResteasyClient()) { + UriBuilder credentialUriBuilder = UriBuilder.fromUri(oid4vciIssuerConfig.getCredentialEndpoint()); + URI credentialUri = credentialUriBuilder.build(); + WebTarget credentialTarget = clientForCredentialRequest.target(credentialUri); + + CredentialRequest request = new CredentialRequest(); + request.setFormat(oid4vciIssuerConfig.getCredentialsSupported().get("test-credential").getFormat()); + request.setCredentialIdentifier(oid4vciIssuerConfig.getCredentialsSupported().get("test-credential").getId()); + + assertEquals("jwt_vc", oid4vciIssuerConfig.getCredentialsSupported().get("test-credential").getFormat().toString()); + assertEquals("test-credential", oid4vciIssuerConfig.getCredentialsSupported().get("test-credential").getId()); + + try (Response response = credentialTarget.request().header(HttpHeaders.AUTHORIZATION, "bearer " + token).post(Entity.json(request))) { + assertEquals(400, response.getStatus()); + } + } + } + } + + // clean-up + clientResource.removeOptionalClientScope(scopeId); + } private static String prepareNonce(AppAuthManager.BearerTokenAuthenticator authenticator, String note) { String nonce = SecretGenerator.getInstance().randomString();