diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java index b4e90d8ed5a..0dd32b2a10e 100644 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java @@ -864,19 +864,24 @@ public class JpaRealmProvider implements RealmProvider, ClientProvider, ClientSc Predicate attrNamePredicate = builder.equal(attributeJoin.get("name"), key); - Predicate attrValuePredicate; if (dbProductName.equals("Oracle")) { - // SELECT * FROM client_attributes WHERE ... DBMS_LOB.COMPARE(value, '0') = 0 ...; - // Oracle is not able to compare a CLOB with a VARCHAR unless it being converted with TO_CHAR - // But for this all values in the table need to be smaller than 4K, otherwise the cast will fail with - // "ORA-22835: Buffer too small for CLOB to CHAR" (even if it is in another row). - // This leaves DBMS_LOB.COMPARE as the option to compare the CLOB with the value. - attrValuePredicate = builder.equal(builder.function("DBMS_LOB.COMPARE", Integer.class, attributeJoin.get("value"), builder.literal(value)), 0); + // Use the dbms_lob.substr index and the full comparison in oracle + Predicate attrValuePredicate1 = builder.equal( + builder.function("dbms_lob.substr", Integer.class, attributeJoin.get("value"), builder.literal(255), builder.literal(1)), + builder.function("substr", Integer.class, builder.literal(value), builder.literal(1), builder.literal(255))); + Predicate attrValuePredicate2 = builder.equal(builder.function("dbms_lob.compare", Integer.class, attributeJoin.get("value"), builder.literal(value)), 0); + predicates.add(builder.and(attrNamePredicate, attrValuePredicate1, attrValuePredicate2)); + } else if (dbProductName.equals("PostgreSQL")) { + // use the substr comparison and the full comparison in postgresql + Predicate attrValuePredicate1 = builder.equal( + builder.function("substr", Integer.class, attributeJoin.get("value"), builder.literal(1), builder.literal(255)), + builder.function("substr", Integer.class, builder.literal(value), builder.literal(1), builder.literal(255))); + Predicate attrValuePredicate2 = builder.equal(attributeJoin.get("value"), value); + predicates.add(builder.and(attrNamePredicate, attrValuePredicate1, attrValuePredicate2)); } else { - attrValuePredicate = builder.equal(attributeJoin.get("value"), value); + Predicate attrValuePredicate = builder.equal(attributeJoin.get("value"), value); + predicates.add(builder.and(attrNamePredicate, attrValuePredicate)); } - - predicates.add(builder.and(attrNamePredicate, attrValuePredicate)); } Predicate finalPredicate = builder.and(predicates.toArray(new Predicate[0])); diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-24.0.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-24.0.0.xml index db287dc21d8..406867019a6 100644 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-24.0.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-24.0.0.xml @@ -50,4 +50,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java index b59d5da696f..f7f557f92f4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/AbstractMigrationTest.java @@ -82,6 +82,7 @@ import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -409,6 +410,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { testUnmanagedAttributePolicySet(migrationRealm2, null); testHS512KeyCreated(migrationRealm); testHS512KeyCreated(migrationRealm2); + testClientAttributes(migrationRealm); } if (testLdapUseTruststoreSpiMigration) { testLdapUseTruststoreSpiMigration(migrationRealm2); @@ -1254,4 +1256,20 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest { Assert.assertNotNull("Old HS256 key does not exist", keysMetadata.getActive().get(Algorithm.HS256)); Assert.assertNotNull("New HS256 key does not exist", keysMetadata.getActive().get(Algorithm.HS512)); } + + private void testClientAttributes(RealmResource realm) { + List clients = realm.clients().findByClientId("migration-saml-client"); + Assert.assertEquals(1, clients.size()); + ClientRepresentation client = clients.get(0); + Assert.assertNotNull(client.getAttributes().get("saml.artifact.binding.identifier")); + Assert.assertNotNull(client.getAttributes().get("saml_idp_initiated_sso_url_name")); + List clientIds = realm.clients().query("saml.artifact.binding.identifier:\"" + client.getAttributes().get("saml.artifact.binding.identifier") + "\"") + .stream().map(ClientRepresentation::getClientId) + .collect(Collectors.toList()); + Assert.assertEquals(Collections.singletonList(client.getClientId()), clientIds); + clientIds = realm.clients().query("saml_idp_initiated_sso_url_name:\"" + client.getAttributes().get("saml_idp_initiated_sso_url_name") + "\"") + .stream().map(ClientRepresentation::getClientId) + .collect(Collectors.toList()); + Assert.assertEquals(Collections.singletonList(client.getClientId()), clientIds); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-19.0.3.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-19.0.3.json index 5a85c082919..4301f7cd728 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-19.0.3.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-19.0.3.json @@ -600,7 +600,7 @@ "saml.encrypt" : "false", "saml_assertion_consumer_url_post" : "http://localhost:8080/sales-post/saml", "saml.server.signature" : "true", - "saml_idp_initiated_sso_url_name" : "sales-post", + "saml_idp_initiated_sso_url_name" : "sales-post-very-long-name-greater-than-255-characters-0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901", "exclude.session.state.from.auth.response" : "false", "saml.artifact.binding.identifier" : "ZDisLXkadz6IlDoL8l343V44KP0=", "saml.artifact.binding" : "false",