diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java index 92de4a349e..6496411476 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java @@ -76,8 +76,7 @@ protected static AttrCond createAttrCond(final String schema) { protected static String getValue(final SearchCondition sc) { String value = SearchUtils.toSqlWildcardString( - URLDecoder.decode(sc.getStatement().getValue().toString(), StandardCharsets.UTF_8), false). - replaceAll("\\\\_", "_"); + URLDecoder.decode(sc.getStatement().getValue().toString(), StandardCharsets.UTF_8), false); // see SYNCOPE-1321 if (TIMEZONE.matcher(value).matches()) { diff --git a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java index eff4efd305..a98dc321c9 100644 --- a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java +++ b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java @@ -329,4 +329,17 @@ public void issueSYNCOPE1223() { assertEquals(SearchCond.getLeaf(cond), SearchCondConverter.convert(VISITOR, fiql)); } + + @Test + public void issueSYNCOPE1779() { + String fiql = new UserFiqlSearchConditionBuilder().is("username").equalToIgnoreCase("ros_*").query(); + assertEquals("username=~ros_*", fiql); + + AttrCond attrCond = new AnyCond(AttrCond.Type.ILIKE); + attrCond.setSchema("username"); + attrCond.setExpression("ros\\_%"); + SearchCond leaf = SearchCond.getLeaf(attrCond); + + assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql)); + } } diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OJPAJSONAnySearchDAO.java index ad4aaa9434..67216ee358 100644 --- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OJPAJSONAnySearchDAO.java +++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OJPAJSONAnySearchDAO.java @@ -244,6 +244,11 @@ protected void fillAttrQuery( query.append(lower ? "LOWER(" : ""). append('?').append(setParameter(parameters, value)). append(lower ? ")" : ""); + // workaround for Oracle DB adding explicit escaping string, to search + // for literal _ (underscore) (SYNCOPE-1779) + if (cond.getType() == AttrCond.Type.ILIKE || cond.getType() == AttrCond.Type.LIKE) { + query.append(" ESCAPE '\\' "); + } } } diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java index 6f3863ccdb..8a529246e4 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java @@ -20,7 +20,14 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.openjpa.jdbc.meta.MappingRepository; +import org.apache.openjpa.jdbc.sql.OracleDictionary; +import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory; +import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; +import org.apache.openjpa.persistence.OpenJPAPersistence; import org.apache.syncope.core.persistence.api.dao.DAO; import org.apache.syncope.core.persistence.api.entity.Entity; import org.apache.syncope.core.spring.ApplicationContextProvider; @@ -33,6 +40,8 @@ public abstract class AbstractDAO implements DAO { protected static final Logger LOG = LoggerFactory.getLogger(DAO.class); + private static final Map IS_ORACLE = new ConcurrentHashMap<>(); + protected EntityManagerFactory entityManagerFactory() { return EntityManagerFactoryUtils.findEntityManagerFactory( ApplicationContextProvider.getBeanFactory(), AuthContextUtils.getDomain()); @@ -53,4 +62,17 @@ public void refresh(final E entity) { public void detach(final E entity) { entityManager().detach(entity); } + + protected boolean isOracle() { + Boolean isOracle = IS_ORACLE.get(AuthContextUtils.getDomain()); + if (isOracle == null) { + OpenJPAEntityManagerFactory emf = OpenJPAPersistence.cast(entityManagerFactory()); + OpenJPAEntityManagerFactorySPI emfspi = (OpenJPAEntityManagerFactorySPI) OpenJPAPersistence.cast(emf); + isOracle = ((MappingRepository) emfspi.getConfiguration() + .getMetaDataRepositoryInstance()).getDBDictionary() instanceof OracleDictionary; + IS_ORACLE.put(AuthContextUtils.getDomain(), isOracle); + + } + return isOracle; + } } diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java index 622f8dd505..4e6d29aeb2 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java @@ -999,6 +999,11 @@ protected void fillAttrQuery( } else { query.append('?').append(setParameter(parameters, cond.getExpression())); } + // workaround for Oracle DB adding explicit escaping string, to search + // for literal _ (underscore) (SYNCOPE-1779) + if (isOracle()) { + query.append(" ESCAPE '\\' "); + } } else { if (!(cond instanceof AnyCond)) { query.append("' AND"); diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java index fe0eb5663d..bb13fe418f 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java @@ -875,4 +875,43 @@ public void issueSYNCOPE1727() { assertEquals(1, users.getResult().size()); assertEquals(user.getKey(), users.getResult().get(0).getKey()); } + + @Test + public void issueSYNCOPE1779() { + // 1. create user with underscore + UserTO userWithUnderscore = createUser(UserITCase.getSample("syncope1779_test@syncope.apache.org")).getEntity(); + // 2 create second user without underscore + createUser(UserITCase.getSample("syncope1779test@syncope.apache.org")).getEntity(); + + // 3. search for user + if (IS_ELASTICSEARCH_ENABLED) { + try { + Thread.sleep(2000); + } catch (InterruptedException ex) { + // ignore + } + } + + // Search by username + PagedResult users = USER_SERVICE.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM) + .fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("syncope1779_*") + .and().is("firstname").equalTo("syncope1779_*") + .and().is("userId").equalTo("syncope1779_*").query()) + .build()); + assertEquals(1, users.getResult().size()); + assertEquals(userWithUnderscore.getKey(), users.getResult().get(0).getKey()); + // Search also by attribute + users = USER_SERVICE.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM) + .fiql(SyncopeClient.getUserSearchConditionBuilder().is("email").equalTo("syncope1779_*").query()) + .build()); + assertEquals(1, users.getResult().size()); + assertEquals(userWithUnderscore.getKey(), users.getResult().get(0).getKey()); + // search for both + users = USER_SERVICE.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM) + .fiql(SyncopeClient.getUserSearchConditionBuilder().is("email").equalTo("syncope1779*").query()) + .build()); + assertEquals(2, users.getResult().size()); + + users.getResult().forEach(u -> USER_SERVICE.delete(u.getKey())); + } }