Skip to content

Commit

Permalink
[SYNCOPE-1830] added support for search on membership attributes on E…
Browse files Browse the repository at this point in the history
…lasticsearch and OpenSearch (#859)

* [SYNCOPE-1830] added support for search on membership attributes on Elasticsearch and Opensearch
  • Loading branch information
andrea-patricelli authored Oct 15, 2024
1 parent ee75d74 commit fad9a27
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@
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;
Expand Down Expand Up @@ -202,6 +206,23 @@ public Map<String, Object> document(final Any<?> any) {
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@
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;
Expand Down Expand Up @@ -202,6 +206,23 @@ public Map<String, Object> document(final Any<?> any) {
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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -384,10 +385,10 @@ public void push() {
// 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public void misc() throws JsonProcessingException {
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());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1052,4 +1053,40 @@ void issueSYNCOPE1826() {
}
}

@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())));
}

}

0 comments on commit fad9a27

Please sign in to comment.