github-advanced-security[bot] commented on code in PR #1300:
URL: https://github.com/apache/syncope/pull/1300#discussion_r2845885064
##########
core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java:
##########
@@ -89,68 +152,518 @@
return neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.fullPath =
$fullPath RETURN n.id").
bindAll(Map.of("fullPath", fullPath)).fetch().one().
- flatMap(toOptional("n.id", Neo4jRealm.class, cache));
+ flatMap(found ->
realmDAO.findById(found.get("n.id").toString()).map(n -> (Realm) n));
+ }
+
+ protected List<Realm> toList(
+ final Collection<Map<String, Object>> result,
+ final String property) {
+
+ return result.stream().
+ map(found ->
realmDAO.findById(found.get(property).toString())).
+ flatMap(Optional::stream).map(n -> (Realm) n).toList();
}
@Override
public List<Realm> findByName(final String name) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.name = $name RETURN
n.id").
- bindAll(Map.of("name", name)).fetch().all(), "n.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("name", name)).fetch().all(), "n.id");
}
@Override
public List<Realm> findChildren(final Realm realm) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + " {id: $id})<-[r:" +
Neo4jRealm.PARENT_REL + "]-(c) RETURN c.id").
- bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id");
}
@Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
+ public List<Realm> findDescendants(final String base, final String prefix)
{
+ Map<String, Object> parameters = new HashMap<>();
+
+ StringBuilder query = new StringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WHERE (").append("n.fullPath = $base OR n.fullPath =~
$like").append(')');
+ parameters.put("base", base);
+ parameters.put("like", SyncopeConstants.ROOT_REALM.equals(base) ?
"/.*" : base + "/.*");
+
+ if (prefix != null) {
+ query.append(" AND (n.fullPath = $prefix OR n.fullPath =~
$likePrefix)");
+ parameters.put("prefix", prefix);
+ parameters.put("likePrefix",
SyncopeConstants.ROOT_REALM.equals(prefix) ? "/.*" : prefix + "/.*");
+ }
+
+ query.append(" RETURN n.id ORDER BY n.fullPath");
+
+ return toList(neo4jClient.query(
+ query.toString()).bindAll(parameters).fetch().all(), "n.id");
}
- @Override
- public long countDescendants(final Set<String> bases, final String
keyword) {
- Map<String, Object> parameters = new HashMap<>();
+ protected QueryInfo getQuery(final SearchCond cond, final Map<String,
Object> parameters) {
+ boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
+
+ TextStringBuilder query = new TextStringBuilder();
+ Set<String> involvedFields = new HashSet<>();
+ Set<PlainSchema> involvedPlainSchemas = new HashSet<>();
- StringBuilder queryString = buildDescendantsQuery(bases, keyword,
parameters).append(" RETURN COUNT(n)");
- return neo4jTemplate.count(queryString.toString(), parameters);
+ switch (cond.getType()) {
+ case LEAF, NOT_LEAF -> {
+ cond.asLeaf(AnyCond.class).ifPresentOrElse(
+ anyCond -> {
+ AnyCondQuery anyCondQuery = getQuery(anyCond, not,
parameters);
+ query.append(anyCondQuery.query());
+
Optional.ofNullable(anyCondQuery.field()).ifPresent(involvedFields::add);
+ },
+ () -> cond.asLeaf(AttrCond.class).ifPresent(leaf -> {
+ AttrCondQuery attrCondQuery = getQuery(leaf, not,
parameters);
+ query.append(attrCondQuery.query());
+ involvedPlainSchemas.add(attrCondQuery.schema());
+ }));
+
+ // allow for additional search conditions
+ getQueryForCustomConds(cond, parameters, not, query);
+ }
+ case AND -> {
+ QueryInfo leftAndInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftAndInfo.fields());
+ involvedPlainSchemas.addAll(leftAndInfo.plainSchemas());
+
+ QueryInfo rigthAndInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthAndInfo.fields());
+ involvedPlainSchemas.addAll(rigthAndInfo.plainSchemas());
+
+ queryOp(query, "AND", leftAndInfo, rigthAndInfo);
+ }
+
+ case OR -> {
+ QueryInfo leftOrInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftOrInfo.fields());
+ involvedPlainSchemas.addAll(leftOrInfo.plainSchemas());
+
+ QueryInfo rigthOrInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthOrInfo.fields());
+ involvedPlainSchemas.addAll(rigthOrInfo.plainSchemas());
+
+ queryOp(query, "OR", leftOrInfo, rigthOrInfo);
+ }
+
+ default -> {
+ }
+ }
+
+ return new QueryInfo(query, involvedFields, involvedPlainSchemas);
}
- @Override
- public List<Realm> findDescendants(final String base, final String
keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
+ protected void wrapQuery(
+ final Set<String> bases,
+ final QueryInfo queryInfo,
+ final Streamable<Order> orderBy,
+ final Map<String, Object> parameters) {
+
+ TextStringBuilder match = new TextStringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WITH n.id AS id");
+
+ // take fields into account
+ queryInfo.fields().remove("id");
+ Stream.concat(
+ queryInfo.fields().stream(),
+ orderBy.stream().filter(clause ->
!"id".equals(clause.getProperty())
+ &&
realmUtils.getField(clause.getProperty()).isPresent()).map(Order::getProperty)).
+ distinct().forEach(field -> match.append(",
n.").append(field).append(" AS ").append(field));
+
+ // take plain schemas into account
+ Stream.concat(
+ queryInfo.plainSchemas().stream(),
+ orderBy.stream().map(clause ->
plainSchemaDAO.findById(clause.getProperty())).
+ flatMap(Optional::stream)).distinct().forEach(schema
-> {
+
+ match.append(", apoc.convert.getJsonProperty(n,
'plainAttrs.").append(schema.getKey());
+ if (schema.isUniqueConstraint()) {
+ match.append("', '$.uniqueValue')");
+ } else {
+ match.append("', '$.values')");
+ }
+ match.append(" AS ").append(schema.getKey());
+ });
+
+ TextStringBuilder query = queryInfo.query();
+
+ // take bases into account
+ AtomicInteger index = new AtomicInteger(0);
+ String basesClause = bases.stream().map(base -> {
+ int idx = index.incrementAndGet();
+ parameters.put("base" + idx, base);
+ parameters.put("like" + idx,
SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base + "/.*");
+ return "n.fullPath = $base" + idx + " OR n.fullPath =~ $like" +
idx;
+ }).collect(Collectors.joining(" OR "));
+ if (query.startsWith("MATCH (n)")) {
+ query.replaceFirst("MATCH (n)", match + " WHERE (EXISTS { MATCH
(n)");
+ query.append("} ");
+ } else {
+ query.replaceFirst("WHERE EXISTS", "WHERE (EXISTS");
+ query.insert(0, match.append(' '));
+ }
+ query.append(") AND EXISTS { ").append("(n) WHERE
(").append(basesClause).append(")").append(" } ");
+ }
+
+ protected AttrCondQuery getQuery(
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ CheckResult<AttrCond> checked = check(cond);
+
+ TextStringBuilder query = new TextStringBuilder("MATCH (n) ");
+ switch (cond.getType()) {
+ case ISNOTNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NOT NULL");
+
+ case ISNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NULL");
+
+ default ->
+ fillAttrQuery(query, checked.value(), checked.schema(), cond,
not, parameters);
+ }
+
+ return new AttrCondQuery(query.toString(), checked.schema());
+ }
+
+ protected void getQueryForCustomConds(
+ final SearchCond cond,
Review Comment:
## Useless parameter
The parameter 'cond' is never used.
[Show more
details](https://github.com/apache/syncope/security/code-scanning/2577)
##########
core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java:
##########
@@ -89,68 +152,518 @@
return neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.fullPath =
$fullPath RETURN n.id").
bindAll(Map.of("fullPath", fullPath)).fetch().one().
- flatMap(toOptional("n.id", Neo4jRealm.class, cache));
+ flatMap(found ->
realmDAO.findById(found.get("n.id").toString()).map(n -> (Realm) n));
+ }
+
+ protected List<Realm> toList(
+ final Collection<Map<String, Object>> result,
+ final String property) {
+
+ return result.stream().
+ map(found ->
realmDAO.findById(found.get(property).toString())).
+ flatMap(Optional::stream).map(n -> (Realm) n).toList();
}
@Override
public List<Realm> findByName(final String name) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.name = $name RETURN
n.id").
- bindAll(Map.of("name", name)).fetch().all(), "n.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("name", name)).fetch().all(), "n.id");
}
@Override
public List<Realm> findChildren(final Realm realm) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + " {id: $id})<-[r:" +
Neo4jRealm.PARENT_REL + "]-(c) RETURN c.id").
- bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id");
}
@Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
+ public List<Realm> findDescendants(final String base, final String prefix)
{
+ Map<String, Object> parameters = new HashMap<>();
+
+ StringBuilder query = new StringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WHERE (").append("n.fullPath = $base OR n.fullPath =~
$like").append(')');
+ parameters.put("base", base);
+ parameters.put("like", SyncopeConstants.ROOT_REALM.equals(base) ?
"/.*" : base + "/.*");
+
+ if (prefix != null) {
+ query.append(" AND (n.fullPath = $prefix OR n.fullPath =~
$likePrefix)");
+ parameters.put("prefix", prefix);
+ parameters.put("likePrefix",
SyncopeConstants.ROOT_REALM.equals(prefix) ? "/.*" : prefix + "/.*");
+ }
+
+ query.append(" RETURN n.id ORDER BY n.fullPath");
+
+ return toList(neo4jClient.query(
+ query.toString()).bindAll(parameters).fetch().all(), "n.id");
}
- @Override
- public long countDescendants(final Set<String> bases, final String
keyword) {
- Map<String, Object> parameters = new HashMap<>();
+ protected QueryInfo getQuery(final SearchCond cond, final Map<String,
Object> parameters) {
+ boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
+
+ TextStringBuilder query = new TextStringBuilder();
+ Set<String> involvedFields = new HashSet<>();
+ Set<PlainSchema> involvedPlainSchemas = new HashSet<>();
- StringBuilder queryString = buildDescendantsQuery(bases, keyword,
parameters).append(" RETURN COUNT(n)");
- return neo4jTemplate.count(queryString.toString(), parameters);
+ switch (cond.getType()) {
+ case LEAF, NOT_LEAF -> {
+ cond.asLeaf(AnyCond.class).ifPresentOrElse(
+ anyCond -> {
+ AnyCondQuery anyCondQuery = getQuery(anyCond, not,
parameters);
+ query.append(anyCondQuery.query());
+
Optional.ofNullable(anyCondQuery.field()).ifPresent(involvedFields::add);
+ },
+ () -> cond.asLeaf(AttrCond.class).ifPresent(leaf -> {
+ AttrCondQuery attrCondQuery = getQuery(leaf, not,
parameters);
+ query.append(attrCondQuery.query());
+ involvedPlainSchemas.add(attrCondQuery.schema());
+ }));
+
+ // allow for additional search conditions
+ getQueryForCustomConds(cond, parameters, not, query);
+ }
+ case AND -> {
+ QueryInfo leftAndInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftAndInfo.fields());
+ involvedPlainSchemas.addAll(leftAndInfo.plainSchemas());
+
+ QueryInfo rigthAndInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthAndInfo.fields());
+ involvedPlainSchemas.addAll(rigthAndInfo.plainSchemas());
+
+ queryOp(query, "AND", leftAndInfo, rigthAndInfo);
+ }
+
+ case OR -> {
+ QueryInfo leftOrInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftOrInfo.fields());
+ involvedPlainSchemas.addAll(leftOrInfo.plainSchemas());
+
+ QueryInfo rigthOrInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthOrInfo.fields());
+ involvedPlainSchemas.addAll(rigthOrInfo.plainSchemas());
+
+ queryOp(query, "OR", leftOrInfo, rigthOrInfo);
+ }
+
+ default -> {
+ }
+ }
+
+ return new QueryInfo(query, involvedFields, involvedPlainSchemas);
}
- @Override
- public List<Realm> findDescendants(final String base, final String
keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
+ protected void wrapQuery(
+ final Set<String> bases,
+ final QueryInfo queryInfo,
+ final Streamable<Order> orderBy,
+ final Map<String, Object> parameters) {
+
+ TextStringBuilder match = new TextStringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WITH n.id AS id");
+
+ // take fields into account
+ queryInfo.fields().remove("id");
+ Stream.concat(
+ queryInfo.fields().stream(),
+ orderBy.stream().filter(clause ->
!"id".equals(clause.getProperty())
+ &&
realmUtils.getField(clause.getProperty()).isPresent()).map(Order::getProperty)).
+ distinct().forEach(field -> match.append(",
n.").append(field).append(" AS ").append(field));
+
+ // take plain schemas into account
+ Stream.concat(
+ queryInfo.plainSchemas().stream(),
+ orderBy.stream().map(clause ->
plainSchemaDAO.findById(clause.getProperty())).
+ flatMap(Optional::stream)).distinct().forEach(schema
-> {
+
+ match.append(", apoc.convert.getJsonProperty(n,
'plainAttrs.").append(schema.getKey());
+ if (schema.isUniqueConstraint()) {
+ match.append("', '$.uniqueValue')");
+ } else {
+ match.append("', '$.values')");
+ }
+ match.append(" AS ").append(schema.getKey());
+ });
+
+ TextStringBuilder query = queryInfo.query();
+
+ // take bases into account
+ AtomicInteger index = new AtomicInteger(0);
+ String basesClause = bases.stream().map(base -> {
+ int idx = index.incrementAndGet();
+ parameters.put("base" + idx, base);
+ parameters.put("like" + idx,
SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base + "/.*");
+ return "n.fullPath = $base" + idx + " OR n.fullPath =~ $like" +
idx;
+ }).collect(Collectors.joining(" OR "));
+ if (query.startsWith("MATCH (n)")) {
+ query.replaceFirst("MATCH (n)", match + " WHERE (EXISTS { MATCH
(n)");
+ query.append("} ");
+ } else {
+ query.replaceFirst("WHERE EXISTS", "WHERE (EXISTS");
+ query.insert(0, match.append(' '));
+ }
+ query.append(") AND EXISTS { ").append("(n) WHERE
(").append(basesClause).append(")").append(" } ");
+ }
+
+ protected AttrCondQuery getQuery(
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ CheckResult<AttrCond> checked = check(cond);
+
+ TextStringBuilder query = new TextStringBuilder("MATCH (n) ");
+ switch (cond.getType()) {
+ case ISNOTNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NOT NULL");
+
+ case ISNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NULL");
+
+ default ->
+ fillAttrQuery(query, checked.value(), checked.schema(), cond,
not, parameters);
+ }
+
+ return new AttrCondQuery(query.toString(), checked.schema());
+ }
+
+ protected void getQueryForCustomConds(
+ final SearchCond cond,
+ final Map<String, Object> parameters,
Review Comment:
## Useless parameter
The parameter 'parameters' is never used.
[Show more
details](https://github.com/apache/syncope/security/code-scanning/2578)
##########
core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java:
##########
@@ -89,68 +152,518 @@
return neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.fullPath =
$fullPath RETURN n.id").
bindAll(Map.of("fullPath", fullPath)).fetch().one().
- flatMap(toOptional("n.id", Neo4jRealm.class, cache));
+ flatMap(found ->
realmDAO.findById(found.get("n.id").toString()).map(n -> (Realm) n));
+ }
+
+ protected List<Realm> toList(
+ final Collection<Map<String, Object>> result,
+ final String property) {
+
+ return result.stream().
+ map(found ->
realmDAO.findById(found.get(property).toString())).
+ flatMap(Optional::stream).map(n -> (Realm) n).toList();
}
@Override
public List<Realm> findByName(final String name) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.name = $name RETURN
n.id").
- bindAll(Map.of("name", name)).fetch().all(), "n.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("name", name)).fetch().all(), "n.id");
}
@Override
public List<Realm> findChildren(final Realm realm) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + " {id: $id})<-[r:" +
Neo4jRealm.PARENT_REL + "]-(c) RETURN c.id").
- bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id");
}
@Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
+ public List<Realm> findDescendants(final String base, final String prefix)
{
+ Map<String, Object> parameters = new HashMap<>();
+
+ StringBuilder query = new StringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WHERE (").append("n.fullPath = $base OR n.fullPath =~
$like").append(')');
+ parameters.put("base", base);
+ parameters.put("like", SyncopeConstants.ROOT_REALM.equals(base) ?
"/.*" : base + "/.*");
+
+ if (prefix != null) {
+ query.append(" AND (n.fullPath = $prefix OR n.fullPath =~
$likePrefix)");
+ parameters.put("prefix", prefix);
+ parameters.put("likePrefix",
SyncopeConstants.ROOT_REALM.equals(prefix) ? "/.*" : prefix + "/.*");
+ }
+
+ query.append(" RETURN n.id ORDER BY n.fullPath");
+
+ return toList(neo4jClient.query(
+ query.toString()).bindAll(parameters).fetch().all(), "n.id");
}
- @Override
- public long countDescendants(final Set<String> bases, final String
keyword) {
- Map<String, Object> parameters = new HashMap<>();
+ protected QueryInfo getQuery(final SearchCond cond, final Map<String,
Object> parameters) {
+ boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
+
+ TextStringBuilder query = new TextStringBuilder();
+ Set<String> involvedFields = new HashSet<>();
+ Set<PlainSchema> involvedPlainSchemas = new HashSet<>();
- StringBuilder queryString = buildDescendantsQuery(bases, keyword,
parameters).append(" RETURN COUNT(n)");
- return neo4jTemplate.count(queryString.toString(), parameters);
+ switch (cond.getType()) {
+ case LEAF, NOT_LEAF -> {
+ cond.asLeaf(AnyCond.class).ifPresentOrElse(
+ anyCond -> {
+ AnyCondQuery anyCondQuery = getQuery(anyCond, not,
parameters);
+ query.append(anyCondQuery.query());
+
Optional.ofNullable(anyCondQuery.field()).ifPresent(involvedFields::add);
+ },
+ () -> cond.asLeaf(AttrCond.class).ifPresent(leaf -> {
+ AttrCondQuery attrCondQuery = getQuery(leaf, not,
parameters);
+ query.append(attrCondQuery.query());
+ involvedPlainSchemas.add(attrCondQuery.schema());
+ }));
+
+ // allow for additional search conditions
+ getQueryForCustomConds(cond, parameters, not, query);
+ }
+ case AND -> {
+ QueryInfo leftAndInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftAndInfo.fields());
+ involvedPlainSchemas.addAll(leftAndInfo.plainSchemas());
+
+ QueryInfo rigthAndInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthAndInfo.fields());
+ involvedPlainSchemas.addAll(rigthAndInfo.plainSchemas());
+
+ queryOp(query, "AND", leftAndInfo, rigthAndInfo);
+ }
+
+ case OR -> {
+ QueryInfo leftOrInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftOrInfo.fields());
+ involvedPlainSchemas.addAll(leftOrInfo.plainSchemas());
+
+ QueryInfo rigthOrInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthOrInfo.fields());
+ involvedPlainSchemas.addAll(rigthOrInfo.plainSchemas());
+
+ queryOp(query, "OR", leftOrInfo, rigthOrInfo);
+ }
+
+ default -> {
+ }
+ }
+
+ return new QueryInfo(query, involvedFields, involvedPlainSchemas);
}
- @Override
- public List<Realm> findDescendants(final String base, final String
keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
+ protected void wrapQuery(
+ final Set<String> bases,
+ final QueryInfo queryInfo,
+ final Streamable<Order> orderBy,
+ final Map<String, Object> parameters) {
+
+ TextStringBuilder match = new TextStringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WITH n.id AS id");
+
+ // take fields into account
+ queryInfo.fields().remove("id");
+ Stream.concat(
+ queryInfo.fields().stream(),
+ orderBy.stream().filter(clause ->
!"id".equals(clause.getProperty())
+ &&
realmUtils.getField(clause.getProperty()).isPresent()).map(Order::getProperty)).
+ distinct().forEach(field -> match.append(",
n.").append(field).append(" AS ").append(field));
+
+ // take plain schemas into account
+ Stream.concat(
+ queryInfo.plainSchemas().stream(),
+ orderBy.stream().map(clause ->
plainSchemaDAO.findById(clause.getProperty())).
+ flatMap(Optional::stream)).distinct().forEach(schema
-> {
+
+ match.append(", apoc.convert.getJsonProperty(n,
'plainAttrs.").append(schema.getKey());
+ if (schema.isUniqueConstraint()) {
+ match.append("', '$.uniqueValue')");
+ } else {
+ match.append("', '$.values')");
+ }
+ match.append(" AS ").append(schema.getKey());
+ });
+
+ TextStringBuilder query = queryInfo.query();
+
+ // take bases into account
+ AtomicInteger index = new AtomicInteger(0);
+ String basesClause = bases.stream().map(base -> {
+ int idx = index.incrementAndGet();
+ parameters.put("base" + idx, base);
+ parameters.put("like" + idx,
SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base + "/.*");
+ return "n.fullPath = $base" + idx + " OR n.fullPath =~ $like" +
idx;
+ }).collect(Collectors.joining(" OR "));
+ if (query.startsWith("MATCH (n)")) {
+ query.replaceFirst("MATCH (n)", match + " WHERE (EXISTS { MATCH
(n)");
+ query.append("} ");
+ } else {
+ query.replaceFirst("WHERE EXISTS", "WHERE (EXISTS");
+ query.insert(0, match.append(' '));
+ }
+ query.append(") AND EXISTS { ").append("(n) WHERE
(").append(basesClause).append(")").append(" } ");
+ }
+
+ protected AttrCondQuery getQuery(
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ CheckResult<AttrCond> checked = check(cond);
+
+ TextStringBuilder query = new TextStringBuilder("MATCH (n) ");
+ switch (cond.getType()) {
+ case ISNOTNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NOT NULL");
+
+ case ISNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NULL");
+
+ default ->
+ fillAttrQuery(query, checked.value(), checked.schema(), cond,
not, parameters);
+ }
+
+ return new AttrCondQuery(query.toString(), checked.schema());
+ }
+
+ protected void getQueryForCustomConds(
+ final SearchCond cond,
+ final Map<String, Object> parameters,
+ final boolean not,
+ final TextStringBuilder query) {
+
+ // do nothing by default, leave it open for subclasses
+ }
+
+ protected void fillAttrQuery(
+ final TextStringBuilder query,
+ final PlainAttrValue attrValue,
+ final PlainSchema schema,
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ if (not && cond.getType() == AttrCond.Type.ISNULL) {
+ cond.setType(AttrCond.Type.ISNOTNULL);
+ fillAttrQuery(query, attrValue, schema, cond, true, parameters);
+ return;
+ }
+ if (not) {
+ if (schema.isUniqueConstraint()) {
+ fillAttrQuery(query, attrValue, schema, cond, false,
parameters);
+ query.replaceFirst("WHERE", "WHERE NOT(");
+ query.append(')');
+ } else {
+ fillAttrQuery(query, attrValue, schema, cond, false,
parameters);
+ query.replaceAll("any(", schema.getKey() + " IS NULL OR
none(");
+ }
+ return;
+ }
+
+ String value = Optional.ofNullable(attrValue.getDateValue()).
+ map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format).
+ orElseGet(cond::getExpression);
+
+ boolean isStr = true;
+ boolean lower = false;
+ if (schema.getType().isStringClass()) {
+ lower = (cond.getType() == AttrCond.Type.IEQ || cond.getType() ==
AttrCond.Type.ILIKE);
+ } else if (schema.getType() != AttrSchemaType.Date) {
+ lower = false;
+ try {
+ switch (schema.getType()) {
+ case Long ->
+ Long.valueOf(value);
+
+ case Double ->
+ Double.valueOf(value);
+
+ case Boolean -> {
+ if (!("true".equalsIgnoreCase(value) ||
"false".equalsIgnoreCase(value))) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ default -> {
+ }
+ }
+
+ isStr = false;
+ } catch (Exception nfe) {
+ // ignore
+ }
+ }
+
+ query.append("WHERE ");
+
+ switch (cond.getType()) {
+ case ISNULL -> {
+ }
+
+ case ISNOTNULL ->
+ query.append(schema.getKey()).append(" IS NOT NULL");
+
+ case ILIKE, LIKE -> {
+ if (schema.getType().isStringClass()) {
+ appendPlainAttrCond(
+ query,
+ schema,
+ " =~ \"" + (lower ? "(?i)" : "")
+ +
AnyRepoExt.escapeForLikeRegex(value).replace("%", ".*") + '"');
+ } else {
+ query.append(ALWAYS_FALSE_CLAUSE);
+ LOG.error("LIKE is only compatible with string or enum
schemas");
+ }
+ }
+
+ case IEQ, EQ -> {
+ if (StringUtils.containsAny(value, AnyRepoExt.REGEX_CHARS) ||
lower) {
+ appendPlainAttrCond(
+ query,
+ schema,
+ " =~ \"^" + (lower ? "(?i)" : "")
+ +
AnyRepoExt.escapeForLikeRegex(value).replace("%", ".*") + "$\"");
+ } else {
+ appendPlainAttrCond(
+ query,
+ schema,
+ " = " + escapeIfString(value, isStr));
+ }
+ }
+
+ case GE ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " >= " + escapeIfString(value, isStr));
+
+ case GT ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " > " + escapeIfString(value, isStr));
+
+ case LE ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " <= " + escapeIfString(value, isStr));
+
+ case LT ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " < " + escapeIfString(value, isStr));
+
+ default -> {
+ }
+ }
+ // shouldn't occour: processed before
+ }
+
+ protected void fillAttrQuery(
Review Comment:
## Confusing overloading of methods
Method Neo4jRealmSearchDAO.fillAttrQuery(..) could be confused with
overloaded method [fillAttrQuery](1), since dispatch depends on static types.
[Show more
details](https://github.com/apache/syncope/security/code-scanning/2582)
##########
core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java:
##########
@@ -89,68 +152,518 @@
return neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.fullPath =
$fullPath RETURN n.id").
bindAll(Map.of("fullPath", fullPath)).fetch().one().
- flatMap(toOptional("n.id", Neo4jRealm.class, cache));
+ flatMap(found ->
realmDAO.findById(found.get("n.id").toString()).map(n -> (Realm) n));
+ }
+
+ protected List<Realm> toList(
+ final Collection<Map<String, Object>> result,
+ final String property) {
+
+ return result.stream().
+ map(found ->
realmDAO.findById(found.get(property).toString())).
+ flatMap(Optional::stream).map(n -> (Realm) n).toList();
}
@Override
public List<Realm> findByName(final String name) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.name = $name RETURN
n.id").
- bindAll(Map.of("name", name)).fetch().all(), "n.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("name", name)).fetch().all(), "n.id");
}
@Override
public List<Realm> findChildren(final Realm realm) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + " {id: $id})<-[r:" +
Neo4jRealm.PARENT_REL + "]-(c) RETURN c.id").
- bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id");
}
@Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
+ public List<Realm> findDescendants(final String base, final String prefix)
{
+ Map<String, Object> parameters = new HashMap<>();
+
+ StringBuilder query = new StringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WHERE (").append("n.fullPath = $base OR n.fullPath =~
$like").append(')');
+ parameters.put("base", base);
+ parameters.put("like", SyncopeConstants.ROOT_REALM.equals(base) ?
"/.*" : base + "/.*");
+
+ if (prefix != null) {
+ query.append(" AND (n.fullPath = $prefix OR n.fullPath =~
$likePrefix)");
+ parameters.put("prefix", prefix);
+ parameters.put("likePrefix",
SyncopeConstants.ROOT_REALM.equals(prefix) ? "/.*" : prefix + "/.*");
+ }
+
+ query.append(" RETURN n.id ORDER BY n.fullPath");
+
+ return toList(neo4jClient.query(
+ query.toString()).bindAll(parameters).fetch().all(), "n.id");
}
- @Override
- public long countDescendants(final Set<String> bases, final String
keyword) {
- Map<String, Object> parameters = new HashMap<>();
+ protected QueryInfo getQuery(final SearchCond cond, final Map<String,
Object> parameters) {
+ boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
+
+ TextStringBuilder query = new TextStringBuilder();
+ Set<String> involvedFields = new HashSet<>();
+ Set<PlainSchema> involvedPlainSchemas = new HashSet<>();
- StringBuilder queryString = buildDescendantsQuery(bases, keyword,
parameters).append(" RETURN COUNT(n)");
- return neo4jTemplate.count(queryString.toString(), parameters);
+ switch (cond.getType()) {
+ case LEAF, NOT_LEAF -> {
+ cond.asLeaf(AnyCond.class).ifPresentOrElse(
+ anyCond -> {
+ AnyCondQuery anyCondQuery = getQuery(anyCond, not,
parameters);
+ query.append(anyCondQuery.query());
+
Optional.ofNullable(anyCondQuery.field()).ifPresent(involvedFields::add);
+ },
+ () -> cond.asLeaf(AttrCond.class).ifPresent(leaf -> {
+ AttrCondQuery attrCondQuery = getQuery(leaf, not,
parameters);
+ query.append(attrCondQuery.query());
+ involvedPlainSchemas.add(attrCondQuery.schema());
+ }));
+
+ // allow for additional search conditions
+ getQueryForCustomConds(cond, parameters, not, query);
+ }
+ case AND -> {
+ QueryInfo leftAndInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftAndInfo.fields());
+ involvedPlainSchemas.addAll(leftAndInfo.plainSchemas());
+
+ QueryInfo rigthAndInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthAndInfo.fields());
+ involvedPlainSchemas.addAll(rigthAndInfo.plainSchemas());
+
+ queryOp(query, "AND", leftAndInfo, rigthAndInfo);
+ }
+
+ case OR -> {
+ QueryInfo leftOrInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftOrInfo.fields());
+ involvedPlainSchemas.addAll(leftOrInfo.plainSchemas());
+
+ QueryInfo rigthOrInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthOrInfo.fields());
+ involvedPlainSchemas.addAll(rigthOrInfo.plainSchemas());
+
+ queryOp(query, "OR", leftOrInfo, rigthOrInfo);
+ }
+
+ default -> {
+ }
+ }
+
+ return new QueryInfo(query, involvedFields, involvedPlainSchemas);
}
- @Override
- public List<Realm> findDescendants(final String base, final String
keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
+ protected void wrapQuery(
+ final Set<String> bases,
+ final QueryInfo queryInfo,
+ final Streamable<Order> orderBy,
+ final Map<String, Object> parameters) {
+
+ TextStringBuilder match = new TextStringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WITH n.id AS id");
+
+ // take fields into account
+ queryInfo.fields().remove("id");
+ Stream.concat(
+ queryInfo.fields().stream(),
+ orderBy.stream().filter(clause ->
!"id".equals(clause.getProperty())
+ &&
realmUtils.getField(clause.getProperty()).isPresent()).map(Order::getProperty)).
+ distinct().forEach(field -> match.append(",
n.").append(field).append(" AS ").append(field));
+
+ // take plain schemas into account
+ Stream.concat(
+ queryInfo.plainSchemas().stream(),
+ orderBy.stream().map(clause ->
plainSchemaDAO.findById(clause.getProperty())).
+ flatMap(Optional::stream)).distinct().forEach(schema
-> {
+
+ match.append(", apoc.convert.getJsonProperty(n,
'plainAttrs.").append(schema.getKey());
+ if (schema.isUniqueConstraint()) {
+ match.append("', '$.uniqueValue')");
+ } else {
+ match.append("', '$.values')");
+ }
+ match.append(" AS ").append(schema.getKey());
+ });
+
+ TextStringBuilder query = queryInfo.query();
+
+ // take bases into account
+ AtomicInteger index = new AtomicInteger(0);
+ String basesClause = bases.stream().map(base -> {
+ int idx = index.incrementAndGet();
+ parameters.put("base" + idx, base);
+ parameters.put("like" + idx,
SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base + "/.*");
+ return "n.fullPath = $base" + idx + " OR n.fullPath =~ $like" +
idx;
+ }).collect(Collectors.joining(" OR "));
+ if (query.startsWith("MATCH (n)")) {
+ query.replaceFirst("MATCH (n)", match + " WHERE (EXISTS { MATCH
(n)");
+ query.append("} ");
+ } else {
+ query.replaceFirst("WHERE EXISTS", "WHERE (EXISTS");
+ query.insert(0, match.append(' '));
+ }
+ query.append(") AND EXISTS { ").append("(n) WHERE
(").append(basesClause).append(")").append(" } ");
+ }
+
+ protected AttrCondQuery getQuery(
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ CheckResult<AttrCond> checked = check(cond);
+
+ TextStringBuilder query = new TextStringBuilder("MATCH (n) ");
+ switch (cond.getType()) {
+ case ISNOTNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NOT NULL");
+
+ case ISNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NULL");
+
+ default ->
+ fillAttrQuery(query, checked.value(), checked.schema(), cond,
not, parameters);
+ }
+
+ return new AttrCondQuery(query.toString(), checked.schema());
+ }
+
+ protected void getQueryForCustomConds(
+ final SearchCond cond,
+ final Map<String, Object> parameters,
+ final boolean not,
Review Comment:
## Useless parameter
The parameter 'not' is never used.
[Show more
details](https://github.com/apache/syncope/security/code-scanning/2579)
##########
core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java:
##########
@@ -89,68 +152,518 @@
return neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.fullPath =
$fullPath RETURN n.id").
bindAll(Map.of("fullPath", fullPath)).fetch().one().
- flatMap(toOptional("n.id", Neo4jRealm.class, cache));
+ flatMap(found ->
realmDAO.findById(found.get("n.id").toString()).map(n -> (Realm) n));
+ }
+
+ protected List<Realm> toList(
+ final Collection<Map<String, Object>> result,
+ final String property) {
+
+ return result.stream().
+ map(found ->
realmDAO.findById(found.get(property).toString())).
+ flatMap(Optional::stream).map(n -> (Realm) n).toList();
}
@Override
public List<Realm> findByName(final String name) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.name = $name RETURN
n.id").
- bindAll(Map.of("name", name)).fetch().all(), "n.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("name", name)).fetch().all(), "n.id");
}
@Override
public List<Realm> findChildren(final Realm realm) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + " {id: $id})<-[r:" +
Neo4jRealm.PARENT_REL + "]-(c) RETURN c.id").
- bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id");
}
@Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
+ public List<Realm> findDescendants(final String base, final String prefix)
{
+ Map<String, Object> parameters = new HashMap<>();
+
+ StringBuilder query = new StringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WHERE (").append("n.fullPath = $base OR n.fullPath =~
$like").append(')');
+ parameters.put("base", base);
+ parameters.put("like", SyncopeConstants.ROOT_REALM.equals(base) ?
"/.*" : base + "/.*");
+
+ if (prefix != null) {
+ query.append(" AND (n.fullPath = $prefix OR n.fullPath =~
$likePrefix)");
+ parameters.put("prefix", prefix);
+ parameters.put("likePrefix",
SyncopeConstants.ROOT_REALM.equals(prefix) ? "/.*" : prefix + "/.*");
+ }
+
+ query.append(" RETURN n.id ORDER BY n.fullPath");
+
+ return toList(neo4jClient.query(
+ query.toString()).bindAll(parameters).fetch().all(), "n.id");
}
- @Override
- public long countDescendants(final Set<String> bases, final String
keyword) {
- Map<String, Object> parameters = new HashMap<>();
+ protected QueryInfo getQuery(final SearchCond cond, final Map<String,
Object> parameters) {
+ boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
+
+ TextStringBuilder query = new TextStringBuilder();
+ Set<String> involvedFields = new HashSet<>();
+ Set<PlainSchema> involvedPlainSchemas = new HashSet<>();
- StringBuilder queryString = buildDescendantsQuery(bases, keyword,
parameters).append(" RETURN COUNT(n)");
- return neo4jTemplate.count(queryString.toString(), parameters);
+ switch (cond.getType()) {
+ case LEAF, NOT_LEAF -> {
+ cond.asLeaf(AnyCond.class).ifPresentOrElse(
+ anyCond -> {
+ AnyCondQuery anyCondQuery = getQuery(anyCond, not,
parameters);
+ query.append(anyCondQuery.query());
+
Optional.ofNullable(anyCondQuery.field()).ifPresent(involvedFields::add);
+ },
+ () -> cond.asLeaf(AttrCond.class).ifPresent(leaf -> {
+ AttrCondQuery attrCondQuery = getQuery(leaf, not,
parameters);
+ query.append(attrCondQuery.query());
+ involvedPlainSchemas.add(attrCondQuery.schema());
+ }));
+
+ // allow for additional search conditions
+ getQueryForCustomConds(cond, parameters, not, query);
+ }
+ case AND -> {
+ QueryInfo leftAndInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftAndInfo.fields());
+ involvedPlainSchemas.addAll(leftAndInfo.plainSchemas());
+
+ QueryInfo rigthAndInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthAndInfo.fields());
+ involvedPlainSchemas.addAll(rigthAndInfo.plainSchemas());
+
+ queryOp(query, "AND", leftAndInfo, rigthAndInfo);
+ }
+
+ case OR -> {
+ QueryInfo leftOrInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftOrInfo.fields());
+ involvedPlainSchemas.addAll(leftOrInfo.plainSchemas());
+
+ QueryInfo rigthOrInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthOrInfo.fields());
+ involvedPlainSchemas.addAll(rigthOrInfo.plainSchemas());
+
+ queryOp(query, "OR", leftOrInfo, rigthOrInfo);
+ }
+
+ default -> {
+ }
+ }
+
+ return new QueryInfo(query, involvedFields, involvedPlainSchemas);
}
- @Override
- public List<Realm> findDescendants(final String base, final String
keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
+ protected void wrapQuery(
+ final Set<String> bases,
+ final QueryInfo queryInfo,
+ final Streamable<Order> orderBy,
+ final Map<String, Object> parameters) {
+
+ TextStringBuilder match = new TextStringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WITH n.id AS id");
+
+ // take fields into account
+ queryInfo.fields().remove("id");
+ Stream.concat(
+ queryInfo.fields().stream(),
+ orderBy.stream().filter(clause ->
!"id".equals(clause.getProperty())
+ &&
realmUtils.getField(clause.getProperty()).isPresent()).map(Order::getProperty)).
+ distinct().forEach(field -> match.append(",
n.").append(field).append(" AS ").append(field));
+
+ // take plain schemas into account
+ Stream.concat(
+ queryInfo.plainSchemas().stream(),
+ orderBy.stream().map(clause ->
plainSchemaDAO.findById(clause.getProperty())).
+ flatMap(Optional::stream)).distinct().forEach(schema
-> {
+
+ match.append(", apoc.convert.getJsonProperty(n,
'plainAttrs.").append(schema.getKey());
+ if (schema.isUniqueConstraint()) {
+ match.append("', '$.uniqueValue')");
+ } else {
+ match.append("', '$.values')");
+ }
+ match.append(" AS ").append(schema.getKey());
+ });
+
+ TextStringBuilder query = queryInfo.query();
+
+ // take bases into account
+ AtomicInteger index = new AtomicInteger(0);
+ String basesClause = bases.stream().map(base -> {
+ int idx = index.incrementAndGet();
+ parameters.put("base" + idx, base);
+ parameters.put("like" + idx,
SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base + "/.*");
+ return "n.fullPath = $base" + idx + " OR n.fullPath =~ $like" +
idx;
+ }).collect(Collectors.joining(" OR "));
+ if (query.startsWith("MATCH (n)")) {
+ query.replaceFirst("MATCH (n)", match + " WHERE (EXISTS { MATCH
(n)");
+ query.append("} ");
+ } else {
+ query.replaceFirst("WHERE EXISTS", "WHERE (EXISTS");
+ query.insert(0, match.append(' '));
+ }
+ query.append(") AND EXISTS { ").append("(n) WHERE
(").append(basesClause).append(")").append(" } ");
+ }
+
+ protected AttrCondQuery getQuery(
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ CheckResult<AttrCond> checked = check(cond);
+
+ TextStringBuilder query = new TextStringBuilder("MATCH (n) ");
+ switch (cond.getType()) {
+ case ISNOTNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NOT NULL");
+
+ case ISNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NULL");
+
+ default ->
+ fillAttrQuery(query, checked.value(), checked.schema(), cond,
not, parameters);
+ }
+
+ return new AttrCondQuery(query.toString(), checked.schema());
+ }
+
+ protected void getQueryForCustomConds(
+ final SearchCond cond,
+ final Map<String, Object> parameters,
+ final boolean not,
+ final TextStringBuilder query) {
+
+ // do nothing by default, leave it open for subclasses
+ }
+
+ protected void fillAttrQuery(
+ final TextStringBuilder query,
+ final PlainAttrValue attrValue,
+ final PlainSchema schema,
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ if (not && cond.getType() == AttrCond.Type.ISNULL) {
+ cond.setType(AttrCond.Type.ISNOTNULL);
+ fillAttrQuery(query, attrValue, schema, cond, true, parameters);
+ return;
+ }
+ if (not) {
+ if (schema.isUniqueConstraint()) {
+ fillAttrQuery(query, attrValue, schema, cond, false,
parameters);
+ query.replaceFirst("WHERE", "WHERE NOT(");
+ query.append(')');
+ } else {
+ fillAttrQuery(query, attrValue, schema, cond, false,
parameters);
+ query.replaceAll("any(", schema.getKey() + " IS NULL OR
none(");
+ }
+ return;
+ }
+
+ String value = Optional.ofNullable(attrValue.getDateValue()).
+ map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format).
+ orElseGet(cond::getExpression);
+
+ boolean isStr = true;
+ boolean lower = false;
+ if (schema.getType().isStringClass()) {
+ lower = (cond.getType() == AttrCond.Type.IEQ || cond.getType() ==
AttrCond.Type.ILIKE);
+ } else if (schema.getType() != AttrSchemaType.Date) {
+ lower = false;
+ try {
+ switch (schema.getType()) {
+ case Long ->
+ Long.valueOf(value);
+
+ case Double ->
+ Double.valueOf(value);
+
+ case Boolean -> {
+ if (!("true".equalsIgnoreCase(value) ||
"false".equalsIgnoreCase(value))) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ default -> {
+ }
+ }
+
+ isStr = false;
+ } catch (Exception nfe) {
+ // ignore
+ }
+ }
+
+ query.append("WHERE ");
+
+ switch (cond.getType()) {
+ case ISNULL -> {
+ }
+
+ case ISNOTNULL ->
+ query.append(schema.getKey()).append(" IS NOT NULL");
+
+ case ILIKE, LIKE -> {
+ if (schema.getType().isStringClass()) {
+ appendPlainAttrCond(
+ query,
+ schema,
+ " =~ \"" + (lower ? "(?i)" : "")
+ +
AnyRepoExt.escapeForLikeRegex(value).replace("%", ".*") + '"');
+ } else {
+ query.append(ALWAYS_FALSE_CLAUSE);
+ LOG.error("LIKE is only compatible with string or enum
schemas");
+ }
+ }
+
+ case IEQ, EQ -> {
+ if (StringUtils.containsAny(value, AnyRepoExt.REGEX_CHARS) ||
lower) {
+ appendPlainAttrCond(
+ query,
+ schema,
+ " =~ \"^" + (lower ? "(?i)" : "")
+ +
AnyRepoExt.escapeForLikeRegex(value).replace("%", ".*") + "$\"");
+ } else {
+ appendPlainAttrCond(
+ query,
+ schema,
+ " = " + escapeIfString(value, isStr));
+ }
+ }
+
+ case GE ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " >= " + escapeIfString(value, isStr));
+
+ case GT ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " > " + escapeIfString(value, isStr));
+
+ case LE ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " <= " + escapeIfString(value, isStr));
+
+ case LT ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " < " + escapeIfString(value, isStr));
+
+ default -> {
+ }
+ }
+ // shouldn't occour: processed before
+ }
+
+ protected void fillAttrQuery(
+ final TextStringBuilder query,
+ final PlainAttrValue attrValue,
+ final PlainSchema schema,
+ final AnyCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ if (not && cond.getType() == AttrCond.Type.ISNULL) {
+ cond.setType(AttrCond.Type.ISNOTNULL);
+ fillAttrQuery(query, attrValue, schema, cond, true, parameters);
+ return;
+ }
+ if (not) {
+ query.append("NOT (");
+ fillAttrQuery(query, attrValue, schema, cond, false, parameters);
+ query.append(')');
+ return;
+ }
+ if (not && cond.getType() == AttrCond.Type.ISNULL) {
+ cond.setType(AttrCond.Type.ISNOTNULL);
+ fillAttrQuery(query, attrValue, schema, cond, true, parameters);
+ return;
+ }
+
+ boolean lower = schema.getType().isStringClass()
+ && (cond.getType() == AttrCond.Type.IEQ || cond.getType() ==
AttrCond.Type.ILIKE);
+
+ String property = "n." + cond.getSchema();
+ if (lower) {
+ property = "toLower (" + property + ')';
+ }
+
+ switch (cond.getType()) {
+
+ case ISNULL ->
+ query.append(property).append(" IS NULL");
+
+ case ISNOTNULL ->
+ query.append(property).append(" IS NOT NULL");
+
+ case ILIKE, LIKE -> {
+ if (schema.getType().isStringClass()) {
+ query.append(property).append(" =~ ");
+ if (lower) {
+ query.append("toLower($").
+ append(setParameter(parameters,
cond.getExpression().replace("%", ".*"))).
+ append(')');
+ } else {
+ query.append('$').append(setParameter(parameters,
cond.getExpression().replace("%", ".*")));
+ }
+ } else {
+ query.append(' ').append(ALWAYS_FALSE_CLAUSE);
+ LOG.error("LIKE is only compatible with string or enum
schemas");
+ }
+ }
+
+ case IEQ, EQ -> {
+ query.append(property).append('=');
+
+ if (lower) {
+ query.append("toLower($").append(setParameter(parameters,
attrValue.getValue())).append(')');
+ } else {
+ query.append('$').append(setParameter(parameters,
attrValue.getValue()));
+ }
+ }
+
+ case GE -> {
+ query.append(property);
+ if (not) {
+ query.append('<');
+ } else {
+ query.append(">=");
+ }
+ query.append('$').append(setParameter(parameters,
attrValue.getValue()));
+ }
+
+ case GT -> {
+ query.append(property);
+ if (not) {
+ query.append("<=");
+ } else {
+ query.append('>');
+ }
+ query.append('$').append(setParameter(parameters,
attrValue.getValue()));
+ }
+
+ case LE -> {
+ query.append(property);
+ if (not) {
+ query.append('>');
+ } else {
+ query.append("<=");
+ }
+ query.append('$').append(setParameter(parameters,
attrValue.getValue()));
+ }
+
+ case LT -> {
+ query.append(property);
+ if (not) {
+ query.append(">=");
+ } else {
+ query.append('<');
+ }
+ query.append('$').append(setParameter(parameters,
attrValue.getValue()));
+ }
+
+ default -> {
+ }
+ }
+ }
+
+ protected AnyCondQuery getQuery(
Review Comment:
## Confusing overloading of methods
Method Neo4jRealmSearchDAO.getQuery(..) could be confused with overloaded
method [getQuery](1), since dispatch depends on static types.
[Show more
details](https://github.com/apache/syncope/security/code-scanning/2581)
##########
core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java:
##########
@@ -89,68 +152,518 @@
return neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.fullPath =
$fullPath RETURN n.id").
bindAll(Map.of("fullPath", fullPath)).fetch().one().
- flatMap(toOptional("n.id", Neo4jRealm.class, cache));
+ flatMap(found ->
realmDAO.findById(found.get("n.id").toString()).map(n -> (Realm) n));
+ }
+
+ protected List<Realm> toList(
+ final Collection<Map<String, Object>> result,
+ final String property) {
+
+ return result.stream().
+ map(found ->
realmDAO.findById(found.get(property).toString())).
+ flatMap(Optional::stream).map(n -> (Realm) n).toList();
}
@Override
public List<Realm> findByName(final String name) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.name = $name RETURN
n.id").
- bindAll(Map.of("name", name)).fetch().all(), "n.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("name", name)).fetch().all(), "n.id");
}
@Override
public List<Realm> findChildren(final Realm realm) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + " {id: $id})<-[r:" +
Neo4jRealm.PARENT_REL + "]-(c) RETURN c.id").
- bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id",
Neo4jRealm.class, cache);
+ bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id");
}
@Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
+ public List<Realm> findDescendants(final String base, final String prefix)
{
+ Map<String, Object> parameters = new HashMap<>();
+
+ StringBuilder query = new StringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WHERE (").append("n.fullPath = $base OR n.fullPath =~
$like").append(')');
+ parameters.put("base", base);
+ parameters.put("like", SyncopeConstants.ROOT_REALM.equals(base) ?
"/.*" : base + "/.*");
+
+ if (prefix != null) {
+ query.append(" AND (n.fullPath = $prefix OR n.fullPath =~
$likePrefix)");
+ parameters.put("prefix", prefix);
+ parameters.put("likePrefix",
SyncopeConstants.ROOT_REALM.equals(prefix) ? "/.*" : prefix + "/.*");
+ }
+
+ query.append(" RETURN n.id ORDER BY n.fullPath");
+
+ return toList(neo4jClient.query(
+ query.toString()).bindAll(parameters).fetch().all(), "n.id");
}
- @Override
- public long countDescendants(final Set<String> bases, final String
keyword) {
- Map<String, Object> parameters = new HashMap<>();
+ protected QueryInfo getQuery(final SearchCond cond, final Map<String,
Object> parameters) {
+ boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
+
+ TextStringBuilder query = new TextStringBuilder();
+ Set<String> involvedFields = new HashSet<>();
+ Set<PlainSchema> involvedPlainSchemas = new HashSet<>();
- StringBuilder queryString = buildDescendantsQuery(bases, keyword,
parameters).append(" RETURN COUNT(n)");
- return neo4jTemplate.count(queryString.toString(), parameters);
+ switch (cond.getType()) {
+ case LEAF, NOT_LEAF -> {
+ cond.asLeaf(AnyCond.class).ifPresentOrElse(
+ anyCond -> {
+ AnyCondQuery anyCondQuery = getQuery(anyCond, not,
parameters);
+ query.append(anyCondQuery.query());
+
Optional.ofNullable(anyCondQuery.field()).ifPresent(involvedFields::add);
+ },
+ () -> cond.asLeaf(AttrCond.class).ifPresent(leaf -> {
+ AttrCondQuery attrCondQuery = getQuery(leaf, not,
parameters);
+ query.append(attrCondQuery.query());
+ involvedPlainSchemas.add(attrCondQuery.schema());
+ }));
+
+ // allow for additional search conditions
+ getQueryForCustomConds(cond, parameters, not, query);
+ }
+ case AND -> {
+ QueryInfo leftAndInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftAndInfo.fields());
+ involvedPlainSchemas.addAll(leftAndInfo.plainSchemas());
+
+ QueryInfo rigthAndInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthAndInfo.fields());
+ involvedPlainSchemas.addAll(rigthAndInfo.plainSchemas());
+
+ queryOp(query, "AND", leftAndInfo, rigthAndInfo);
+ }
+
+ case OR -> {
+ QueryInfo leftOrInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftOrInfo.fields());
+ involvedPlainSchemas.addAll(leftOrInfo.plainSchemas());
+
+ QueryInfo rigthOrInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthOrInfo.fields());
+ involvedPlainSchemas.addAll(rigthOrInfo.plainSchemas());
+
+ queryOp(query, "OR", leftOrInfo, rigthOrInfo);
+ }
+
+ default -> {
+ }
+ }
+
+ return new QueryInfo(query, involvedFields, involvedPlainSchemas);
}
- @Override
- public List<Realm> findDescendants(final String base, final String
keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
+ protected void wrapQuery(
+ final Set<String> bases,
+ final QueryInfo queryInfo,
+ final Streamable<Order> orderBy,
+ final Map<String, Object> parameters) {
+
+ TextStringBuilder match = new TextStringBuilder("MATCH
(n:").append(Neo4jRealm.NODE).append(") ").
+ append("WITH n.id AS id");
+
+ // take fields into account
+ queryInfo.fields().remove("id");
+ Stream.concat(
+ queryInfo.fields().stream(),
+ orderBy.stream().filter(clause ->
!"id".equals(clause.getProperty())
+ &&
realmUtils.getField(clause.getProperty()).isPresent()).map(Order::getProperty)).
+ distinct().forEach(field -> match.append(",
n.").append(field).append(" AS ").append(field));
+
+ // take plain schemas into account
+ Stream.concat(
+ queryInfo.plainSchemas().stream(),
+ orderBy.stream().map(clause ->
plainSchemaDAO.findById(clause.getProperty())).
+ flatMap(Optional::stream)).distinct().forEach(schema
-> {
+
+ match.append(", apoc.convert.getJsonProperty(n,
'plainAttrs.").append(schema.getKey());
+ if (schema.isUniqueConstraint()) {
+ match.append("', '$.uniqueValue')");
+ } else {
+ match.append("', '$.values')");
+ }
+ match.append(" AS ").append(schema.getKey());
+ });
+
+ TextStringBuilder query = queryInfo.query();
+
+ // take bases into account
+ AtomicInteger index = new AtomicInteger(0);
+ String basesClause = bases.stream().map(base -> {
+ int idx = index.incrementAndGet();
+ parameters.put("base" + idx, base);
+ parameters.put("like" + idx,
SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base + "/.*");
+ return "n.fullPath = $base" + idx + " OR n.fullPath =~ $like" +
idx;
+ }).collect(Collectors.joining(" OR "));
+ if (query.startsWith("MATCH (n)")) {
+ query.replaceFirst("MATCH (n)", match + " WHERE (EXISTS { MATCH
(n)");
+ query.append("} ");
+ } else {
+ query.replaceFirst("WHERE EXISTS", "WHERE (EXISTS");
+ query.insert(0, match.append(' '));
+ }
+ query.append(") AND EXISTS { ").append("(n) WHERE
(").append(basesClause).append(")").append(" } ");
+ }
+
+ protected AttrCondQuery getQuery(
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ CheckResult<AttrCond> checked = check(cond);
+
+ TextStringBuilder query = new TextStringBuilder("MATCH (n) ");
+ switch (cond.getType()) {
+ case ISNOTNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NOT NULL");
+
+ case ISNULL ->
+ query.append("WHERE
n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NULL");
+
+ default ->
+ fillAttrQuery(query, checked.value(), checked.schema(), cond,
not, parameters);
+ }
+
+ return new AttrCondQuery(query.toString(), checked.schema());
+ }
+
+ protected void getQueryForCustomConds(
+ final SearchCond cond,
+ final Map<String, Object> parameters,
+ final boolean not,
+ final TextStringBuilder query) {
Review Comment:
## Useless parameter
The parameter 'query' is never used.
[Show more
details](https://github.com/apache/syncope/security/code-scanning/2580)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]