Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Wiedemann <[email protected]>
  • Loading branch information
wistefan committed Jul 30, 2024
1 parent 8883ca8 commit c1460b4
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
* Represents a CredentialsOffer according to the OID4VCI Spec
Expand Down Expand Up @@ -68,4 +69,16 @@ public CredentialsOffer setGrants(PreAuthorizedGrant grants) {
this.grants = grants;
return this;
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CredentialsOffer that)) return false;
return Objects.equals(getCredentialIssuer(), that.getCredentialIssuer()) && Objects.equals(getCredentialConfigurationIds(), that.getCredentialConfigurationIds()) && Objects.equals(getGrants(), that.getGrants());
}

@Override
public int hashCode() {
return Objects.hash(getCredentialIssuer(), getCredentialConfigurationIds(), getGrants());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Objects;

/**
* Represents a pre-authorized grant, as used by the Credential Offer in OID4VCI
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer}
Expand Down Expand Up @@ -76,4 +78,16 @@ public PreAuthorizedCode setAuthorizationServer(String authorizationServer) {
this.authorizationServer = authorizationServer;
return this;
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PreAuthorizedCode that)) return false;
return getInterval() == that.getInterval() && Objects.equals(getPreAuthorizedCode(), that.getPreAuthorizedCode()) && Objects.equals(getTxCode(), that.getTxCode()) && Objects.equals(getAuthorizationServer(), that.getAuthorizationServer());
}

@Override
public int hashCode() {
return Objects.hash(getPreAuthorizedCode(), getTxCode(), getInterval(), getAuthorizationServer());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantType;
import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory;

import java.util.Objects;

/**
* Container for the pre-authorized code to be used in a Credential Offer
* <p>
Expand All @@ -43,4 +45,16 @@ public PreAuthorizedGrant setPreAuthorizedCode(PreAuthorizedCode preAuthorizedCo
this.preAuthorizedCode = preAuthorizedCode;
return this;
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PreAuthorizedGrant grant)) return false;
return Objects.equals(getPreAuthorizedCode(), grant.getPreAuthorizedCode());
}

@Override
public int hashCode() {
return Objects.hash(getPreAuthorizedCode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public static String persistCode(KeycloakSession session, AuthenticatedClientSes

/**
* Will parse the code and retrieve the corresponding OAuth2Code and AuthenticatedClientSessionModel. Will also check if code wasn't already
* used and if it wasn't expired. If it was already used (or other error happened during parsing), then returned parser will have "isIllegalHash"
* used and if it wasn't expired. If it was already used (or other error happened during parsing), then returned parser will have "isIllegalCode"
* set to true. If it was expired, the parser will have "isExpired" set to true
*
* @param session
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.Algorithm;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
Expand All @@ -53,6 +55,8 @@
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
import org.keycloak.protocol.oid4vc.model.VerifiableCredential;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.OAuth2Code;
import org.keycloak.protocol.oidc.utils.OAuth2CodeParser;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
Expand Down Expand Up @@ -80,6 +84,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint.CREDENTIAL_OFFER_URI_CODE_SCOPE;

/**
* Moved test to subclass. so we can reuse initialization code.
Expand Down Expand Up @@ -244,12 +249,19 @@ protected void testCredentialIssuanceWithAuthZCodeFlow(BiFunction<String, String
});
}

protected static String prepareNonce(AppAuthManager.BearerTokenAuthenticator authenticator, String note) {
String nonce = SecretGenerator.getInstance().randomString();
protected static String prepareSessionCode(KeycloakSession session, AppAuthManager.BearerTokenAuthenticator authenticator, String note) {
AuthenticationManager.AuthResult authResult = authenticator.authenticate();
UserSessionModel userSessionModel = authResult.getSession();
userSessionModel.getAuthenticatedClientSessionByClient(authResult.getClient().getId()).setNote(nonce, note);
return nonce;
AuthenticatedClientSessionModel authenticatedClientSessionModel = userSessionModel.getAuthenticatedClientSessionByClient(authResult.getClient().getId());
String codeId = SecretGenerator.getInstance().randomString();
String nonce = SecretGenerator.getInstance().randomString();
OAuth2Code oAuth2Code = new OAuth2Code(codeId, Time.currentTime() + 6000, nonce, CREDENTIAL_OFFER_URI_CODE_SCOPE, null, null, null,
authenticatedClientSessionModel.getUserSession().getId());

String oauthCode = OAuth2CodeParser.persistCode(session, authenticatedClientSessionModel, oAuth2Code);

authenticatedClientSessionModel.setNote(oauthCode, note);
return oauthCode;
}

protected static OID4VCIssuerEndpoint prepareIssuerEndpoint(KeycloakSession session, AppAuthManager.BearerTokenAuthenticator authenticator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
import org.keycloak.protocol.oid4vc.model.Format;
import org.keycloak.protocol.oid4vc.model.OfferUriType;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode;
import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant;
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
import org.keycloak.protocol.oid4vc.model.VerifiableCredential;
Expand Down Expand Up @@ -185,7 +186,7 @@ public void testGetCredentialOfferWithABrokenNote() throws Throwable {
.run((session -> {
AppAuthManager.BearerTokenAuthenticator authenticator = new AppAuthManager.BearerTokenAuthenticator(session);
authenticator.setTokenString(token);
String nonce = prepareNonce(authenticator, "invalidNote");
String nonce = prepareSessionCode(session, authenticator, "invalidNote");
OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator);
issuerEndpoint.getCredentialOffer(nonce);
}));
Expand All @@ -195,41 +196,28 @@ public void testGetCredentialOfferWithABrokenNote() throws Throwable {
@Test
public void testGetCredentialOffer() {
String token = getBearerToken(oauth);
String rootURL = suiteContext.getAuthServerInfo().getContextRoot().toString();
testingClient
.server(TEST_REALM_NAME)
.run((session) -> {
AppAuthManager.BearerTokenAuthenticator authenticator = new AppAuthManager.BearerTokenAuthenticator(session);
authenticator.setTokenString(token);

SupportedCredentialConfiguration supportedCredentialConfiguration = new SupportedCredentialConfiguration()
.setId("test-credential")
.setScope("VerifiableCredential")
.setFormat(Format.JWT_VC);
String nonce = prepareNonce(authenticator, JsonSerialization.writeValueAsString(supportedCredentialConfiguration));

CredentialsOffer credentialsOffer = new CredentialsOffer()
.setCredentialIssuer("the-issuer")
.setGrants(new PreAuthorizedGrant().setPreAuthorizedCode(new PreAuthorizedCode().setPreAuthorizedCode("the-code")))
.setCredentialConfigurationIds(List.of("credential-configuration-id"));

String sessionCode = prepareSessionCode(session, authenticator, JsonSerialization.writeValueAsString(credentialsOffer));
// the cache transactions need to be commited explicitly in the test. Without that, the OAuth2Code will only be commited to
// the cache after .run((session)-> ...)
session.getTransactionManager().commit();
OID4VCIssuerEndpoint issuerEndpoint = prepareIssuerEndpoint(session, authenticator);
Response credentialOfferResponse = issuerEndpoint.getCredentialOffer(nonce);
Response credentialOfferResponse = issuerEndpoint.getCredentialOffer(sessionCode);
assertEquals("The offer should have been returned.", HttpStatus.SC_OK, credentialOfferResponse.getStatus());
Object credentialOfferEntity = credentialOfferResponse.getEntity();
assertNotNull("An actual offer should be in the response.", credentialOfferEntity);

CredentialsOffer credentialsOffer = JsonSerialization.mapper.convertValue(credentialOfferEntity, CredentialsOffer.class);
assertNotNull("Credentials should have been offered.", credentialsOffer.getCredentialConfigurationIds());
assertFalse("Credentials should have been offered.", credentialsOffer.getCredentialConfigurationIds().isEmpty());
List<String> supportedCredentials = credentialsOffer.getCredentialConfigurationIds();
assertEquals("Exactly one credential should have been returned.", 1, supportedCredentials.size());
String offeredCredentialId = supportedCredentials.get(0);
assertEquals("The credential should be as defined in the note.", supportedCredentialConfiguration.getId(), offeredCredentialId);

PreAuthorizedGrant grant = credentialsOffer.getGrants();
assertNotNull("The grant should be included.", grant);
assertNotNull("The grant should contain the pre-authorized code.", grant.getPreAuthorizedCode());
assertNotNull("The actual pre-authorized code should be included.", grant
.getPreAuthorizedCode()
.getPreAuthorizedCode());

assertEquals("The correct issuer should be included.", rootURL + "/auth/realms/" + TEST_REALM_NAME, credentialsOffer.getCredentialIssuer());
CredentialsOffer retrievedCredentialsOffer = JsonSerialization.mapper.convertValue(credentialOfferEntity, CredentialsOffer.class);
assertEquals("The offer should be the one prepared with for the session.", credentialsOffer, retrievedCredentialsOffer);
});
}

Expand Down Expand Up @@ -350,7 +338,6 @@ 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(HttpHeaders.AUTHORIZATION, "Bearer " + token);
CloseableHttpResponse credentialOfferResponse = httpClient.execute(getCredentialOffer);

assertEquals("A valid offer should be returned", HttpStatus.SC_OK, credentialOfferResponse.getStatusLine().getStatusCode());
Expand Down

0 comments on commit c1460b4

Please sign in to comment.