This is an automated email from the ASF dual-hosted git repository.
andreapatricelli pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/3_0_X by this push:
new fad9a27b68 [SYNCOPE-1830] added support for search on membership
attributes on Elasticsearch and OpenSearch (#859)
fad9a27b68 is described below
commit fad9a27b68763f9dac8b587f5bf84f5eb463eff3
Author: Andrea Patricelli <[email protected]>
AuthorDate: Tue Oct 15 10:04:28 2024 +0200
[SYNCOPE-1830] added support for search on membership attributes on
Elasticsearch and OpenSearch (#859)
* [SYNCOPE-1830] added support for search on membership attributes on
Elasticsearch and Opensearch
---
.../src/test/resources/domains/MasterContent.xml | 3 ++
.../src/test/resources/domains/MasterContent.xml | 3 ++
.../elasticsearch/client/ElasticsearchUtils.java | 21 ++++++++++++
.../ext/opensearch/client/OpenSearchUtils.java | 21 ++++++++++++
.../syncope/fit/core/LinkedAccountITCase.java | 9 +++---
.../apache/syncope/fit/core/MembershipITCase.java | 2 +-
.../org/apache/syncope/fit/core/SearchITCase.java | 37 ++++++++++++++++++++++
7 files changed, 91 insertions(+), 5 deletions(-)
diff --git
a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index 467f903395..3792cf6c82 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -381,6 +381,9 @@ under the License.
realm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
creator="admin" lastModifier="admin"
creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20
11:00:00"/>
+ <TypeExtension id="88a71478-30aa-4ee0-8b2b-6cb32e7ba264"
+ group_id="f779c0d4-633b-4be5-8f57-32eb478a3ca5"
anyType_id="PRINTER"/>
+ <TypeExtension_AnyTypeClass
typeExtension_id="88a71478-30aa-4ee0-8b2b-6cb32e7ba264"
anyTypeClass_id="other"/>
<SyncopeGroup id="0cbcabd2-4410-4b6b-8f05-a052b451d18f"
name="groupForWorkflowApproval"
realm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
creator="admin" lastModifier="admin"
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index e02c31bf83..a36dae3b1d 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -241,6 +241,9 @@ under the License.
realm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
creator="admin" lastModifier="admin"
creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20
11:00:00"/>
+ <TypeExtension id="88a71478-30aa-4ee0-8b2b-6cb32e7ba264"
+ group_id="f779c0d4-633b-4be5-8f57-32eb478a3ca5"
anyType_id="PRINTER"/>
+ <TypeExtension_AnyTypeClass
typeExtension_id="88a71478-30aa-4ee0-8b2b-6cb32e7ba264"
anyTypeClass_id="other"/>
<SyncopeGroup id="0cbcabd2-4410-4b6b-8f05-a052b451d18f"
name="groupForWorkflowApproval"
realm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
creator="admin" lastModifier="admin"
diff --git
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
index eb7fab9fef..5d607b8e14 100644
---
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
+++
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
@@ -36,10 +36,14 @@ import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
+import org.apache.syncope.core.persistence.api.entity.GroupablePlainAttr;
+import org.apache.syncope.core.persistence.api.entity.GroupableRelatable;
+import org.apache.syncope.core.persistence.api.entity.Membership;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
import org.apache.syncope.core.persistence.api.entity.Privilege;
import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.Relationship;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -202,6 +206,23 @@ public class ElasticsearchUtils {
builder.put(plainAttr.getSchema().getKey(), values.size() == 1 ?
values.get(0) : values);
}
+ // add also flattened membership attributes
+ if (any instanceof GroupableRelatable) {
+ GroupableRelatable<? extends Any, ? extends Membership, ? extends
GroupablePlainAttr, ? extends Any, ?
+ extends Relationship> entity =
GroupableRelatable.class.cast(any);
+ entity.getMemberships().forEach(m ->
entity.getPlainAttrs(m).forEach(mAttr -> {
+ List<Object> values =
mAttr.getValues().stream().map(PlainAttrValue::getValue)
+ .collect(Collectors.toList());
+
+ Optional.ofNullable(mAttr.getUniqueValue()).ifPresent(v ->
values.add(v.getValue()));
+
+ if (!builder.containsKey(mAttr.getSchema().getKey())) {
+ builder.put(mAttr.getSchema().getKey(), new HashSet<>());
+ }
+ builder.put(mAttr.getSchema().getKey(), values.size() == 1 ?
values.get(0) : values);
+ }));
+ }
+
return builder;
}
diff --git
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
index 19816df030..ded9a814b7 100644
---
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
+++
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
@@ -36,10 +36,14 @@ import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
+import org.apache.syncope.core.persistence.api.entity.GroupablePlainAttr;
+import org.apache.syncope.core.persistence.api.entity.GroupableRelatable;
+import org.apache.syncope.core.persistence.api.entity.Membership;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
import org.apache.syncope.core.persistence.api.entity.Privilege;
import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.Relationship;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -202,6 +206,23 @@ public class OpenSearchUtils {
builder.put(plainAttr.getSchema().getKey(), values.size() == 1 ?
values.get(0) : values);
}
+ // add also flattened membership attributes
+ if (any instanceof GroupableRelatable) {
+ GroupableRelatable<? extends Any, ? extends Membership, ? extends
GroupablePlainAttr, ? extends Any, ?
+ extends Relationship> entity =
GroupableRelatable.class.cast(any);
+ entity.getMemberships().forEach(m ->
entity.getPlainAttrs(m).forEach(mAttr -> {
+ List<Object> values =
mAttr.getValues().stream().map(PlainAttrValue::getValue)
+ .collect(Collectors.toList());
+
+ Optional.ofNullable(mAttr.getUniqueValue()).ifPresent(v ->
values.add(v.getValue()));
+
+ if (!builder.containsKey(mAttr.getSchema().getKey())) {
+ builder.put(mAttr.getSchema().getKey(), new HashSet<>());
+ }
+ builder.put(mAttr.getSchema().getKey(), values.size() == 1 ?
values.get(0) : values);
+ }));
+ }
+
return builder;
}
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LinkedAccountITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LinkedAccountITCase.java
index e6d17d6960..c9214e526c 100644
---
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LinkedAccountITCase.java
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/LinkedAccountITCase.java
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.fit.core;
+import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -384,10 +385,10 @@ public class LinkedAccountITCase extends AbstractITCase {
// ignore
}
}
- tasks = TASK_SERVICE.search(
- new
TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_REST).
-
anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build());
- assertEquals(3, tasks.getTotalCount());
+
+ await().until(() -> TASK_SERVICE.search(
+ new
TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_REST)
+
.anyTypeKind(AnyTypeKind.USER).entityKey(user.getKey()).build()).getTotalCount()
== 3);
// 6. verify that both user and account are now found on resource
response = webClient.get();
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
index 9ceb511a91..7ed665923b 100644
---
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
@@ -105,7 +105,7 @@ public class MembershipITCase extends AbstractITCase {
assertEquals(1,
membership.getPlainAttr("aLong").get().getValues().size());
assertEquals("1977",
membership.getPlainAttr("aLong").get().getValues().get(0));
- // 3. verify that derived attrbutes from 'csv' and 'other' are
also populated for user's membership
+ // 3. verify that derived attributes from 'csv' and 'other' are
also populated for user's membership
assertFalse(membership.getDerAttr("csvuserid").get().getValues().isEmpty());
assertFalse(membership.getDerAttr("noschema").get().getValues().isEmpty());
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 f7266d49ce..cfe7e8daa6 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
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.fit.core;
+import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -1052,4 +1053,40 @@ public class SearchITCase extends AbstractITCase {
}
}
+ @Test
+ void userByMembershipAttribute() {
+ // search user by membership attribute
+ UserTO puccini = USER_SERVICE.read("puccini");
+ GroupTO additional = GROUP_SERVICE.read("additional");
+ // add a membership and its plain attribute
+ updateUser(new UserUR.Builder(puccini.getKey()).memberships(
+ new
MembershipUR.Builder(additional.getKey()).plainAttrs(attr("ctype",
"additionalctype"))
+ .build()).build());
+ await().until(() -> USER_SERVICE.search(new
AnyQuery.Builder().page(1).size(10)
+
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("ctype").equalTo("additionalctype").query())
+ .build()).getTotalCount() == 1);
+ assertTrue(USER_SERVICE.search(new AnyQuery.Builder().page(1).size(10)
+
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("ctype").equalTo("additionalctype").query())
+ .build()).getResult().stream().anyMatch(u ->
"puccini".equals(u.getUsername())));
+ }
+
+ @Test
+ void anyObjectByMembershipAttribute() {
+ // search user by membership attribute
+ AnyObjectTO canonMf =
ANY_OBJECT_SERVICE.read("8559d14d-58c2-46eb-a2d4-a7d35161e8f8");
+ GroupTO otherchild = GROUP_SERVICE.read("otherchild");
+ // add a membership and its plain attribute
+ updateAnyObject(new AnyObjectUR.Builder(canonMf.getKey()).memberships(
+ new
MembershipUR.Builder(otherchild.getKey()).plainAttrs(attr("ctype",
"otherchildctype"))
+ .build()).build());
+ await().until(() -> ANY_OBJECT_SERVICE.search(new
AnyQuery.Builder().page(1).size(10)
+
.fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("ctype").equalTo("otherchildctype")
+ .query()).build()).getTotalCount() == 1);
+ assertTrue(ANY_OBJECT_SERVICE.search(new
AnyQuery.Builder().page(1).size(10)
+
.fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("ctype").equalTo(
+ "otherchildctype")
+ .query()).build()).getResult().stream()
+ .anyMatch(u ->
"8559d14d-58c2-46eb-a2d4-a7d35161e8f8".equals(u.getKey())));
+ }
+
}