mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-21 14:30:05 -06:00
Enforce batch_size ≥ 2 validation for batch_credential_issuance (#42003)
Closes #41590 Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com>
This commit is contained in:
committed by
GitHub
parent
dc6afee14e
commit
a74076e8ab
@@ -29,6 +29,8 @@ public final class Oid4VciConstants {
|
||||
|
||||
public static final String CREDENTIAL_SUBJECT = "credentialSubject";
|
||||
|
||||
public static final String BATCH_CREDENTIAL_ISSUANCE_BATCH_SIZE = "batch_credential_issuance.batch_size";
|
||||
|
||||
private Oid4VciConstants() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,14 +91,29 @@ public class OID4VCIssuerWellKnownProvider implements WellKnownProvider {
|
||||
}
|
||||
|
||||
private CredentialIssuer.BatchCredentialIssuance getBatchCredentialIssuance(KeycloakSession session) {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
String batchSize = realm.getAttribute("batch_credential_issuance.batch_size");
|
||||
return getBatchCredentialIssuance(session.getContext().getRealm());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the batch credential issuance configuration for the given realm.
|
||||
* This method is public and static to facilitate testing without requiring session state management.
|
||||
*
|
||||
* @param realm The realm model
|
||||
* @return The batch credential issuance configuration or null if not configured or invalid
|
||||
*/
|
||||
public static CredentialIssuer.BatchCredentialIssuance getBatchCredentialIssuance(RealmModel realm) {
|
||||
String batchSize = realm.getAttribute(Oid4VciConstants.BATCH_CREDENTIAL_ISSUANCE_BATCH_SIZE);
|
||||
if (batchSize != null) {
|
||||
try {
|
||||
int parsedBatchSize = Integer.parseInt(batchSize);
|
||||
if (parsedBatchSize < 2) {
|
||||
LOGGER.warnf("%s must be 2 or greater, but was %d. Skipping batch_credential_issuance.", Oid4VciConstants.BATCH_CREDENTIAL_ISSUANCE_BATCH_SIZE, parsedBatchSize);
|
||||
return null;
|
||||
}
|
||||
return new CredentialIssuer.BatchCredentialIssuance()
|
||||
.setBatchSize(Integer.parseInt(batchSize));
|
||||
.setBatchSize(parsedBatchSize);
|
||||
} catch (Exception e) {
|
||||
LOGGER.warnf(e, "Failed to parse batch_credential_issuance.batch_size from realm attributes.");
|
||||
LOGGER.warnf(e, "Failed to parse %s from realm attributes.", Oid4VciConstants.BATCH_CREDENTIAL_ISSUANCE_BATCH_SIZE);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -75,6 +75,7 @@ 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.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProvider.ATTR_ENCRYPTION_REQUIRED;
|
||||
import org.keycloak.constants.Oid4VciConstants;
|
||||
|
||||
|
||||
public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest {
|
||||
@@ -83,7 +84,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
Map<String, String> attributes = Optional.ofNullable(testRealm.getAttributes()).orElseGet(HashMap::new);
|
||||
attributes.put("credential_response_encryption.encryption_required", "true");
|
||||
attributes.put("batch_credential_issuance.batch_size", "10");
|
||||
attributes.put(Oid4VciConstants.BATCH_CREDENTIAL_ISSUANCE_BATCH_SIZE, "10");
|
||||
attributes.put("signed_metadata", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.XYZ123abc");
|
||||
attributes.put(ATTR_ENCRYPTION_REQUIRED, "true");
|
||||
testRealm.setAttributes(attributes);
|
||||
@@ -190,7 +191,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
|
||||
realm.setAttribute(ATTR_ENCRYPTION_REQUIRED, "true");
|
||||
realm.setAttribute("batch_credential_issuance.batch_size", "10");
|
||||
realm.setAttribute(Oid4VciConstants.BATCH_CREDENTIAL_ISSUANCE_BATCH_SIZE, "10");
|
||||
realm.setAttribute("signed_metadata", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.XYZ123abc");
|
||||
|
||||
OID4VCIssuerWellKnownProvider provider = new OID4VCIssuerWellKnownProvider(session);
|
||||
@@ -413,6 +414,57 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchCredentialIssuanceValidation() {
|
||||
KeycloakTestingClient testingClient = this.testingClient;
|
||||
|
||||
// Valid batch size (2 or greater) should be accepted
|
||||
testBatchSizeValidation(testingClient, "5", true, 5);
|
||||
|
||||
// Invalid batch size (less than 2) should be rejected
|
||||
testBatchSizeValidation(testingClient, "1", false, null);
|
||||
|
||||
// Edge case - batch size exactly 2 should be accepted
|
||||
testBatchSizeValidation(testingClient, "2", true, 2);
|
||||
|
||||
// Zero batch size should be rejected
|
||||
testBatchSizeValidation(testingClient, "0", false, null);
|
||||
|
||||
// Negative batch size should be rejected
|
||||
testBatchSizeValidation(testingClient, "-1", false, null);
|
||||
|
||||
// Large valid batch size should be accepted
|
||||
testBatchSizeValidation(testingClient, "1000", true, 1000);
|
||||
|
||||
// Non-numeric value should be rejected (parsing exception)
|
||||
testBatchSizeValidation(testingClient, "invalid", false, null);
|
||||
}
|
||||
|
||||
private void testBatchSizeValidation(KeycloakTestingClient testingClient, String batchSize, boolean shouldBePresent, Integer expectedValue) {
|
||||
testingClient
|
||||
.server(TEST_REALM_NAME)
|
||||
.run(session -> {
|
||||
// Create a new isolated realm for testing
|
||||
RealmModel testRealm = session.realms().createRealm("test-batch-validation-" + batchSize);
|
||||
|
||||
try {
|
||||
testRealm.setAttribute(Oid4VciConstants.BATCH_CREDENTIAL_ISSUANCE_BATCH_SIZE, batchSize);
|
||||
|
||||
CredentialIssuer.BatchCredentialIssuance result = OID4VCIssuerWellKnownProvider.getBatchCredentialIssuance(testRealm);
|
||||
|
||||
if (shouldBePresent) {
|
||||
Assert.assertNotNull("batch_credential_issuance should be present for batch size " + batchSize, result);
|
||||
Assert.assertEquals("batch_credential_issuance should have correct batch size for " + batchSize,
|
||||
expectedValue, result.getBatchSize());
|
||||
} else {
|
||||
Assert.assertNull("batch_credential_issuance should be null for invalid batch size " + batchSize, result);
|
||||
}
|
||||
} finally {
|
||||
session.realms().removeRealm(testRealm.getId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void extendConfigureTestRealm(RealmRepresentation testRealm, ClientRepresentation clientRepresentation) {
|
||||
if (testRealm.getComponents() == null) {
|
||||
testRealm.setComponents(new MultivaluedHashMap<>());
|
||||
|
||||
Reference in New Issue
Block a user