Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.WebTarget;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
Expand All @@ -44,6 +49,7 @@
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProviderFactory;
import org.keycloak.protocol.oid4vc.issuance.TimeProvider;
import org.keycloak.protocol.oid4vc.issuance.signing.JwtSigningService;
import org.keycloak.protocol.oid4vc.model.CredentialIssuer;
Expand All @@ -62,7 +68,9 @@
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.runonserver.RunOnServerException;
import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.util.JsonSerialization;

Expand Down Expand Up @@ -369,7 +377,7 @@ public void testCredentialIssuance() throws Exception {

// 1. Retrieving the credential-offer-uri
HttpGet getCredentialOfferURI = new HttpGet(getBasePath(TEST_REALM_NAME) + "credential-offer-uri?credential_configuration_id=test-credential");
getCredentialOfferURI.addHeader("Authorization", "Bearer " + token);
getCredentialOfferURI.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
CloseableHttpResponse credentialOfferURIResponse = httpClient.execute(getCredentialOfferURI);

assertEquals("A valid offer uri should be returned", HttpStatus.SC_OK, credentialOfferURIResponse.getStatusLine().getStatusCode());
Expand All @@ -378,7 +386,7 @@ public void testCredentialIssuance() throws Exception {

// 2. Using the uri to get the actual credential offer
HttpGet getCredentialOffer = new HttpGet(credentialOfferURI.getIssuer() + "/" + credentialOfferURI.getNonce());
getCredentialOffer.addHeader("Authorization", "Bearer " + token);
getCredentialOffer.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
CloseableHttpResponse credentialOfferResponse = httpClient.execute(getCredentialOffer);

assertEquals("A valid offer should be returned", HttpStatus.SC_OK, credentialOfferResponse.getStatusLine().getStatusCode());
Expand Down Expand Up @@ -430,6 +438,62 @@ public void testCredentialIssuance() throws Exception {
}


// 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 {

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))) {
CredentialResponse credentialResponse = JsonSerialization.readValue(response.readEntity(String.class),CredentialResponse.class);

assertEquals(200, response.getStatus());
JsonWebToken jsonWebToken = TokenVerifier.create((String) credentialResponse.getCredential(), JsonWebToken.class).getToken();
assertEquals("did:web:test.org", jsonWebToken.getIssuer());

VerifiableCredential credential = new ObjectMapper().convertValue(jsonWebToken.getOtherClaims().get("vc"), VerifiableCredential.class);
assertEquals(TEST_TYPES, credential.getType());
assertEquals(TEST_DID, credential.getIssuer());
assertEquals("john@email.cz", credential.getCredentialSubject().getClaims().get("email"));
}
}
}
}
}



private static String prepareNonce(AppAuthManager.BearerTokenAuthenticator authenticator, String note) {
String nonce = SecretGenerator.getInstance().randomString();
AuthenticationManager.AuthResult authResult = authenticator.authenticate();
Expand Down Expand Up @@ -457,7 +521,11 @@ private static OID4VCIssuerEndpoint prepareIssuerEndpoint(KeycloakSession sessio
}

private String getBasePath(String realm) {
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/" + realm + "/protocol/oid4vc/";
return getRealmPath(realm) + "/protocol/oid4vc/";
}

private String getRealmPath(String realm){
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/" + realm;
}

private void requestOffer(String token, String credentialEndpoint, SupportedCredentialConfiguration offeredCredential) throws IOException, VerificationException {
Expand All @@ -468,7 +536,7 @@ private void requestOffer(String token, String credentialEndpoint, SupportedCred
StringEntity stringEntity = new StringEntity(OBJECT_MAPPER.writeValueAsString(request), ContentType.APPLICATION_JSON);

HttpPost postCredential = new HttpPost(credentialEndpoint);
postCredential.addHeader("Authorization", "Bearer " + token);
postCredential.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
postCredential.setEntity(stringEntity);
CloseableHttpResponse credentialRequestResponse = httpClient.execute(postCredential);
assertEquals(HttpStatus.SC_OK, credentialRequestResponse.getStatusLine().getStatusCode());
Expand Down