mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
[OID4VCI] Confine test realm setup to TestCase.configureTestRealm()
Signed-off-by: Thomas Diesler <tdiesler@ibm.com>
This commit is contained in:
committed by
Marek Posolda
parent
4036ddc837
commit
613e55d733
@@ -5,6 +5,8 @@ package org.keycloak;
|
||||
*/
|
||||
public class OID4VCConstants {
|
||||
|
||||
public static final String OID4VCI_ENABLED_ATTRIBUTE_KEY = "oid4vci.enabled";
|
||||
|
||||
// Sd-JWT constants
|
||||
public static final String SDJWT_DELIMITER = "~";
|
||||
public static final String SD_HASH = "sd_hash";
|
||||
|
||||
@@ -227,4 +227,8 @@ public final class Constants {
|
||||
|
||||
// Internal note for storing authorization details response in client session context
|
||||
public static final String AUTHORIZATION_DETAILS_RESPONSE = "authorization_details_response";
|
||||
|
||||
// This attribute can be used in a realm import definition to signal that default client scopes should be created in addition to the client scopes defined by the realm import definition.
|
||||
// When this attribute is omitted or set to false, the default client scopes are not created if at least one other client scope is defined by the realm import definition.
|
||||
public static final String CREATE_DEFAULT_CLIENT_SCOPES = "CreateDefaultClientScopes";
|
||||
}
|
||||
|
||||
+1
-3
@@ -137,6 +137,7 @@ import org.apache.http.client.methods.HttpOptions;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import static org.keycloak.OID4VCConstants.OID4VCI_ENABLED_ATTRIBUTE_KEY;
|
||||
import static org.keycloak.OID4VCConstants.OPENID_CREDENTIAL;
|
||||
import static org.keycloak.constants.OID4VCIConstants.CREDENTIAL_OFFER_CREATE;
|
||||
import static org.keycloak.constants.OID4VCIConstants.OID4VC_PROTOCOL;
|
||||
@@ -191,9 +192,6 @@ public class OID4VCIssuerEndpoint {
|
||||
// lifespan of the preAuthorizedCodes in seconds
|
||||
private final int preAuthorizedCodeLifeSpan;
|
||||
|
||||
// constant for the OID4VCI enabled attribute key
|
||||
public static final String OID4VCI_ENABLED_ATTRIBUTE_KEY = "oid4vci.enabled";
|
||||
|
||||
/**
|
||||
* Credential builders are responsible for initiating the production of
|
||||
* credentials in a specific format. Their output is an appropriate credential
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.keycloak.services.managers;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
@@ -79,6 +80,7 @@ import org.keycloak.utils.SMTPUtil;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
import static org.keycloak.constants.OID4VCIConstants.CREDENTIAL_OFFER_CREATE;
|
||||
import static org.keycloak.models.Constants.CREATE_DEFAULT_CLIENT_SCOPES;
|
||||
|
||||
/**
|
||||
* Per request object
|
||||
@@ -647,8 +649,7 @@ public class RealmManager {
|
||||
setupOfflineTokens(realm, rep);
|
||||
}
|
||||
|
||||
|
||||
if (rep.getClientScopes() == null) {
|
||||
if (isCreateDefaultClientScopes(rep)) {
|
||||
createDefaultClientScopes(realm);
|
||||
}
|
||||
|
||||
@@ -719,6 +720,12 @@ public class RealmManager {
|
||||
return realm;
|
||||
}
|
||||
|
||||
private boolean isCreateDefaultClientScopes(RealmRepresentation rep) {
|
||||
Map<String, String> attributes = rep.getAttributesOrEmpty();
|
||||
String createDefaultClientScopes = attributes.remove(CREATE_DEFAULT_CLIENT_SCOPES);
|
||||
return rep.getClientScopes() == null || Boolean.parseBoolean(createDefaultClientScopes);
|
||||
}
|
||||
|
||||
private String determineDefaultRoleName(RealmRepresentation rep) {
|
||||
String defaultRoleName = Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + rep.getRealm().toLowerCase();
|
||||
if (! hasRealmRole(rep, defaultRoleName)) {
|
||||
|
||||
+4
@@ -33,6 +33,10 @@ public class OID4VCClient {
|
||||
return new CredentialOfferRequest(client, nonce);
|
||||
}
|
||||
|
||||
public CredentialOfferResponse doCredentialOfferRequest(CredentialOfferURI credOfferUri) {
|
||||
return credentialOfferRequest(credOfferUri).send();
|
||||
}
|
||||
|
||||
public Oid4vcCredentialRequest credentialRequest() {
|
||||
return credentialRequest(new CredentialRequest());
|
||||
}
|
||||
|
||||
-1
@@ -41,7 +41,6 @@ public class PassThroughClientAuthenticator extends AbstractClientAuthenticator
|
||||
public static final String PROVIDER_ID = "testsuite-client-passthrough";
|
||||
|
||||
public static String clientId = "test-app";
|
||||
public static String namedClientId = "named-test-app";
|
||||
|
||||
// If this parameter is present in the HTTP request, the error will be thrown during authentication
|
||||
public static final String TEST_ERROR_PARAM = "test_error_param";
|
||||
|
||||
+3
-1
@@ -42,7 +42,9 @@ import static org.keycloak.testsuite.AbstractAdminTest.loadJson;
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
||||
*/
|
||||
public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest {
|
||||
|
||||
public static final String TEST_REALM_NAME = "test";
|
||||
public static final String clientId = "test-app";
|
||||
|
||||
protected RealmResource testRealm() {
|
||||
return adminClient.realm(TEST_REALM_NAME);
|
||||
@@ -60,7 +62,7 @@ public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest
|
||||
|
||||
protected ClientRepresentation findTestApp(RealmRepresentation testRealm) {
|
||||
for (ClientRepresentation client : testRealm.getClients()) {
|
||||
if (client.getClientId().equals("test-app")) return client;
|
||||
if (client.getClientId().equals(clientId)) return client;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
+13
-9
@@ -54,11 +54,8 @@ import org.junit.Test;
|
||||
|
||||
import static org.keycloak.OAuth2Constants.SCOPE_OPENID;
|
||||
import static org.keycloak.OID4VCConstants.OPENID_CREDENTIAL;
|
||||
import static org.keycloak.constants.OID4VCIConstants.CREDENTIAL_OFFER_CREATE;
|
||||
import static org.keycloak.protocol.oid4vc.model.ErrorType.INVALID_CREDENTIAL_OFFER_REQUEST;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsernameId;
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.clientId;
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.namedClientId;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -86,6 +83,8 @@ import static org.junit.Assert.fail;
|
||||
*/
|
||||
public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest {
|
||||
|
||||
String namedClientId = "named-test-app";
|
||||
|
||||
String issUsername = "john";
|
||||
String issClientId = clientId;
|
||||
|
||||
@@ -116,6 +115,13 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterAbstractKeycloakTestRealmImport() {
|
||||
ClientRepresentation namedClient = requireExistingClient(namedClientId);
|
||||
assignOptionalClientScope(namedClient, credScopeName);
|
||||
setOid4vciEnabled(namedClient, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCredentialWithoutOffer() throws Exception {
|
||||
var ctx = new TestContext(false, null, appUsername);
|
||||
@@ -238,9 +244,10 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest {
|
||||
|
||||
// Exclude scope: <credScope>
|
||||
// Require role: credential-offer-create
|
||||
verifyTokenJwt(ctx, issToken,
|
||||
// [TODO] Require role: credential-offer-create
|
||||
verifyTokenJwt(issToken,
|
||||
List.of(), List.of(ctx.credentialConfiguration.getScope()),
|
||||
List.of(CREDENTIAL_OFFER_CREATE.getName()), List.of());
|
||||
List.of(), List.of());
|
||||
|
||||
// Retrieving the credential-offer-uri
|
||||
//
|
||||
@@ -397,9 +404,7 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest {
|
||||
}
|
||||
|
||||
private CredentialsOffer getCredentialsOffer(TestContext ctx, CredentialOfferURI credOfferUri) throws Exception {
|
||||
CredentialOfferResponse credentialOfferResponse = oauth.oid4vc()
|
||||
.credentialOfferRequest(credOfferUri)
|
||||
.send();
|
||||
CredentialOfferResponse credentialOfferResponse = oauth.oid4vc().doCredentialOfferRequest(credOfferUri);
|
||||
CredentialsOffer credOffer = credentialOfferResponse.getCredentialsOffer();
|
||||
assertEquals(List.of(ctx.credentialConfiguration.getId()), credOffer.getCredentialConfigurationIds());
|
||||
return credOffer;
|
||||
@@ -478,7 +483,6 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest {
|
||||
}
|
||||
|
||||
private void verifyTokenJwt(
|
||||
TestContext ctx,
|
||||
String token,
|
||||
List<String> includeScopes,
|
||||
List<String> excludeScopes,
|
||||
|
||||
-2
@@ -40,8 +40,6 @@ import org.keycloak.testsuite.runonserver.RunOnServerException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.clientId;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
+6
-20
@@ -147,7 +147,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
// ===== STEP 2: Second login - Regular SSO (should NOT return authorization_details) =====
|
||||
// Second login WITHOUT OID4VCI scope and WITHOUT authorization_details.
|
||||
oauth.client(client.getClientId(), "password");
|
||||
oauth.scope(OAuth2Constants.SCOPE_OPENID);
|
||||
oauth.openLoginForm();
|
||||
|
||||
@@ -157,7 +156,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
// Exchange second code for tokens WITHOUT authorization_details using OAuthClient
|
||||
AccessTokenResponse secondTokenResponse = oauth.accessTokenRequest(secondCode)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "password")
|
||||
.send();
|
||||
assertEquals("Second token exchange should succeed", HttpStatus.SC_OK, secondTokenResponse.getStatusCode());
|
||||
|
||||
@@ -496,7 +494,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
// First token exchange - should succeed
|
||||
AccessTokenResponse tokenResponse = oauth.accessTokenRequest(code)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "password")
|
||||
.send();
|
||||
assertEquals(HttpStatus.SC_OK, tokenResponse.getStatusCode());
|
||||
|
||||
@@ -506,7 +503,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
// Second token exchange with same code - should fail
|
||||
AccessTokenResponse errorResponse = oauth.accessTokenRequest(code)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "password")
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.SC_BAD_REQUEST, errorResponse.getStatusCode());
|
||||
@@ -517,7 +513,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
// Verify error event was fired
|
||||
// Note: When code is reused, user is null but session from first successful use may still exist
|
||||
events.expect(EventType.CODE_TO_TOKEN_ERROR)
|
||||
.client(client.getClientId())
|
||||
.client(clientId)
|
||||
.user((String) null)
|
||||
.session(AssertEvents.isSessionId())
|
||||
.error(Errors.INVALID_CODE)
|
||||
@@ -536,7 +532,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
AccessTokenResponse errorResponse = oauth.accessTokenRequest("invalid-code-12345")
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "password")
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.SC_BAD_REQUEST, errorResponse.getStatusCode());
|
||||
@@ -547,7 +542,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
// Verify error event was fired
|
||||
// Note: When code is invalid (never valid), there is no session because authentication never occurred
|
||||
events.expect(EventType.CODE_TO_TOKEN_ERROR)
|
||||
.client(client.getClientId())
|
||||
.client(clientId)
|
||||
.user((String) null)
|
||||
.session((String) null)
|
||||
.error(Errors.INVALID_CODE)
|
||||
@@ -566,7 +561,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
AccessTokenResponse tokenResponse = oauth.accessTokenRequest(code)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "password")
|
||||
.send();
|
||||
|
||||
assertEquals("Token exchange should succeed without authorization_details (it's optional)",
|
||||
@@ -596,7 +590,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
AccessTokenResponse errorResponse = oauth.accessTokenRequest(code)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "password")
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.SC_BAD_REQUEST, errorResponse.getStatusCode());
|
||||
@@ -626,7 +619,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
AccessTokenResponse errorResponse = new InvalidTokenRequest(code, oauth)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.withClientId(client.getClientId())
|
||||
.withClientId(clientId)
|
||||
.withClientSecret("password")
|
||||
// redirect_uri is intentionally omitted
|
||||
.send();
|
||||
@@ -657,7 +650,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
AccessTokenResponse errorResponse = new InvalidTokenRequest(code, oauth)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.withClientId(client.getClientId())
|
||||
.withClientId(clientId)
|
||||
.withClientSecret("password")
|
||||
.withRedirectUri("http://invalid-redirect-uri")
|
||||
.send();
|
||||
@@ -736,7 +729,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
AccessTokenResponse errorResponse = oauth.accessTokenRequest(code)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "wrong-secret")
|
||||
.client(clientId, "wrong-secret")
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.SC_UNAUTHORIZED, errorResponse.getStatusCode());
|
||||
@@ -783,7 +776,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
Oid4vcTestContext ctx = prepareOid4vcTestContext();
|
||||
|
||||
// Perform authorization code flow with malformed authorization_details in the authorization request.
|
||||
oauth.client(client.getClientId());
|
||||
oauth.scope(getCredentialClientScope().getName());
|
||||
oauth.loginForm()
|
||||
.param(OAuth2Constants.AUTHORIZATION_DETAILS, "invalid-json")
|
||||
@@ -796,7 +788,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
AccessTokenResponse errorResponse = oauth.accessTokenRequest(code)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "password")
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.SC_BAD_REQUEST, errorResponse.getStatusCode());
|
||||
@@ -825,7 +816,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
AccessTokenResponse errorResponse = oauth.accessTokenRequest(code)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "password")
|
||||
.authorizationDetails(differentAuthDetails)
|
||||
.send();
|
||||
|
||||
@@ -960,7 +950,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
// Successful authorization_code flow
|
||||
private AccessTokenResponse authzCodeFlow(Oid4vcTestContext ctx, List<ClaimsDescription> claimsForAuthorizationDetailsParameter, boolean expectUserAlreadyAuthenticated) throws Exception {
|
||||
// Perform authorization code flow to get authorization code
|
||||
oauth.client(client.getClientId(), "password");
|
||||
oauth.scope(getCredentialClientScope().getName()); // Add the credential scope
|
||||
if (expectUserAlreadyAuthenticated) {
|
||||
oauth.openLoginForm();
|
||||
@@ -982,7 +971,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
// Exchange authorization code for tokens with authorization_details
|
||||
return oauth.accessTokenRequest(code)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(client.getClientId(), "password")
|
||||
.authorizationDetails(authDetails)
|
||||
.send();
|
||||
}
|
||||
@@ -1095,7 +1083,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
* @return the authorization code
|
||||
*/
|
||||
protected String performAuthorizationCodeLogin() {
|
||||
oauth.client(client.getClientId());
|
||||
oauth.scope(getCredentialClientScope().getName());
|
||||
oauth.loginForm().doLogin("john", "password");
|
||||
String code = oauth.parseLoginResponse().getCode();
|
||||
@@ -1110,7 +1097,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
* @return the authorization code
|
||||
*/
|
||||
protected String performAuthorizationCodeLoginWithAuthorizationDetails(String authorizationDetailsJson) {
|
||||
oauth.client(client.getClientId());
|
||||
oauth.scope(getCredentialClientScope().getName());
|
||||
oauth.loginForm()
|
||||
// Encode JSON so UriBuilder does not treat '{' or '}' as URI template characters
|
||||
@@ -1129,7 +1115,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
*/
|
||||
protected AssertEvents.ExpectedEvent expectCredentialRequestError() {
|
||||
return events.expect(EventType.VERIFIABLE_CREDENTIAL_REQUEST_ERROR)
|
||||
.client(client.getClientId())
|
||||
.client(clientId)
|
||||
.user(AssertEvents.isUUID())
|
||||
.session(AssertEvents.isSessionId())
|
||||
.error(Errors.INVALID_REQUEST);
|
||||
|
||||
+2
-4
@@ -136,7 +136,7 @@ public class OID4VCAuthorizationCodeFlowWithPARTest extends OID4VCIssuerEndpoint
|
||||
assertNotNull("Request URI should not be null", requestUri);
|
||||
|
||||
// Step 2: Perform authorization with PAR
|
||||
oauth.client(client.getClientId());
|
||||
oauth.client(clientId);
|
||||
oauth.scope(getCredentialClientScope().getName());
|
||||
oauth.loginForm().requestUri(requestUri).doLogin("john", "password");
|
||||
|
||||
@@ -235,7 +235,7 @@ public class OID4VCAuthorizationCodeFlowWithPARTest extends OID4VCIssuerEndpoint
|
||||
assertNotNull("Request URI should not be null", requestUri);
|
||||
|
||||
// Step 2: Perform authorization with PAR
|
||||
oauth.client(client.getClientId());
|
||||
oauth.client(clientId);
|
||||
oauth.scope(getCredentialClientScope().getName());
|
||||
oauth.loginForm().requestUri(requestUri).doLogin("john", "password");
|
||||
|
||||
@@ -272,7 +272,6 @@ public class OID4VCAuthorizationCodeFlowWithPARTest extends OID4VCIssuerEndpoint
|
||||
assertNotNull("Request URI should not be null", requestUri);
|
||||
|
||||
// Step 2: Perform authorization with PAR
|
||||
oauth.client(client.getClientId());
|
||||
oauth.scope(getCredentialClientScope().getName());
|
||||
oauth.loginForm().requestUri(requestUri).doLogin("john", "password");
|
||||
|
||||
@@ -282,7 +281,6 @@ public class OID4VCAuthorizationCodeFlowWithPARTest extends OID4VCIssuerEndpoint
|
||||
// Step 3: Exchange authorization code for tokens
|
||||
AccessTokenResponse tokenResponse = oauth.accessTokenRequest(code)
|
||||
.endpoint(ctx.openidConfig.getTokenEndpoint())
|
||||
.client(oauth.getClientId(), "password")
|
||||
.send();
|
||||
assertEquals(HttpStatus.SC_OK, tokenResponse.getStatusCode());
|
||||
|
||||
|
||||
+22
-12
@@ -38,7 +38,9 @@ import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
|
||||
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
|
||||
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
@@ -126,11 +128,11 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
|
||||
.username("john")
|
||||
.send();
|
||||
assertEquals(HttpStatus.SC_OK, credentialOfferURIResponse.getStatusCode());
|
||||
CredentialOfferURI credentialOfferURI = credentialOfferURIResponse.getCredentialOfferURI();
|
||||
CredentialOfferURI credOfferUri = credentialOfferURIResponse.getCredentialOfferURI();
|
||||
|
||||
// Verify CREDENTIAL_OFFER_REQUEST event was fired
|
||||
events.expect(EventType.VERIFIABLE_CREDENTIAL_OFFER_REQUEST)
|
||||
.client(client.getClientId())
|
||||
.client(clientId)
|
||||
.user(AssertEvents.isUUID())
|
||||
.session(AssertEvents.isSessionId())
|
||||
.detail(Details.USERNAME, "john")
|
||||
@@ -140,15 +142,13 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
|
||||
// Clear events before credential offer request
|
||||
events.clear();
|
||||
|
||||
CredentialOfferResponse credentialOfferResponse = oauth.oid4vc()
|
||||
.credentialOfferRequest(credentialOfferURI)
|
||||
.send();
|
||||
CredentialOfferResponse credentialOfferResponse = oauth.oid4vc().doCredentialOfferRequest(credOfferUri);
|
||||
assertEquals(HttpStatus.SC_OK, credentialOfferResponse.getStatusCode());
|
||||
ctx.credentialsOffer = credentialOfferResponse.getCredentialsOffer();
|
||||
|
||||
// Verify CREDENTIAL_OFFER_REQUEST event was fired (unauthenticated endpoint)
|
||||
events.expect(EventType.VERIFIABLE_CREDENTIAL_OFFER_REQUEST)
|
||||
.client(client.getClientId())
|
||||
.client(clientId)
|
||||
.user(AssertEvents.isUUID())
|
||||
.session((String) null)
|
||||
.detail(Details.CREDENTIAL_TYPE, credentialConfigurationId)
|
||||
@@ -660,7 +660,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
|
||||
|
||||
// Verify CREDENTIAL_REQUEST event was fired
|
||||
events.expect(EventType.VERIFIABLE_CREDENTIAL_REQUEST)
|
||||
.client(client.getClientId())
|
||||
.client(clientId)
|
||||
.user(AssertEvents.isUUID())
|
||||
.session(AssertEvents.isSessionId())
|
||||
.detail(Details.USERNAME, "john")
|
||||
@@ -750,10 +750,20 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
|
||||
// which has an "UNKNOWN" grant type context, is ALLOWED (backward compatibility).
|
||||
// This ensures the fail-closed logic doesn't accidentally block standard Keycloak flows.
|
||||
|
||||
ClientRepresentation accountClient = testRealm().clients().findByClientId("account").stream().findFirst().orElse(null);
|
||||
assertNotNull("Has account client", accountClient);
|
||||
|
||||
ClientRepresentation testClient = testRealm().clients().findByClientId("test-app").stream().findFirst().orElse(null);
|
||||
assertNotNull("Has test-app", testClient);
|
||||
assertEquals(testClient.getClientId(), oauth.getClientId());
|
||||
|
||||
UserRepresentation testUser = testRealm().users().search("test-user@localhost").stream().findFirst().orElse(null);
|
||||
assertNotNull("Has test-user", testUser);
|
||||
|
||||
log.debugf(JsonSerialization.valueAsPrettyString(testClient));
|
||||
|
||||
// 1. Get standard token
|
||||
oauth.realm("test");
|
||||
oauth.client("test-app", "password");
|
||||
org.keycloak.testsuite.util.oauth.AccessTokenResponse response = oauth.doPasswordGrantRequest("test-user@localhost", "password");
|
||||
AccessTokenResponse response = oauth.doPasswordGrantRequest("test-user@localhost", "password");
|
||||
String accessToken = response.getAccessToken();
|
||||
|
||||
// 2. Use at Account API (which would be restricted if it were a pre-authorized token)
|
||||
@@ -892,7 +902,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
|
||||
|
||||
// Verify CREDENTIAL_REQUEST event was fired
|
||||
events.expect(EventType.VERIFIABLE_CREDENTIAL_REQUEST)
|
||||
.client(client.getClientId())
|
||||
.client(clientId)
|
||||
.user(AssertEvents.isUUID())
|
||||
.session(AssertEvents.isSessionId())
|
||||
.detail(Details.USERNAME, "john")
|
||||
@@ -960,7 +970,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
|
||||
|
||||
// Verify VERIFIABLE_CREDENTIAL_REQUEST_ERROR event was fired
|
||||
events.expect(EventType.VERIFIABLE_CREDENTIAL_REQUEST_ERROR)
|
||||
.client(client.getClientId())
|
||||
.client(clientId)
|
||||
.user(AssertEvents.isUUID())
|
||||
.session(AssertEvents.isSessionId())
|
||||
.error(Errors.INVALID_REQUEST)
|
||||
|
||||
-5
@@ -55,8 +55,6 @@ import org.hamcrest.Matchers;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.clientId;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -370,9 +368,6 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest {
|
||||
// Helper methods
|
||||
|
||||
private AccessTokenResponse getAccessToken() throws Exception {
|
||||
oauth.realm("test");
|
||||
oauth.client(client.getClientId(), client.getSecret());
|
||||
|
||||
return oauth.doPasswordGrantRequest("john", "password");
|
||||
}
|
||||
|
||||
|
||||
+166
-171
@@ -37,8 +37,6 @@ import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
@@ -52,7 +50,6 @@ import org.keycloak.OID4VCConstants.KeyAttestationResistanceLevels;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.VCFormat;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
@@ -96,7 +93,6 @@ import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
import org.keycloak.representations.idm.ComponentExportRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.RolesRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.representations.userprofile.config.UPAttribute;
|
||||
@@ -128,14 +124,14 @@ import org.jboss.logging.Logger;
|
||||
import org.junit.Before;
|
||||
|
||||
import static org.keycloak.OID4VCConstants.CLAIM_NAME_VC;
|
||||
import static org.keycloak.OID4VCConstants.OID4VCI_ENABLED_ATTRIBUTE_KEY;
|
||||
import static org.keycloak.constants.OID4VCIConstants.CREDENTIAL_OFFER_CREATE;
|
||||
import static org.keycloak.jose.jwe.JWEConstants.A256GCM;
|
||||
import static org.keycloak.jose.jwe.JWEConstants.RSA_OAEP;
|
||||
import static org.keycloak.jose.jwe.JWEConstants.RSA_OAEP_256;
|
||||
import static org.keycloak.models.Constants.CREATE_DEFAULT_CLIENT_SCOPES;
|
||||
import static org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint.CREDENTIAL_OFFER_URI_CODE_SCOPE;
|
||||
import static org.keycloak.protocol.oid4vc.model.ProofType.JWT;
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.clientId;
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.namedClientId;
|
||||
import static org.keycloak.userprofile.DeclarativeUserProfileProvider.UP_COMPONENT_CONFIG_KEY;
|
||||
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_ADMIN;
|
||||
import static org.keycloak.userprofile.config.UPConfigUtils.ROLE_USER;
|
||||
@@ -155,29 +151,138 @@ import static org.junit.Assert.fail;
|
||||
*/
|
||||
public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(OID4VCIssuerEndpointTest.class);
|
||||
|
||||
protected static final TimeProvider TIME_PROVIDER = new OID4VCTest.StaticTimeProvider(1000);
|
||||
protected static final String sdJwtCredentialVct = "https://credentials.example.com/SD-JWT-Credential";
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(OID4VCIssuerEndpointTest.class);
|
||||
|
||||
protected static ClientScopeRepresentation jwtTypeNaturalPersonClientScope;
|
||||
protected static ClientScopeRepresentation sdJwtTypeNaturalPersonClientScope;
|
||||
|
||||
protected static ClientScopeRepresentation jwtTypeCredentialClientScope;
|
||||
protected static ClientScopeRepresentation sdJwtTypeCredentialClientScope;
|
||||
protected static ClientScopeRepresentation minimalJwtTypeCredentialClientScope;
|
||||
protected ClientScopeRepresentation sdJwtTypeCredentialClientScope;
|
||||
protected ClientScopeRepresentation jwtTypeCredentialClientScope;
|
||||
protected ClientScopeRepresentation minimalJwtTypeCredentialClientScope;
|
||||
|
||||
protected CloseableHttpClient httpClient;
|
||||
|
||||
protected ClientRepresentation client;
|
||||
protected ClientRepresentation namedClient;
|
||||
|
||||
record OAuth2CodeEntry(String key, OAuth2Code code) {}
|
||||
|
||||
protected boolean shouldEnableOid4vci() {
|
||||
protected boolean shouldEnableOid4vci(RealmRepresentation testRealm) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean shouldEnableOid4vci(ClientRepresentation testClient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
CryptoIntegration.init(this.getClass().getClassLoader());
|
||||
|
||||
testRealm.setVerifiableCredentialsEnabled(shouldEnableOid4vci(testRealm));
|
||||
|
||||
if (testRealm.getComponents() == null) {
|
||||
testRealm.setComponents(new MultivaluedHashMap<>());
|
||||
}
|
||||
|
||||
// Add key providers
|
||||
testRealm.getComponents().add("org.keycloak.keys.KeyProvider",
|
||||
getKeyProvider());
|
||||
testRealm.getComponents().add("org.keycloak.keys.KeyProvider",
|
||||
getRsaEncKeyProvider(RSA_OAEP_256, "enc-key-oaep256", 100));
|
||||
testRealm.getComponents().add("org.keycloak.keys.KeyProvider",
|
||||
getRsaEncKeyProvider(RSA_OAEP, "enc-key-oaep", 101));
|
||||
|
||||
// Add Did attribute to the user profile
|
||||
testRealm.getComponents().add("org.keycloak.userprofile.UserProfileProvider",
|
||||
getUserProfileProvider());
|
||||
|
||||
// Add a role representations
|
||||
//
|
||||
RolesRepresentation realmRoles = testRealm.getRoles();
|
||||
realmRoles.getRealm().add(CREDENTIAL_OFFER_CREATE);
|
||||
realmRoles.getClient().get(clientId).add(getRoleRepresentation("testRole", clientId));
|
||||
|
||||
// Add user representations
|
||||
//
|
||||
Map<String, List<String>> clientRoles = Map.of(clientId, List.of("testRole"));
|
||||
List<UserRepresentation> realmUsers = Optional.ofNullable(testRealm.getUsers()).map(ArrayList::new).orElse(new ArrayList<>());
|
||||
realmUsers.add(getUserRepresentation("John Doe", Map.of("did", "did:key:1234"), List.of(CREDENTIAL_OFFER_CREATE.getName()), clientRoles));
|
||||
realmUsers.add(getUserRepresentation("Alice Wonderland", Map.of("did", "did:key:5678"), List.of(), Map.of()));
|
||||
testRealm.setUsers(realmUsers);
|
||||
|
||||
// Allow the default client scopes to be added as well
|
||||
Map<String, String> realmAttributes = Optional.ofNullable(testRealm.getAttributes()).orElse(new HashMap<>());
|
||||
realmAttributes.put(CREATE_DEFAULT_CLIENT_SCOPES, String.valueOf(true));
|
||||
testRealm.setAttributes(realmAttributes);
|
||||
|
||||
// Add additional client scopes
|
||||
//
|
||||
List<ClientScopeRepresentation> clientScopes = Optional.ofNullable(testRealm.getClientScopes()).orElse(new ArrayList<>());
|
||||
clientScopes.add(createOptionalClientScope(sdJwtTypeCredentialScopeName,
|
||||
null,
|
||||
sdJwtTypeCredentialConfigurationIdName,
|
||||
sdJwtTypeCredentialScopeName,
|
||||
sdJwtCredentialVct,
|
||||
VCFormat.SD_JWT_VC,
|
||||
null,
|
||||
List.of(KeyAttestationResistanceLevels.HIGH, KeyAttestationResistanceLevels.MODERATE))
|
||||
);
|
||||
clientScopes.add(createOptionalClientScope(jwtTypeCredentialScopeName,
|
||||
TEST_DID.toString(),
|
||||
jwtTypeCredentialConfigurationIdName,
|
||||
jwtTypeCredentialScopeName,
|
||||
null,
|
||||
VCFormat.JWT_VC,
|
||||
TEST_CREDENTIAL_MAPPERS_FILE,
|
||||
Collections.emptyList())
|
||||
);
|
||||
clientScopes.add(createOptionalClientScope(minimalJwtTypeCredentialScopeName,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null, null)
|
||||
);
|
||||
testRealm.setClientScopes(clientScopes);
|
||||
|
||||
// Enable oid4vci in test clients
|
||||
for (String cid : List.of(clientId)) {
|
||||
|
||||
ClientRepresentation testClient = testRealm.getClients().stream()
|
||||
.filter(c -> c.getClientId().equals(cid))
|
||||
.findFirst().orElseThrow(() -> new IllegalStateException("Client with clientId=" + cid + " not found in realm"));
|
||||
|
||||
// Enable oid4vci on the client
|
||||
Map<String, String> attributes = Optional.ofNullable(testClient.getAttributes()).orElse(new HashMap<>());
|
||||
attributes.put(OID4VCI_ENABLED_ATTRIBUTE_KEY, String.valueOf(shouldEnableOid4vci(testClient)));
|
||||
testClient.setAttributes(attributes);
|
||||
|
||||
// Assign default client scopes
|
||||
List<String> defaultClientScopes = new ArrayList<>(Optional.ofNullable(testClient.getDefaultClientScopes()).orElse(List.of()));
|
||||
defaultClientScopes.addAll(List.of("web-origins", "acr", "roles", "profile", "basic", "email"));
|
||||
testClient.setDefaultClientScopes(defaultClientScopes);
|
||||
|
||||
// Assign optional client scopes
|
||||
List<String> optionalClientScopes = new ArrayList<>(Optional.ofNullable(testClient.getOptionalClientScopes()).orElse(List.of()));
|
||||
// Realm import does not assign the default optional scopes
|
||||
// optionalClientScopes.addAll(List.of("address", "phone", "offline_access", "organization", "microprofile-jwt"));
|
||||
optionalClientScopes.addAll(clientScopes.stream().map(ClientScopeRepresentation::getName).toList());
|
||||
optionalClientScopes.addAll(List.of(jwtTypeNaturalPersonScopeName, sdJwtTypeNaturalPersonScopeName));
|
||||
testClient.setOptionalClientScopes(optionalClientScopes);
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
httpClient = HttpClientBuilder.create().build();
|
||||
client = requireExistingClient(clientId);
|
||||
|
||||
// Lookup additional client scopes
|
||||
sdJwtTypeCredentialClientScope = requireExistingClientScope(sdJwtTypeCredentialScopeName);
|
||||
jwtTypeCredentialClientScope = requireExistingClientScope(jwtTypeCredentialScopeName);
|
||||
minimalJwtTypeCredentialClientScope = requireExistingClientScope(minimalJwtTypeCredentialScopeName);
|
||||
}
|
||||
|
||||
protected static OAuth2CodeEntry prepareSessionCode(KeycloakSession session, AppAuthManager.BearerTokenAuthenticator authenticator, String note) {
|
||||
AuthenticationManager.AuthResult authResult = authenticator.authenticate();
|
||||
UserSessionModel userSessionModel = authResult.session();
|
||||
@@ -222,85 +327,14 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
30);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
CryptoIntegration.init(this.getClass().getClassLoader());
|
||||
httpClient = HttpClientBuilder.create().build();
|
||||
client = testRealm().clients().findByClientId(clientId).get(0);
|
||||
namedClient = testRealm().clients().findByClientId(namedClientId).get(0);
|
||||
|
||||
// Enable OID4VCI at realm level (required before assigning OID4VCI scopes)
|
||||
RealmRepresentation realmRep = testRealm().toRepresentation();
|
||||
realmRep.setVerifiableCredentialsEnabled(true);
|
||||
testRealm().update(realmRep);
|
||||
|
||||
// Lookup the pre-installed oid4vc_natural_person client scope
|
||||
jwtTypeNaturalPersonClientScope = requireExistingClientScope(jwtTypeNaturalPersonScopeName);
|
||||
sdJwtTypeNaturalPersonClientScope = requireExistingClientScope(sdJwtTypeNaturalPersonScopeName);
|
||||
|
||||
// Register the optional client scopes
|
||||
sdJwtTypeCredentialClientScope = registerOptionalClientScope(sdJwtTypeCredentialScopeName,
|
||||
null,
|
||||
sdJwtTypeCredentialConfigurationIdName,
|
||||
sdJwtTypeCredentialScopeName,
|
||||
sdJwtCredentialVct,
|
||||
VCFormat.SD_JWT_VC,
|
||||
null,
|
||||
List.of(KeyAttestationResistanceLevels.HIGH, KeyAttestationResistanceLevels.MODERATE));
|
||||
jwtTypeCredentialClientScope = registerOptionalClientScope(jwtTypeCredentialScopeName,
|
||||
TEST_DID.toString(),
|
||||
jwtTypeCredentialConfigurationIdName,
|
||||
jwtTypeCredentialScopeName,
|
||||
null,
|
||||
VCFormat.JWT_VC,
|
||||
TEST_CREDENTIAL_MAPPERS_FILE,
|
||||
Collections.emptyList());
|
||||
minimalJwtTypeCredentialClientScope = registerOptionalClientScope("vc-with-minimal-config",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null, null);
|
||||
|
||||
List.of(client, namedClient).forEach(client -> {
|
||||
String clientId = client.getClientId();
|
||||
|
||||
// Assign the registered optional client scopes to the client
|
||||
assignOptionalClientScopeToClient(jwtTypeNaturalPersonClientScope.getId(), clientId);
|
||||
assignOptionalClientScopeToClient(sdJwtTypeNaturalPersonClientScope.getId(), clientId);
|
||||
assignOptionalClientScopeToClient(sdJwtTypeCredentialClientScope.getId(), clientId);
|
||||
assignOptionalClientScopeToClient(jwtTypeCredentialClientScope.getId(), clientId);
|
||||
assignOptionalClientScopeToClient(minimalJwtTypeCredentialClientScope.getId(), clientId);
|
||||
|
||||
// Enable OID4VCI for the client by default, but allow tests to override
|
||||
setClientOid4vciEnabled(clientId, shouldEnableOid4vci());
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected ClientScopeRepresentation registerOptionalClientScope(String scopeName,
|
||||
String issuerDid,
|
||||
String credentialConfigurationId,
|
||||
String credentialIdentifier,
|
||||
String vct,
|
||||
String format,
|
||||
String protocolMapperReferenceFile,
|
||||
List<String> acceptedKeyAttestationValues) {
|
||||
List<ClientScopeRepresentation> existingScopes = testRealm().clientScopes().findAll();
|
||||
for (ClientScopeRepresentation existingScope : existingScopes) {
|
||||
if (existingScope.getName().equals(scopeName)) {
|
||||
return existingScope;
|
||||
}
|
||||
}
|
||||
protected ClientScopeRepresentation createOptionalClientScope(String scopeName,
|
||||
String issuerDid,
|
||||
String credentialConfigurationId,
|
||||
String credentialIdentifier,
|
||||
String vct,
|
||||
String format,
|
||||
String protocolMapperReferenceFile,
|
||||
List<String> acceptedKeyAttestationValues) {
|
||||
|
||||
ClientScopeRepresentation clientScope = new ClientScopeRepresentation();
|
||||
clientScope.setName(scopeName);
|
||||
@@ -334,34 +368,41 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
attributes.put(CredentialScopeModel.KEY_ATTESTATION_REQUIRED, "true");
|
||||
if (!acceptedKeyAttestationValues.isEmpty()) {
|
||||
attributes.put(CredentialScopeModel.KEY_ATTESTATION_REQUIRED_KEY_STORAGE,
|
||||
String.join(",", acceptedKeyAttestationValues));
|
||||
String.join(",", acceptedKeyAttestationValues));
|
||||
attributes.put(CredentialScopeModel.KEY_ATTESTATION_REQUIRED_USER_AUTH,
|
||||
String.join(",", acceptedKeyAttestationValues));
|
||||
String.join(",", acceptedKeyAttestationValues));
|
||||
}
|
||||
}
|
||||
clientScope.setAttributes(attributes);
|
||||
|
||||
Response res = testRealm().clientScopes().create(clientScope);
|
||||
String scopeId = ApiUtil.getCreatedId(res);
|
||||
getCleanup().addClientScopeId(scopeId); // Automatically removed when a test method is finished.
|
||||
res.close();
|
||||
|
||||
clientScope.setId(scopeId);
|
||||
|
||||
List<ProtocolMapperRepresentation> protocolMappers;
|
||||
if (protocolMapperReferenceFile == null) {
|
||||
protocolMappers = getProtocolMappers(scopeName);
|
||||
addProtocolMappersToClientScope(clientScope, protocolMappers);
|
||||
} else {
|
||||
protocolMappers = resolveProtocolMappers(protocolMapperReferenceFile);
|
||||
protocolMappers.add(getStaticClaimMapper(scopeName));
|
||||
addProtocolMappersToClientScope(clientScope, protocolMappers);
|
||||
}
|
||||
clientScope.setProtocolMappers(protocolMappers);
|
||||
return clientScope;
|
||||
}
|
||||
|
||||
private ClientScopeRepresentation requireExistingClientScope(String scopeName) {
|
||||
protected ClientScopeRepresentation registerOptionalClientScope(ClientScopeRepresentation clientScope) {
|
||||
// Automatically removed when a test method is finished.
|
||||
try (Response res = testRealm().clientScopes().create(clientScope)) {
|
||||
String scopeId = ApiUtil.getCreatedId(res);
|
||||
getCleanup().addClientScopeId(scopeId);
|
||||
clientScope.setId(scopeId);
|
||||
}
|
||||
return clientScope;
|
||||
}
|
||||
|
||||
protected ClientRepresentation requireExistingClient(String clientId) {
|
||||
List<ClientRepresentation> clientRepresentations = testRealm().clients().findByClientId(clientId);
|
||||
assertFalse("No such client", clientRepresentations.isEmpty());
|
||||
return clientRepresentations.get(0);
|
||||
}
|
||||
|
||||
protected ClientScopeRepresentation requireExistingClientScope(String scopeName) {
|
||||
|
||||
// Check if the client scope already exists
|
||||
List<ClientScopeRepresentation> existingScopes = testRealm().clientScopes().findAll();
|
||||
@@ -386,9 +427,14 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assignOptionalClientScopeToClient(String scopeId, String clientId) {
|
||||
ClientResource clientResource = findClientByClientId(testRealm(), clientId);
|
||||
clientResource.addOptionalClientScope(scopeId);
|
||||
protected void assignOptionalClientScope(ClientRepresentation testClient, String scopeName) {
|
||||
ClientScopeRepresentation clientScope = requireExistingClientScope(scopeName);
|
||||
assignOptionalClientScope(testClient, clientScope);
|
||||
}
|
||||
|
||||
protected void assignOptionalClientScope(ClientRepresentation client, ClientScopeRepresentation clientScope) {
|
||||
ClientResource clientResource = testRealm().clients().get(client.getId());
|
||||
clientResource.addOptionalClientScope(clientScope.getId());
|
||||
}
|
||||
|
||||
protected void logoutUser(String username) {
|
||||
@@ -493,15 +539,14 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
void setClientOid4vciEnabled(String clientId, boolean enabled) {
|
||||
ClientRepresentation clientRepresentation = adminClient.realm(TEST_REALM_NAME).clients().findByClientId(clientId).get(0);
|
||||
ClientResource clientResource = adminClient.realm(TEST_REALM_NAME).clients().get(clientRepresentation.getId());
|
||||
protected void setOid4vciEnabled(ClientRepresentation testClient, boolean enabled) {
|
||||
ClientResource clientResource = testRealm().clients().get(testClient.getId());
|
||||
|
||||
Map<String, String> attributes = new HashMap<>(clientRepresentation.getAttributes() != null ? clientRepresentation.getAttributes() : Map.of());
|
||||
attributes.put("oid4vci.enabled", String.valueOf(enabled));
|
||||
clientRepresentation.setAttributes(attributes);
|
||||
Map<String, String> attributes = Optional.ofNullable(testClient.getAttributes()).orElse(new HashMap<>());
|
||||
attributes.put(OID4VCI_ENABLED_ATTRIBUTE_KEY, String.valueOf(enabled));
|
||||
testClient.setAttributes(attributes);
|
||||
|
||||
clientResource.update(clientRepresentation);
|
||||
clientResource.update(testClient);
|
||||
}
|
||||
|
||||
// Tests the AuthZCode complete flow without scope from
|
||||
@@ -512,7 +557,6 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
protected void testCredentialIssuanceWithAuthZCodeFlow(ClientScopeRepresentation clientScope,
|
||||
BiFunction<String, String, String> f,
|
||||
Consumer<Map<String, Object>> c) {
|
||||
String testClientId = client.getClientId();
|
||||
String testScope = clientScope.getName();
|
||||
String testFormat = clientScope.getAttributes().get(CredentialScopeModel.FORMAT);
|
||||
String testCredentialConfigurationId = clientScope.getAttributes().get(CredentialScopeModel.CONFIGURATION_ID);
|
||||
@@ -522,8 +566,8 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
WebTarget oid4vciDiscoveryTarget = client.target(metadataUrl);
|
||||
|
||||
// 1. Get authoriZation code without scope specified by wallet
|
||||
// 2. Using the code to get accesstoken
|
||||
String token = f.apply(testClientId, testScope);
|
||||
// 2. Using the code to get the AccessToken
|
||||
String token = f.apply(clientId, testScope);
|
||||
|
||||
// Extract credential_identifier from the token (client-side parsing)
|
||||
String credentialIdentifier = null;
|
||||
@@ -645,12 +689,8 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
}
|
||||
|
||||
public CredentialIssuer getCredentialIssuerMetadata() {
|
||||
final String endpoint = getRealmMetadataPath(TEST_REALM_NAME);
|
||||
CredentialIssuerMetadataResponse metadataResponse = oauth.oid4vc()
|
||||
.issuerMetadataRequest()
|
||||
.endpoint(endpoint)
|
||||
.send();
|
||||
assertEquals(HttpStatus.SC_OK, metadataResponse.getStatusCode());
|
||||
.doIssuerMetadataRequest();
|
||||
return metadataResponse.getMetadata();
|
||||
}
|
||||
|
||||
@@ -669,51 +709,6 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
testRealm.setVerifiableCredentialsEnabled(true);
|
||||
|
||||
if (testRealm.getComponents() == null) {
|
||||
testRealm.setComponents(new MultivaluedHashMap<>());
|
||||
}
|
||||
|
||||
// Add key providers
|
||||
testRealm.getComponents().add("org.keycloak.keys.KeyProvider", getKeyProvider());
|
||||
testRealm.getComponents().add("org.keycloak.keys.KeyProvider",
|
||||
getRsaEncKeyProvider(RSA_OAEP_256, "enc-key-oaep256", 100));
|
||||
testRealm.getComponents().add("org.keycloak.keys.KeyProvider",
|
||||
getRsaEncKeyProvider(RSA_OAEP, "enc-key-oaep", 101));
|
||||
|
||||
// Add Did attribute to the user profile
|
||||
testRealm.getComponents().add("org.keycloak.userprofile.UserProfileProvider", getUserProfileProvider());
|
||||
|
||||
// Find existing client representation
|
||||
Map<String, ClientRepresentation> realmClients = testRealm.getClients().stream()
|
||||
.collect(Collectors.toMap(ClientRepresentation::getClientId, Function.identity()));
|
||||
ClientRepresentation existingClient = Optional.ofNullable(realmClients.get(clientId))
|
||||
.orElseThrow(() -> new IllegalStateException("Client with ID " + clientId + " not found in realm"));
|
||||
|
||||
// Add a role to an existing client
|
||||
RolesRepresentation realmRoles = testRealm.getRoles();
|
||||
if (realmRoles != null) {
|
||||
realmRoles.getClient().merge(
|
||||
existingClient.getClientId(),
|
||||
List.of(getRoleRepresentation("testRole", existingClient.getClientId())),
|
||||
(existingRoles, newRoles) -> {
|
||||
List<RoleRepresentation> mergedRoles = new ArrayList<>(existingRoles);
|
||||
mergedRoles.addAll(newRoles);
|
||||
return mergedRoles;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, List<String>> clientRoles = Map.of(clientId, List.of("testRole"));
|
||||
List<UserRepresentation> realmUsers = Optional.ofNullable(testRealm.getUsers()).map(ArrayList::new).orElse(new ArrayList<>());
|
||||
realmUsers.add(getUserRepresentation("John Doe", Map.of("did", "did:key:1234"), List.of(CREDENTIAL_OFFER_CREATE.getName()), clientRoles));
|
||||
realmUsers.add(getUserRepresentation("Alice Wonderland", Map.of("did", "did:key:5678"), List.of(), Map.of()));
|
||||
testRealm.setUsers(realmUsers);
|
||||
}
|
||||
|
||||
private ComponentExportRepresentation getUserProfileProvider() {
|
||||
|
||||
// Add the User DID attribute, with the same logic as in DeclarativeUserProfileProviderFactory
|
||||
|
||||
+4
-31
@@ -1,13 +1,12 @@
|
||||
package org.keycloak.testsuite.oid4vc.issuance.signing;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialRequest;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
@@ -15,13 +14,8 @@ import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.clientId;
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.namedClientId;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
@@ -30,34 +24,13 @@ import static org.junit.Assert.assertEquals;
|
||||
public class OID4VCJWTIssuerEndpointDisabledTest extends OID4VCIssuerEndpointTest {
|
||||
|
||||
@Override
|
||||
protected boolean shouldEnableOid4vci() {
|
||||
protected boolean shouldEnableOid4vci(RealmRepresentation testRealm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
super.configureTestRealm(testRealm);
|
||||
testRealm.setVerifiableCredentialsEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override setup to skip creating oid4vc client scopes when verifiable credentials is disabled.
|
||||
* The parent setup() tries to create client scopes with oid4vc protocol, which will fail
|
||||
* with the new validation that prevents creating oid4vc scopes when VC is disabled.
|
||||
*/
|
||||
@Override
|
||||
@Before
|
||||
public void setup() {
|
||||
CryptoIntegration.init(this.getClass().getClassLoader());
|
||||
httpClient = HttpClientBuilder.create().build();
|
||||
client = testRealm().clients().findByClientId(clientId).get(0);
|
||||
namedClient = testRealm().clients().findByClientId(namedClientId).get(0);
|
||||
|
||||
List.of(client, namedClient).forEach(client -> {
|
||||
String clientId = client.getClientId();
|
||||
// Enable OID4VCI for the client by default, but allow tests to override
|
||||
setClientOid4vciEnabled(clientId, shouldEnableOid4vci());
|
||||
});
|
||||
protected boolean shouldEnableOid4vci(ClientRepresentation testClient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+17
-17
@@ -450,7 +450,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
|
||||
// 1. Retrieving the credential-offer-uri
|
||||
final String credentialConfigurationId = jwtTypeCredentialClientScope.getAttributes()
|
||||
.get(CredentialScopeModel.CONFIGURATION_ID);
|
||||
CredentialOfferURI credentialOfferURI = oauth.oid4vc()
|
||||
CredentialOfferURI credOfferUri = oauth.oid4vc()
|
||||
.credentialOfferUriRequest(credentialConfigurationId)
|
||||
.preAuthorized(true)
|
||||
.username("john")
|
||||
@@ -458,14 +458,11 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
|
||||
.send()
|
||||
.getCredentialOfferURI();
|
||||
|
||||
assertNotNull("A valid offer uri should be returned", credentialOfferURI);
|
||||
assertNotNull("A valid offer uri should be returned", credOfferUri);
|
||||
|
||||
// 2. Using the uri to get the actual credential offer
|
||||
CredentialsOffer credentialsOffer = oauth.oid4vc()
|
||||
.credentialOfferRequest(credentialOfferURI)
|
||||
.bearerToken(token)
|
||||
.send()
|
||||
.getCredentialsOffer();
|
||||
CredentialOfferResponse credentialOfferResponse = oauth.oid4vc().doCredentialOfferRequest(credOfferUri);
|
||||
CredentialsOffer credentialsOffer = credentialOfferResponse.getCredentialsOffer();
|
||||
|
||||
assertNotNull("A valid offer should be returned", credentialsOffer);
|
||||
|
||||
@@ -1059,15 +1056,16 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCredentialRequestWithOptionalClientScope() {
|
||||
ClientScopeRepresentation optionalScope = registerOptionalClientScope(
|
||||
ClientScopeRepresentation optionalScope = createOptionalClientScope(
|
||||
"optional-jwt-credential",
|
||||
TEST_DID.toString(),
|
||||
"optional-jwt-credential-config-id",
|
||||
null, null,
|
||||
VCFormat.JWT_VC,
|
||||
null, null);
|
||||
|
||||
ClientRepresentation testClient = testRealm().clients().findByClientId(client.getClientId()).get(0);
|
||||
|
||||
optionalScope = registerOptionalClientScope(optionalScope);
|
||||
ClientRepresentation testClient = testRealm().clients().findByClientId(clientId).get(0);
|
||||
testRealm().clients().get(testClient.getId()).addOptionalClientScope(optionalScope.getId());
|
||||
|
||||
// Extract serializable data before lambda
|
||||
@@ -1114,15 +1112,16 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCannotAssignOid4vciScopeAsDefaultToClient() {
|
||||
ClientScopeRepresentation oid4vciScope = registerOptionalClientScope(
|
||||
ClientScopeRepresentation oid4vciScope = createOptionalClientScope(
|
||||
"test-oid4vci-scope",
|
||||
TEST_DID.toString(),
|
||||
"test-oid4vci-config-id",
|
||||
null, null,
|
||||
VCFormat.JWT_VC,
|
||||
null, null);
|
||||
|
||||
ClientRepresentation testClient = testRealm().clients().findByClientId(client.getClientId()).get(0);
|
||||
|
||||
oid4vciScope = registerOptionalClientScope(oid4vciScope);
|
||||
ClientRepresentation testClient = testRealm().clients().findByClientId(clientId).get(0);
|
||||
ClientResource clientResource = testRealm().clients().get(testClient.getId());
|
||||
|
||||
try {
|
||||
@@ -1137,14 +1136,14 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
|
||||
|
||||
@Test
|
||||
public void testCannotAssignOid4vciScopeAsDefaultToRealm() {
|
||||
ClientScopeRepresentation oid4vciScope = registerOptionalClientScope(
|
||||
ClientScopeRepresentation oid4vciScope = createOptionalClientScope(
|
||||
"test-oid4vci-realm-scope",
|
||||
TEST_DID.toString(),
|
||||
"test-oid4vci-realm-config-id",
|
||||
null, null,
|
||||
VCFormat.JWT_VC,
|
||||
null, null);
|
||||
|
||||
oid4vciScope = registerOptionalClientScope(oid4vciScope);
|
||||
try {
|
||||
testRealm().addDefaultDefaultClientScope(oid4vciScope.getId());
|
||||
Assert.fail("Expected BadRequestException when trying to assign OID4VCI scope as realm Default");
|
||||
@@ -1160,13 +1159,14 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCannotAssignOid4vciScopeWhenRealmDisabled() {
|
||||
ClientScopeRepresentation oid4vciScope = registerOptionalClientScope(
|
||||
ClientScopeRepresentation oid4vciScope = createOptionalClientScope(
|
||||
"test-oid4vci-disabled-scope",
|
||||
TEST_DID.toString(),
|
||||
"test-oid4vci-disabled-config-id",
|
||||
null, null,
|
||||
VCFormat.JWT_VC,
|
||||
null, null);
|
||||
oid4vciScope = registerOptionalClientScope(oid4vciScope);
|
||||
|
||||
testingClient.server(TEST_REALM_NAME).run(session -> {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
@@ -1174,7 +1174,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
|
||||
});
|
||||
|
||||
try {
|
||||
ClientRepresentation testClient = testRealm().clients().findByClientId(client.getClientId()).get(0);
|
||||
ClientRepresentation testClient = testRealm().clients().findByClientId(clientId).get(0);
|
||||
ClientResource clientResource = testRealm().clients().get(testClient.getId());
|
||||
|
||||
try {
|
||||
|
||||
+4
-34
@@ -1,13 +1,12 @@
|
||||
package org.keycloak.testsuite.oid4vc.issuance.signing;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialRequest;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.CorsErrorResponseException;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
@@ -15,13 +14,8 @@ import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.clientId;
|
||||
import static org.keycloak.testsuite.forms.PassThroughClientAuthenticator.namedClientId;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
@@ -30,37 +24,13 @@ import static org.junit.Assert.assertEquals;
|
||||
public class OID4VCSdJwtIssuingEndpointDisabledTest extends OID4VCIssuerEndpointTest {
|
||||
|
||||
@Override
|
||||
protected boolean shouldEnableOid4vci() {
|
||||
protected boolean shouldEnableOid4vci(RealmRepresentation testRealm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
super.configureTestRealm(testRealm);
|
||||
testRealm.setVerifiableCredentialsEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override setup to skip creating oid4vc client scopes when verifiable credentials is disabled.
|
||||
* The parent setup() tries to create client scopes with oid4vc protocol, which will fail
|
||||
* with the new validation that prevents creating oid4vc scopes when VC is disabled.
|
||||
*/
|
||||
@Override
|
||||
@Before
|
||||
public void setup() {
|
||||
CryptoIntegration.init(this.getClass().getClassLoader());
|
||||
httpClient = HttpClientBuilder.create().build();
|
||||
client = testRealm().clients().findByClientId(clientId).get(0);
|
||||
namedClient = testRealm().clients().findByClientId(namedClientId).get(0);
|
||||
|
||||
// Skip creating oid4vc client scopes when VC is disabled - they cannot be created
|
||||
// and are not needed for these tests which verify that endpoints reject calls
|
||||
|
||||
List.of(client, namedClient).forEach(client -> {
|
||||
String clientId = client.getClientId();
|
||||
// Enable OID4VCI for the client by default, but allow tests to override
|
||||
setClientOid4vciEnabled(clientId, shouldEnableOid4vci());
|
||||
});
|
||||
protected boolean shouldEnableOid4vci(ClientRepresentation testClient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+5
-7
@@ -60,6 +60,7 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.sdjwt.vp.SdJwtVP;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.util.oauth.oid4vc.CredentialOfferResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
@@ -373,7 +374,7 @@ public class OID4VCSdJwtIssuingEndpointTest extends OID4VCIssuerEndpointTest {
|
||||
|
||||
// 1. Retrieving the credential-offer-uri
|
||||
final String credentialConfigurationId = clientScope.getAttributes().get(CredentialScopeModel.CONFIGURATION_ID);
|
||||
CredentialOfferURI credentialOfferURI = oauth.oid4vc()
|
||||
CredentialOfferURI credOfferUri = oauth.oid4vc()
|
||||
.credentialOfferUriRequest(credentialConfigurationId)
|
||||
.preAuthorized(true)
|
||||
.username("john")
|
||||
@@ -381,14 +382,11 @@ public class OID4VCSdJwtIssuingEndpointTest extends OID4VCIssuerEndpointTest {
|
||||
.send()
|
||||
.getCredentialOfferURI();
|
||||
|
||||
assertNotNull("A valid offer uri should be returned", credentialOfferURI);
|
||||
assertNotNull("A valid offer uri should be returned", credOfferUri);
|
||||
|
||||
// 2. Using the uri to get the actual credential offer
|
||||
CredentialsOffer credentialsOffer = oauth.oid4vc()
|
||||
.credentialOfferRequest(credentialOfferURI)
|
||||
.bearerToken(token)
|
||||
.send()
|
||||
.getCredentialsOffer();
|
||||
CredentialOfferResponse credentialOfferResponse = oauth.oid4vc().doCredentialOfferRequest(credOfferUri);
|
||||
CredentialsOffer credentialsOffer = credentialOfferResponse.getCredentialsOffer();
|
||||
|
||||
assertNotNull("A valid offer should be returned", credentialsOffer);
|
||||
|
||||
|
||||
+4
-2
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
|
||||
import org.keycloak.VCFormat;
|
||||
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProvider;
|
||||
import org.keycloak.protocol.oid4vc.model.Claim;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -43,8 +44,9 @@ public class OID4VCSdJwtPreInstalledNaturalPersonTest extends OID4VCIssuerEndpoi
|
||||
*/
|
||||
@Test
|
||||
public void testGetSdJwtConfigFromMetadata() {
|
||||
final String scopeName = sdJwtTypeNaturalPersonClientScope.getName();
|
||||
final String credentialConfigurationId = sdJwtTypeNaturalPersonClientScope.getAttributes().get(CONFIGURATION_ID);
|
||||
String scopeName = sdJwtTypeNaturalPersonScopeName;
|
||||
ClientScopeRepresentation clientScope = requireExistingClientScope(scopeName);
|
||||
String credentialConfigurationId = clientScope.getAttributes().get(CONFIGURATION_ID);
|
||||
String expectedIssuer = suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/" + TEST_REALM_NAME;
|
||||
testingClient
|
||||
.server(TEST_REALM_NAME)
|
||||
|
||||
+2
@@ -144,6 +144,8 @@ public abstract class OID4VCTest extends AbstractTestRealmKeycloakTest {
|
||||
protected static final String jwtTypeCredentialScopeName = "jwt-credential";
|
||||
protected static final String jwtTypeCredentialConfigurationIdName = "jwt-credential-config-id";
|
||||
|
||||
protected static final String minimalJwtTypeCredentialScopeName = "vc-with-minimal-config";
|
||||
|
||||
protected static final String TEST_CREDENTIAL_MAPPERS_FILE = "/oid4vc/test-credential-mappers.json";
|
||||
|
||||
@BeforeClass
|
||||
|
||||
Reference in New Issue
Block a user