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",