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 (#863)
  • Loading branch information
andrea-patricelli authored Oct 15, 2024
1 parent d5e3caa commit a958faa
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,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 @@ -384,6 +384,9 @@ under the License.
creator="admin" lastModifier="admin"
creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/>
<SyncopeGroup_Realm left="f779c0d4-633b-4be5-8f57-32eb478a3ca5" right="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"/>
<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"
creator="admin" lastModifier="admin"
creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 11:00:00"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -35,10 +36,14 @@
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.AuditEvent;
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 @@ -98,6 +103,7 @@ public ElasticsearchUtils(
* @param any user, group or any object to index
* @return document specialized with content from the provided any
*/
@SuppressWarnings("unchecked")
@Transactional
public Map<String, Object> document(final Any<?> any) {
Map<String, Object> builder = new HashMap<>();
Expand Down Expand Up @@ -200,6 +206,27 @@ 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()));

Object attr = builder.computeIfAbsent(mAttr.getSchema().getKey(), k -> new HashSet<>());
// also support case in which there is also an existing attribute set previously
if (attr instanceof Collection) {
((Collection<Object>) attr).addAll(values);
} else {
values.add(attr);
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 @@ -20,6 +20,7 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand All @@ -35,10 +36,14 @@
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.AuditEvent;
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 @@ -98,6 +103,7 @@ public OpenSearchUtils(
* @param any user, group or any object to index
* @return document specialized with content from the provided any
*/
@SuppressWarnings("unchecked")
@Transactional
public Map<String, Object> document(final Any<?> any) {
Map<String, Object> builder = new HashMap<>();
Expand Down Expand Up @@ -200,6 +206,27 @@ 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()));

Object attr = builder.computeIfAbsent(mAttr.getSchema().getKey(), k -> new HashSet<>());
// also support case in which there is also an existing attribute set previously
if (attr instanceof Collection) {
((Collection<Object>) attr).addAll(values);
} else {
values.add(attr);
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 @@ -377,10 +378,9 @@ public void push() {
assertEquals(1, task.getExecutions().size());
assertEquals(ExecStatus.SUCCESS.name(), task.getExecutions().get(0).getStatus());

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").orElseThrow().getValues().size());
assertEquals("1977", membership.getPlainAttr("aLong").orElseThrow().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").orElseThrow().getValues().isEmpty());
assertFalse(membership.getDerAttr("noschema").orElseThrow().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 All @@ -42,6 +43,7 @@
import org.apache.syncope.common.lib.request.AnyObjectUR;
import org.apache.syncope.common.lib.request.AttrPatch;
import org.apache.syncope.common.lib.request.GroupCR;
import org.apache.syncope.common.lib.request.GroupUR;
import org.apache.syncope.common.lib.request.MembershipUR;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
Expand All @@ -53,6 +55,7 @@
import org.apache.syncope.common.lib.to.PagedResult;
import org.apache.syncope.common.lib.to.RealmTO;
import org.apache.syncope.common.lib.to.RoleTO;
import org.apache.syncope.common.lib.to.TypeExtensionTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.ClientExceptionType;
Expand Down Expand Up @@ -1058,4 +1061,56 @@ void issueSYNCOPE1826() {
}
}

@Test
void userByMembershipAttribute() {
// search user by membership attribute
UserTO puccini = USER_SERVICE.read("puccini");
GroupTO additional = GROUP_SERVICE.read("additional");
GroupTO employee = GROUP_SERVICE.read("employee");
TypeExtensionTO typeExtensionTO = new TypeExtensionTO();
typeExtensionTO.setAnyType(AnyTypeKind.USER.name());
typeExtensionTO.getAuxClasses().add("other");
updateGroup(new GroupUR.Builder(employee.getKey()).typeExtension(typeExtensionTO).build());
// add a membership and its plain attribute
updateUser(new UserUR.Builder(puccini.getKey())
.plainAttr(attrAddReplacePatch("ctype", "myownctype"))
.memberships(
new MembershipUR.Builder(additional.getKey()).plainAttrs(attr("ctype", "additionalctype"))
.build(), new MembershipUR.Builder(employee.getKey())
.plainAttrs(attr("ctype", "additionalemployeectype"))
.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())));
assertTrue(USER_SERVICE.search(new AnyQuery.Builder().page(1).size(10)
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("ctype").equalTo("additionalemployeectype")
.query()).build()).getResult().stream().anyMatch(u -> "puccini".equals(u.getUsername())));
// check also that search on user plain attribute (not in membership) works
assertTrue(USER_SERVICE.search(new AnyQuery.Builder().page(1).size(10)
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("ctype").equalTo("myownctype").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 a958faa

Please sign in to comment.