You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2019/10/09 12:34:35 UTC
[syncope] branch 2_1_X updated: [SYNCOPE-1501] Connector Objects
can now be filtered via FIQL
This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/2_1_X by this push:
new d5513ae [SYNCOPE-1501] Connector Objects can now be filtered via FIQL
d5513ae is described below
commit d5513ae2d128653a9160ae35aa4ba596cbeba7d1
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Wed Oct 9 14:33:54 2019 +0200
[SYNCOPE-1501] Connector Objects can now be filtered via FIQL
---
.../client/console/rest/ResourceRestClient.java | 6 +-
.../enduser/resources/DynamicTemplateResource.java | 2 -
.../search/AbstractFiqlSearchConditionBuilder.java | 1 -
.../AnyObjectFiqlSearchConditionBuilder.java | 1 -
...=> ConnObjectTOFiqlSearchConditionBuilder.java} | 50 ++--
.../search/GroupFiqlSearchConditionBuilder.java | 1 -
...jectTOListQuery.java => ConnObjectTOQuery.java} | 23 +-
.../common/rest/api/service/ResourceService.java | 15 +-
.../apache/syncope/core/logic/ResourceLogic.java | 129 +++++----
.../persistence/api/search/FilterConverter.java | 64 +++++
.../core/persistence/api/search/FilterVisitor.java | 172 ++++++++++++
.../persistence/api/search/SearchCondVisitor.java | 27 +-
.../api/search/FilterConverterTest.java | 289 +++++++++++++++++++++
.../core/rest/cxf/service/ResourceServiceImpl.java | 49 +++-
.../enduser/resources/UserRequestsResource.java | 5 +-
.../resources/UserRequestsStartResource.java | 2 -
.../syncope/fit/core/LinkedAccountITCase.java | 34 +--
.../apache/syncope/fit/core/ResourceITCase.java | 99 -------
.../org/apache/syncope/fit/core/SearchITCase.java | 112 ++++++++
19 files changed, 826 insertions(+), 255 deletions(-)
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
index c8e135b..93d0166 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
@@ -27,7 +27,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.PagedConnObjectTOResult;
import org.apache.syncope.common.lib.to.ResourceTO;
-import org.apache.syncope.common.rest.api.beans.ConnObjectTOListQuery;
+import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery;
import org.apache.syncope.common.rest.api.service.ResourceService;
import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -63,7 +63,7 @@ public class ResourceRestClient extends BaseRestClient {
final String pagedResultCookie,
final SortParam<String> sort) {
- ConnObjectTOListQuery.Builder builder = new ConnObjectTOListQuery.Builder().
+ ConnObjectTOQuery.Builder builder = new ConnObjectTOQuery.Builder().
pagedResultsCookie(pagedResultCookie).
size(size).
orderBy(toOrderBy(sort));
@@ -73,7 +73,7 @@ public class ResourceRestClient extends BaseRestClient {
PagedConnObjectTOResult list;
try {
- list = getService(ResourceService.class).listConnObjects(resource, anyTypeKey, builder.build());
+ list = getService(ResourceService.class).searchConnObjects(resource, anyTypeKey, builder.build());
result.addAll(list.getResult());
nextPageResultCookie = list.getPagedResultsCookie();
} catch (Exception e) {
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/DynamicTemplateResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/DynamicTemplateResource.java
index 86b19bd..8e4268f 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/DynamicTemplateResource.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/DynamicTemplateResource.java
@@ -18,8 +18,6 @@
*/
package org.apache.syncope.client.enduser.resources;
-import static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER;
-
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.ws.rs.core.MediaType;
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/search/AbstractFiqlSearchConditionBuilder.java b/common/lib/src/main/java/org/apache/syncope/common/lib/search/AbstractFiqlSearchConditionBuilder.java
index 18fee59..5f8aa43 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/search/AbstractFiqlSearchConditionBuilder.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/search/AbstractFiqlSearchConditionBuilder.java
@@ -146,6 +146,5 @@ public abstract class AbstractFiqlSearchConditionBuilder extends FiqlSearchCondi
this.result = SpecialAttr.DYNREALMS.toString();
return condition(FiqlParser.NEQ, dynRealm, (Object[]) moreDynRealms);
}
-
}
}
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/search/AnyObjectFiqlSearchConditionBuilder.java b/common/lib/src/main/java/org/apache/syncope/common/lib/search/AnyObjectFiqlSearchConditionBuilder.java
index 9276015..b497c7b 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/search/AnyObjectFiqlSearchConditionBuilder.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/search/AnyObjectFiqlSearchConditionBuilder.java
@@ -162,6 +162,5 @@ public class AnyObjectFiqlSearchConditionBuilder extends AbstractFiqlSearchCondi
this.result = SpecialAttr.ASSIGNABLE.toString();
return condition(FiqlParser.EQ, SpecialAttr.NULL);
}
-
}
}
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/search/GroupFiqlSearchConditionBuilder.java b/common/lib/src/main/java/org/apache/syncope/common/lib/search/ConnObjectTOFiqlSearchConditionBuilder.java
similarity index 50%
copy from common/lib/src/main/java/org/apache/syncope/common/lib/search/GroupFiqlSearchConditionBuilder.java
copy to common/lib/src/main/java/org/apache/syncope/common/lib/search/ConnObjectTOFiqlSearchConditionBuilder.java
index 188b5e0..d028a71 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/search/GroupFiqlSearchConditionBuilder.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/search/ConnObjectTOFiqlSearchConditionBuilder.java
@@ -20,15 +20,14 @@ package org.apache.syncope.common.lib.search;
import java.util.Map;
import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
-import org.apache.cxf.jaxrs.ext.search.fiql.FiqlParser;
/**
* Extends {@link AbstractFiqlSearchConditionBuilder} by providing some additional facilities for searching
- * groups in Syncope.
+ * connector objects.
*/
-public class GroupFiqlSearchConditionBuilder extends AbstractFiqlSearchConditionBuilder {
+public class ConnObjectTOFiqlSearchConditionBuilder extends AbstractFiqlSearchConditionBuilder {
- private static final long serialVersionUID = 6275686371606165706L;
+ private static final long serialVersionUID = 4983742159694010935L;
@Override
protected Builder newBuilderInstance() {
@@ -36,30 +35,12 @@ public class GroupFiqlSearchConditionBuilder extends AbstractFiqlSearchCondition
}
@Override
- public GroupProperty is(final String property) {
+ public SyncopeProperty is(final String property) {
return newBuilderInstance().is(property);
}
- public CompleteCondition isAssignable() {
- return newBuilderInstance().
- is(SpecialAttr.ASSIGNABLE.toString()).
- isAssignable();
- }
-
- public CompleteCondition withMembers(final String member, final String... moreMembers) {
- return newBuilderInstance().
- is(SpecialAttr.MEMBER.toString()).
- withMembers(member, moreMembers);
- }
-
- public CompleteCondition withoutMembers(final String member, final String... moreMembers) {
- return newBuilderInstance().
- is(SpecialAttr.MEMBER.toString()).
- withoutMembers(member, moreMembers);
- }
-
protected class Builder extends AbstractFiqlSearchConditionBuilder.Builder
- implements GroupProperty, CompleteCondition {
+ implements SyncopeProperty, CompleteCondition {
public Builder(final Map<String, String> properties) {
super(properties);
@@ -70,29 +51,30 @@ public class GroupFiqlSearchConditionBuilder extends AbstractFiqlSearchCondition
}
@Override
- public GroupProperty is(final String property) {
+ public SyncopeProperty is(final String property) {
Builder b = new Builder(this);
b.result = property;
return b;
}
@Override
- public CompleteCondition isAssignable() {
- this.result = SpecialAttr.ASSIGNABLE.toString();
- return condition(FiqlParser.EQ, SpecialAttr.NULL);
+ public CompleteCondition inDynRealms(final String dynRealm, final String... moreDynRealms) {
+ throw new UnsupportedOperationException();
}
@Override
- public CompleteCondition withMembers(final String member, final String... moreMembers) {
- this.result = SpecialAttr.MEMBER.toString();
- return condition(FiqlParser.EQ, member, (Object[]) moreMembers);
+ public CompleteCondition notInDynRealms(final String dynRealm, final String... moreDynRealms) {
+ throw new UnsupportedOperationException();
}
@Override
- public CompleteCondition withoutMembers(final String member, final String... moreMembers) {
- this.result = SpecialAttr.MEMBER.toString();
- return condition(FiqlParser.NEQ, member, (Object[]) moreMembers);
+ public CompleteCondition hasResources(final String resource, final String... moreResources) {
+ throw new UnsupportedOperationException();
}
+ @Override
+ public CompleteCondition hasNotResources(final String resource, final String... moreResources) {
+ throw new UnsupportedOperationException();
+ }
}
}
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/search/GroupFiqlSearchConditionBuilder.java b/common/lib/src/main/java/org/apache/syncope/common/lib/search/GroupFiqlSearchConditionBuilder.java
index 188b5e0..727b1a3 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/search/GroupFiqlSearchConditionBuilder.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/search/GroupFiqlSearchConditionBuilder.java
@@ -93,6 +93,5 @@ public class GroupFiqlSearchConditionBuilder extends AbstractFiqlSearchCondition
this.result = SpecialAttr.MEMBER.toString();
return condition(FiqlParser.NEQ, member, (Object[]) moreMembers);
}
-
}
}
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ConnObjectTOListQuery.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ConnObjectTOQuery.java
similarity index 84%
rename from common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ConnObjectTOListQuery.java
rename to common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ConnObjectTOQuery.java
index 17322ca..db874ce 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ConnObjectTOListQuery.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ConnObjectTOQuery.java
@@ -25,7 +25,7 @@ import javax.ws.rs.DefaultValue;
import javax.ws.rs.QueryParam;
import org.apache.syncope.common.rest.api.service.JAXRSService;
-public class ConnObjectTOListQuery implements Serializable {
+public class ConnObjectTOQuery implements Serializable {
private static final long serialVersionUID = -371488230250055359L;
@@ -33,7 +33,7 @@ public class ConnObjectTOListQuery implements Serializable {
public static class Builder {
- private final ConnObjectTOListQuery instance = new ConnObjectTOListQuery();
+ private final ConnObjectTOQuery instance = new ConnObjectTOQuery();
public Builder size(final Integer size) {
instance.setSize(size);
@@ -50,10 +50,14 @@ public class ConnObjectTOListQuery implements Serializable {
return this;
}
- public ConnObjectTOListQuery build() {
- return instance;
+ public Builder fiql(final String fiql) {
+ instance.setFiql(fiql);
+ return this;
}
+ public ConnObjectTOQuery build() {
+ return instance;
+ }
}
private Integer size;
@@ -62,6 +66,8 @@ public class ConnObjectTOListQuery implements Serializable {
private String orderBy;
+ private String fiql;
+
public Integer getSize() {
return size == null
? 25
@@ -95,4 +101,13 @@ public class ConnObjectTOListQuery implements Serializable {
public void setOrderBy(final String orderBy) {
this.orderBy = orderBy;
}
+
+ public String getFiql() {
+ return fiql;
+ }
+
+ @QueryParam(JAXRSService.PARAM_FIQL)
+ public void setFiql(final String fiql) {
+ this.fiql = fiql;
+ }
}
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ResourceService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ResourceService.java
index 27b7813..cca2ac0 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ResourceService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ResourceService.java
@@ -45,7 +45,7 @@ import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.PagedConnObjectTOResult;
import org.apache.syncope.common.lib.to.ResourceTO;
import org.apache.syncope.common.rest.api.RESTHeaders;
-import org.apache.syncope.common.rest.api.beans.ConnObjectTOListQuery;
+import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery;
/**
* REST operations for external resources.
@@ -62,16 +62,17 @@ public interface ResourceService extends JAXRSService {
*
* @param key name of resource to read connector object from
* @param anyTypeKey any object type
- * @param anyKey any object key
+ * @param value if value looks like a UUID then it is interpreted as user, group or any object key, otherwise
+ * as key value on the resource
* @return connector object from the external resource, for the given type and key
*/
@GET
- @Path("{key}/{anyTypeKey}/{anyKey}")
+ @Path("{key}/{anyTypeKey}/{value}")
@Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
ConnObjectTO readConnObject(
@NotNull @PathParam("key") String key,
@NotNull @PathParam("anyTypeKey") String anyTypeKey,
- @NotNull @PathParam("anyKey") String anyKey);
+ @NotNull @PathParam("value") String value);
/**
* Returns a paged list of connector objects from external resource, for the given type, matching
@@ -79,16 +80,16 @@ public interface ResourceService extends JAXRSService {
*
* @param key name of resource to read connector object from
* @param anyTypeKey any object type
- * @param listQuery query conditions
+ * @param connObjectTOQuery query conditions
* @return connector objects from the external resource, for the given type
*/
@GET
@Path("{key}/{anyTypeKey}")
@Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
- PagedConnObjectTOResult listConnObjects(
+ PagedConnObjectTOResult searchConnObjects(
@NotNull @PathParam("key") String key,
@NotNull @PathParam("anyTypeKey") String anyTypeKey,
- @BeanParam ConnObjectTOListQuery listQuery);
+ @BeanParam ConnObjectTOQuery connObjectTOQuery);
/**
* Returns the resource with matching name.
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
index 47a892a..b71ddd6 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java
@@ -27,35 +27,30 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Pair;
-import org.apache.commons.lang3.tuple.Triple;
import org.apache.syncope.common.lib.collections.IteratorChain;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.ResourceTO;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.StandardEntitlement;
import org.apache.syncope.core.persistence.api.dao.DuplicateException;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
-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.resource.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
import org.apache.syncope.core.provisioning.api.Connector;
import org.apache.syncope.core.provisioning.api.ConnectorFactory;
import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
-import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
import org.apache.syncope.core.persistence.api.dao.ConnInstanceDAO;
import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.ConnInstance;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.apache.syncope.core.provisioning.api.MappingManager;
@@ -73,6 +68,7 @@ import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.SearchResult;
import org.identityconnectors.framework.common.objects.Uid;
+import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.spi.SearchResultsHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -89,18 +85,9 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
private AnyTypeDAO anyTypeDAO;
@Autowired
- private AnyObjectDAO anyObjectDAO;
-
- @Autowired
private ConnInstanceDAO connInstanceDAO;
@Autowired
- private UserDAO userDAO;
-
- @Autowired
- private GroupDAO groupDAO;
-
- @Autowired
private VirSchemaDAO virSchemaDAO;
@Autowired
@@ -115,6 +102,9 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
@Autowired
private ConnectorFactory connFactory;
+ @Autowired
+ private AnyUtilsFactory anyUtilsFactory;
+
protected void securityChecks(final Set<String> effectiveRealms, final String realm, final String key) {
boolean authorized = effectiveRealms.stream().anyMatch(ownedRealm -> realm.startsWith(ownedRealm));
if (!authorized) {
@@ -276,69 +266,57 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
return resourceDAO.findAll().stream().map(binder::getResourceTO).collect(Collectors.toList());
}
- private Triple<ExternalResource, AnyType, Provision> connObjectInit(
+ private Pair<AnyType, Provision> connObjectInit(
final String resourceKey, final String anyTypeKey) {
ExternalResource resource = resourceDAO.authFind(resourceKey);
if (resource == null) {
throw new NotFoundException("Resource '" + resourceKey + "'");
}
+
AnyType anyType = anyTypeDAO.find(anyTypeKey);
if (anyType == null) {
throw new NotFoundException("AnyType '" + anyTypeKey + "'");
}
- Optional<? extends Provision> provision = resource.getProvision(anyType);
- if (!provision.isPresent()) {
- throw new NotFoundException("Provision on resource '" + resourceKey + "' for type '" + anyTypeKey + "'");
- }
- return ImmutableTriple.of(resource, anyType, provision.get());
+ Provision provision = resource.getProvision(anyType).
+ orElseThrow(() -> new NotFoundException(
+ "Provision on resource '" + resourceKey + "' for type '" + anyTypeKey + "'"));
+
+ return Pair.of(anyType, provision);
}
- @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')")
- @Transactional(readOnly = true)
- public ConnObjectTO readConnObject(final String key, final String anyTypeKey, final String anyKey) {
- Triple<ExternalResource, AnyType, Provision> init = connObjectInit(key, anyTypeKey);
+ private ConnObjectTO readConnObject(
+ final Provision provision,
+ final String connObjectKeyValue) {
- // 1. find any
- Any<?> any = init.getMiddle().getKind() == AnyTypeKind.USER
- ? userDAO.find(anyKey)
- : init.getMiddle().getKind() == AnyTypeKind.ANY_OBJECT
- ? anyObjectDAO.find(anyKey)
- : groupDAO.find(anyKey);
- if (any == null) {
- throw new NotFoundException(init.getMiddle() + " " + anyKey);
- }
-
- // 2. build connObjectKeyItem
- MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(init.getRight()).
- orElseThrow(() -> new NotFoundException(
- "ConnObjectKey mapping for " + init.getMiddle() + " " + anyKey + " on resource '" + key + "'"));
- String connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, init.getRight()).
+ // 0. build connObjectKeyItem
+ MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision).
orElseThrow(() -> new NotFoundException(
- "ConnObjectKey value for " + init.getMiddle() + " " + anyKey + " on resource '" + key + "'"));
+ "ConnObjectKey mapping for " + provision.getAnyType().getKey()
+ + " on resource '" + provision.getResource().getKey() + "'"));
- // 3. determine attributes to query
- Set<MappingItem> linkinMappingItems = virSchemaDAO.findByProvision(init.getRight()).stream().
+ // 1. determine attributes to query
+ Set<MappingItem> linkinMappingItems = virSchemaDAO.findByProvision(provision).stream().
map(virSchema -> virSchema.asLinkingMappingItem()).collect(Collectors.toSet());
Iterator<MappingItem> mapItems = new IteratorChain<>(
- init.getRight().getMapping().getItems().iterator(),
+ provision.getMapping().getItems().iterator(),
linkinMappingItems.iterator());
- // 4. read from the underlying connector
- Connector connector = connFactory.getConnector(init.getLeft());
+ // 2. read from the underlying connector
+ Connector connector = connFactory.getConnector(provision.getResource());
ConnectorObject connectorObject = connector.getObject(
- init.getRight().getObjectClass(),
+ provision.getObjectClass(),
AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKeyValue),
- init.getRight().isIgnoreCaseMatch(),
+ provision.isIgnoreCaseMatch(),
MappingUtils.buildOperationOptions(mapItems));
if (connectorObject == null) {
throw new NotFoundException(
- "Object " + connObjectKeyValue + " with class " + init.getRight().getObjectClass()
- + " not found on resource " + key);
+ "Object " + connObjectKeyValue + " with class " + provision.getObjectClass()
+ + " not found on resource " + provision.getResource().getKey());
}
- // 5. build result
+ // 3. build result
Set<Attribute> attributes = connectorObject.getAttributes();
if (AttributeUtil.find(Uid.NAME, attributes) == null) {
attributes.add(connectorObject.getUid());
@@ -350,10 +328,49 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
return ConnObjectUtils.getConnObjectTO(connectorObject);
}
+ @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')")
+ @Transactional(readOnly = true)
+ public ConnObjectTO readConnObjectByAnyKey(
+ final String key,
+ final String anyTypeKey,
+ final String anyKey) {
+
+ Pair<AnyType, Provision> init = connObjectInit(key, anyTypeKey);
+
+ // 1. find any
+ Any<?> any = anyUtilsFactory.getInstance(init.getLeft().getKind()).dao().authFind(anyKey);
+ if (any == null) {
+ throw new NotFoundException(init.getLeft() + " " + anyKey);
+ }
+
+ // 2. find connObjectKeyValue
+ String connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, init.getRight()).
+ orElseThrow(() -> new NotFoundException(
+ "ConnObjectKey value for " + init.getLeft() + " " + anyKey + " on resource '" + key + "'"));
+
+ return readConnObject(init.getRight(), connObjectKeyValue);
+ }
+
+ @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')")
+ @Transactional(readOnly = true)
+ public ConnObjectTO readConnObjectByConnObjectKey(
+ final String key,
+ final String anyTypeKey,
+ final String connObjectKeyValue) {
+
+ Pair<AnyType, Provision> init = connObjectInit(key, anyTypeKey);
+ return readConnObject(init.getRight(), connObjectKeyValue);
+ }
+
@PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_LIST_CONNOBJECT + "')")
@Transactional(readOnly = true)
- public Pair<SearchResult, List<ConnObjectTO>> listConnObjects(final String key, final String anyTypeKey,
- final int size, final String pagedResultsCookie, final List<OrderByClause> orderBy) {
+ public Pair<SearchResult, List<ConnObjectTO>> searchConnObjects(
+ final Filter filter,
+ final String key,
+ final String anyTypeKey,
+ final int size,
+ final String pagedResultsCookie,
+ final List<OrderByClause> orderBy) {
ExternalResource resource;
ObjectClass objectClass;
@@ -371,8 +388,8 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
options = MappingUtils.buildOperationOptions(
MappingUtils.getPropagationItems(resource.getOrgUnit().getItems()).iterator());
} else {
- Triple<ExternalResource, AnyType, Provision> init = connObjectInit(key, anyTypeKey);
- resource = init.getLeft();
+ Pair<AnyType, Provision> init = connObjectInit(key, anyTypeKey);
+ resource = init.getRight().getResource();
objectClass = init.getRight().getObjectClass();
init.getRight().getMapping().getItems();
@@ -386,7 +403,7 @@ public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
List<ConnObjectTO> connObjects = new ArrayList<>();
SearchResult searchResult = connFactory.getConnector(resource).
- search(objectClass, null, new SearchResultsHandler() {
+ search(objectClass, filter, new SearchResultsHandler() {
private int count;
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/FilterConverter.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/FilterConverter.java
new file mode 100644
index 0000000..124a478
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/FilterConverter.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.search;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.cxf.jaxrs.ext.search.SearchBean;
+import org.apache.cxf.jaxrs.ext.search.SearchCondition;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.search.AbstractFiqlSearchConditionBuilder;
+import org.apache.syncope.common.lib.search.SyncopeFiqlParser;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.identityconnectors.framework.common.objects.filter.Filter;
+
+/**
+ * Converts FIQL expressions to ConnId's {@link Filter}.
+ */
+public final class FilterConverter {
+
+ /**
+ * Parses a FIQL expression into ConnId's {@link Filter}, using {@link SyncopeFiqlParser}.
+ *
+ * @param fiql FIQL string
+ * @return {@link Filter} instance for given FIQL expression
+ */
+ public static Filter convert(final String fiql) {
+ SyncopeFiqlParser<SearchBean> parser = new SyncopeFiqlParser<>(
+ SearchBean.class, AbstractFiqlSearchConditionBuilder.CONTEXTUAL_PROPERTIES);
+
+ try {
+ FilterVisitor visitor = new FilterVisitor();
+ SearchCondition<SearchBean> sc = parser.parse(URLDecoder.decode(fiql, StandardCharsets.UTF_8.name()));
+ sc.accept(visitor);
+
+ return visitor.getQuery();
+ } catch (Exception e) {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchExpression);
+ sce.getElements().add(fiql);
+ sce.getElements().add(ExceptionUtils.getRootCauseMessage(e));
+ throw sce;
+ }
+ }
+
+ private FilterConverter() {
+ // empty constructor for static utility class
+ }
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/FilterVisitor.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/FilterVisitor.java
new file mode 100644
index 0000000..73d3980
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/FilterVisitor.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.search;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.apache.cxf.jaxrs.ext.search.ConditionType;
+import org.apache.cxf.jaxrs.ext.search.SearchBean;
+import org.apache.cxf.jaxrs.ext.search.SearchCondition;
+import org.apache.cxf.jaxrs.ext.search.SearchUtils;
+import org.apache.cxf.jaxrs.ext.search.visitor.AbstractSearchConditionVisitor;
+import org.apache.syncope.common.lib.search.SpecialAttr;
+import org.apache.syncope.common.lib.search.SyncopeFiqlParser;
+import org.apache.syncope.common.lib.search.SyncopeFiqlSearchCondition;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.filter.Filter;
+import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
+
+public class FilterVisitor extends AbstractSearchConditionVisitor<SearchBean, Filter> {
+
+ private Filter filter;
+
+ public FilterVisitor() {
+ super(null);
+ }
+
+ private Filter visitPrimitive(final SearchCondition<SearchBean> sc) {
+ String name = getRealPropertyName(sc.getStatement().getProperty());
+ Optional<SpecialAttr> specialAttrName = SpecialAttr.fromString(name);
+
+ String value = null;
+ try {
+ value = SearchUtils.toSqlWildcardString(
+ URLDecoder.decode(sc.getStatement().getValue().toString(), StandardCharsets.UTF_8.name()), false).
+ replaceAll("\\\\_", "_");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("While decoding " + sc.getStatement().getValue(), e);
+ }
+ Optional<SpecialAttr> specialAttrValue = SpecialAttr.fromString(value);
+
+ ConditionType ct = sc.getConditionType();
+ if (sc instanceof SyncopeFiqlSearchCondition && sc.getConditionType() == ConditionType.CUSTOM) {
+ SyncopeFiqlSearchCondition<SearchBean> sfsc = (SyncopeFiqlSearchCondition<SearchBean>) sc;
+ switch (sfsc.getOperator()) {
+ case SyncopeFiqlParser.IEQ:
+ ct = ConditionType.EQUALS;
+ break;
+
+ case SyncopeFiqlParser.NIEQ:
+ ct = ConditionType.NOT_EQUALS;
+ break;
+
+ default:
+ throw new IllegalArgumentException(
+ String.format("Condition type %s is not supported", sfsc.getOperator()));
+ }
+ }
+
+ Attribute attr = AttributeBuilder.build(name, value);
+
+ Filter leaf;
+ switch (ct) {
+ case EQUALS:
+ case NOT_EQUALS:
+ if (!specialAttrName.isPresent()) {
+ if (specialAttrValue.isPresent() && specialAttrValue.get() == SpecialAttr.NULL) {
+ leaf = FilterBuilder.equalTo(AttributeBuilder.build(name));
+ } else if (value.indexOf('%') == -1) {
+ leaf = sc.getConditionType() == ConditionType.CUSTOM
+ ? FilterBuilder.equalsIgnoreCase(attr)
+ : FilterBuilder.equalTo(attr);
+ } else if (sc.getConditionType() != ConditionType.CUSTOM && value.startsWith("%")) {
+ leaf = FilterBuilder.endsWith(
+ AttributeBuilder.build(name, value.substring(1)));
+ } else if (sc.getConditionType() != ConditionType.CUSTOM && value.endsWith("%")) {
+ leaf = FilterBuilder.startsWith(
+ AttributeBuilder.build(name, value.substring(0, value.length() - 1)));
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Unsupported search value %s", value));
+ }
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Special attr name %s is not supported", specialAttrName));
+ }
+ if (ct == ConditionType.NOT_EQUALS) {
+ leaf = FilterBuilder.not(leaf);
+ }
+ break;
+
+ case GREATER_OR_EQUALS:
+ leaf = FilterBuilder.greaterThanOrEqualTo(attr);
+ break;
+
+ case GREATER_THAN:
+ leaf = FilterBuilder.greaterThan(attr);
+ break;
+
+ case LESS_OR_EQUALS:
+ leaf = FilterBuilder.lessThanOrEqualTo(attr);
+ break;
+
+ case LESS_THAN:
+ leaf = FilterBuilder.lessThan(attr);
+ break;
+
+ default:
+ throw new IllegalArgumentException(String.format("Condition type %s is not supported", ct.name()));
+ }
+
+ return leaf;
+ }
+
+ private Filter visitCompount(final SearchCondition<SearchBean> sc) {
+ List<Filter> searchConds = new ArrayList<>();
+ sc.getSearchConditions().forEach(searchCond -> {
+ searchConds.add(searchCond.getStatement() == null
+ ? visitCompount(searchCond)
+ : visitPrimitive(searchCond));
+ });
+
+ Filter compound;
+ switch (sc.getConditionType()) {
+ case AND:
+ compound = FilterBuilder.and(searchConds);
+ break;
+
+ case OR:
+ compound = FilterBuilder.or(searchConds);
+ break;
+
+ default:
+ throw new IllegalArgumentException(
+ String.format("Condition type %s is not supported", sc.getConditionType().name()));
+ }
+
+ return compound;
+ }
+
+ @Override
+ public void visit(final SearchCondition<SearchBean> sc) {
+ filter = sc.getStatement() == null
+ ? visitCompount(sc)
+ : visitPrimitive(sc);
+ }
+
+ @Override
+ public Filter getQuery() {
+ return filter;
+ }
+}
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 7d01f8a..8f6a097 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
@@ -103,13 +103,18 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
ConditionType ct = sc.getConditionType();
if (sc instanceof SyncopeFiqlSearchCondition && sc.getConditionType() == ConditionType.CUSTOM) {
SyncopeFiqlSearchCondition<SearchBean> sfsc = (SyncopeFiqlSearchCondition<SearchBean>) sc;
- if (SyncopeFiqlParser.IEQ.equals(sfsc.getOperator())) {
- ct = ConditionType.EQUALS;
- } else if (SyncopeFiqlParser.NIEQ.equals(sfsc.getOperator())) {
- ct = ConditionType.NOT_EQUALS;
- } else {
- throw new IllegalArgumentException(
- String.format("Condition type %s is not supported", sfsc.getOperator()));
+ switch (sfsc.getOperator()) {
+ case SyncopeFiqlParser.IEQ:
+ ct = ConditionType.EQUALS;
+ break;
+
+ case SyncopeFiqlParser.NIEQ:
+ ct = ConditionType.NOT_EQUALS;
+ break;
+
+ default:
+ throw new IllegalArgumentException(
+ String.format("Condition type %s is not supported", sfsc.getOperator()));
}
}
@@ -257,10 +262,10 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
private SearchCond visitCompount(final SearchCondition<SearchBean> sc) {
List<SearchCond> searchConds = new ArrayList<>();
- sc.getSearchConditions().forEach(searchCondition -> {
- searchConds.add(searchCondition.getStatement() == null
- ? visitCompount(searchCondition)
- : visitPrimitive(searchCondition));
+ sc.getSearchConditions().forEach(searchCond -> {
+ searchConds.add(searchCond.getStatement() == null
+ ? visitCompount(searchCond)
+ : visitPrimitive(searchCond));
});
SearchCond compound;
diff --git a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/FilterConverterTest.java b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/FilterConverterTest.java
new file mode 100644
index 0000000..40c9b69
--- /dev/null
+++ b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/FilterConverterTest.java
@@ -0,0 +1,289 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.search;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.List;
+import java.util.ListIterator;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.search.SpecialAttr;
+import org.apache.syncope.common.lib.search.ConnObjectTOFiqlSearchConditionBuilder;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.filter.AndFilter;
+import org.identityconnectors.framework.common.objects.filter.Filter;
+import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
+import org.identityconnectors.framework.common.objects.filter.NotFilter;
+import org.identityconnectors.framework.common.objects.filter.OrFilter;
+import org.junit.jupiter.api.Test;
+
+public class FilterConverterTest {
+
+ private boolean equals(final Filter filter1, final Filter filter2) {
+ return EqualsBuilder.reflectionEquals(filter1, filter2);
+ }
+
+ private boolean equals(final List<Filter> filters1, final List<Filter> filters2) {
+ ListIterator<Filter> e1 = filters1.listIterator();
+ ListIterator<Filter> e2 = filters2.listIterator();
+ while (e1.hasNext() && e2.hasNext()) {
+ Filter o1 = e1.next();
+ Filter o2 = e2.next();
+ if (!equals(o1, o2)) {
+ return false;
+ }
+ }
+ return !(e1.hasNext() || e2.hasNext());
+ }
+
+ @Test
+ public void eq() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("username").equalTo("rossini").query();
+ assertEquals("username==rossini", fiql);
+
+ Filter filter = FilterBuilder.equalTo(AttributeBuilder.build("username", "rossini"));
+
+ assertTrue(equals(filter, FilterConverter.convert(fiql)));
+ }
+
+ @Test
+ public void ieq() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("username").equalToIgnoreCase("rossini").query();
+ assertEquals("username=~rossini", fiql);
+
+ Filter filter = FilterBuilder.equalsIgnoreCase(AttributeBuilder.build("username", "rossini"));
+
+ assertTrue(equals(filter, FilterConverter.convert(fiql)));
+ }
+
+ @Test
+ public void nieq() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("username").notEqualTolIgnoreCase("rossini").
+ query();
+ assertEquals("username!~rossini", fiql);
+
+ Filter filter = FilterBuilder.not(
+ FilterBuilder.equalsIgnoreCase(AttributeBuilder.build("username", "rossini")));
+ assertTrue(filter instanceof NotFilter);
+
+ Filter converted = FilterConverter.convert(fiql);
+ assertTrue(converted instanceof NotFilter);
+
+ assertTrue(equals(
+ ((NotFilter) filter).getFilter(), ((NotFilter) converted).getFilter()));
+ }
+
+ @Test
+ public void like() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("username").equalTo("ros*").query();
+ assertEquals("username==ros*", fiql);
+
+ Filter filter = FilterBuilder.startsWith(AttributeBuilder.build("username", "ros"));
+
+ assertTrue(equals(filter, FilterConverter.convert(fiql)));
+
+ fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("username").equalTo("*ini").query();
+ assertEquals("username==*ini", fiql);
+
+ filter = FilterBuilder.endsWith(AttributeBuilder.build("username", "ini"));
+
+ assertTrue(equals(filter, FilterConverter.convert(fiql)));
+
+ fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("username").equalTo("r*ini").query();
+ assertEquals("username==r*ini", fiql);
+
+ try {
+ FilterConverter.convert(fiql);
+ fail();
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidSearchExpression, e.getType());
+ }
+ }
+
+ @Test
+ public void ilike() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("username").equalToIgnoreCase("ros*").query();
+ assertEquals("username=~ros*", fiql);
+
+ try {
+ FilterConverter.convert(fiql);
+ fail();
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidSearchExpression, e.getType());
+ }
+ }
+
+ @Test
+ public void nilike() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("username").notEqualTolIgnoreCase("ros*").query();
+ assertEquals("username!~ros*", fiql);
+
+ try {
+ FilterConverter.convert(fiql);
+ fail();
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidSearchExpression, e.getType());
+ }
+ }
+
+ @Test
+ public void isNull() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("loginDate").nullValue().query();
+ assertEquals("loginDate==" + SpecialAttr.NULL, fiql);
+
+ Filter filter = FilterBuilder.equalTo(AttributeBuilder.build("loginDate"));
+
+ assertTrue(equals(filter, FilterConverter.convert(fiql)));
+ }
+
+ @Test
+ public void isNotNull() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("loginDate").notNullValue().query();
+ assertEquals("loginDate!=" + SpecialAttr.NULL, fiql);
+
+ Filter filter = FilterBuilder.not(FilterBuilder.equalTo(AttributeBuilder.build("loginDate")));
+ assertTrue(filter instanceof NotFilter);
+
+ Filter converted = FilterConverter.convert(fiql);
+ assertTrue(converted instanceof NotFilter);
+
+ assertTrue(equals(
+ ((NotFilter) filter).getFilter(), ((NotFilter) converted).getFilter()));
+ }
+
+ @Test
+ public void inDynRealms() {
+ try {
+ new ConnObjectTOFiqlSearchConditionBuilder().inDynRealms("realm").query();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ assertNotNull(e);
+ }
+
+ try {
+ FilterConverter.convert(SpecialAttr.DYNREALMS + "==realm");
+ fail();
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidSearchExpression, e.getType());
+ }
+ }
+
+ @Test
+ public void notInDynRealms() {
+ try {
+ new ConnObjectTOFiqlSearchConditionBuilder().notInDynRealms("realm").query();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ assertNotNull(e);
+ }
+
+ try {
+ FilterConverter.convert(SpecialAttr.DYNREALMS + "!=realm");
+ fail();
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidSearchExpression, e.getType());
+ }
+ }
+
+ @Test
+ public void hasResources() {
+ try {
+ new ConnObjectTOFiqlSearchConditionBuilder().hasResources("resource").query();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ assertNotNull(e);
+ }
+
+ try {
+ FilterConverter.convert(SpecialAttr.RESOURCES + "==resource");
+ fail();
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidSearchExpression, e.getType());
+ }
+ }
+
+ @Test
+ public void hasNotResources() {
+ try {
+ new ConnObjectTOFiqlSearchConditionBuilder().hasNotResources("resource").query();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ assertNotNull(e);
+ }
+
+ try {
+ FilterConverter.convert(SpecialAttr.RESOURCES + "!=resource");
+ fail();
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidSearchExpression, e.getType());
+ }
+ }
+
+ @Test
+ public void and() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().
+ is("fullname").equalTo("ro*").and("fullname").equalTo("*i").query();
+ assertEquals("fullname==ro*;fullname==*i", fiql);
+
+ Filter filter1 = FilterBuilder.startsWith(AttributeBuilder.build("fullname", "ro"));
+ Filter filter2 = FilterBuilder.endsWith(AttributeBuilder.build("fullname", "i"));
+
+ Filter filter = FilterBuilder.and(filter1, filter2);
+ assertTrue(filter instanceof AndFilter);
+
+ Filter converted = FilterConverter.convert(fiql);
+ assertTrue(converted instanceof AndFilter);
+
+ assertTrue(equals(
+ (List<Filter>) ((AndFilter) filter).getFilters(), (List<Filter>) ((AndFilter) converted).getFilters()));
+ }
+
+ @Test
+ public void or() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().
+ is("fullname").equalTo("ro*").or("fullname").equalTo("*i").query();
+ assertEquals("fullname==ro*,fullname==*i", fiql);
+
+ Filter filter1 = FilterBuilder.startsWith(AttributeBuilder.build("fullname", "ro"));
+ Filter filter2 = FilterBuilder.endsWith(AttributeBuilder.build("fullname", "i"));
+
+ Filter filter = FilterBuilder.or(filter1, filter2);
+ assertTrue(filter instanceof OrFilter);
+
+ Filter converted = FilterConverter.convert(fiql);
+ assertTrue(converted instanceof OrFilter);
+
+ assertTrue(equals(
+ (List<Filter>) ((OrFilter) filter).getFilters(), (List<Filter>) ((OrFilter) converted).getFilters()));
+ }
+
+ @Test
+ public void issueSYNCOPE1223() {
+ String fiql = new ConnObjectTOFiqlSearchConditionBuilder().is("ctype").equalTo("ou=sample%252Co=isp").query();
+
+ Filter filter = FilterBuilder.equalTo(AttributeBuilder.build("ctype", "ou=sample,o=isp"));
+
+ assertTrue(equals(filter, FilterConverter.convert(fiql)));
+ }
+}
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
index 2076fbb..ea255de 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
@@ -25,15 +25,23 @@ import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cxf.jaxrs.ext.search.SearchBean;
+import org.apache.cxf.jaxrs.ext.search.SearchCondition;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.PagedConnObjectTOResult;
import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.rest.api.RESTHeaders;
-import org.apache.syncope.common.rest.api.beans.ConnObjectTOListQuery;
+import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery;
import org.apache.syncope.common.rest.api.service.ResourceService;
import org.apache.syncope.core.logic.ResourceLogic;
+import org.apache.syncope.core.persistence.api.search.FilterVisitor;
import org.identityconnectors.framework.common.objects.SearchResult;
+import org.identityconnectors.framework.common.objects.filter.Filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -83,16 +91,41 @@ public class ResourceServiceImpl extends AbstractServiceImpl implements Resource
}
@Override
- public ConnObjectTO readConnObject(final String key, final String anyTypeKey, final String anyKey) {
- return logic.readConnObject(key, anyTypeKey, anyKey);
+ public ConnObjectTO readConnObject(final String key, final String anyTypeKey, final String value) {
+ return SyncopeConstants.UUID_PATTERN.matcher(value).matches()
+ ? logic.readConnObjectByAnyKey(key, anyTypeKey, value)
+ : logic.readConnObjectByConnObjectKey(key, anyTypeKey, value);
}
@Override
- public PagedConnObjectTOResult listConnObjects(
- final String key, final String anyTypeKey, final ConnObjectTOListQuery listQuery) {
+ public PagedConnObjectTOResult searchConnObjects(
+ final String key, final String anyTypeKey, final ConnObjectTOQuery query) {
+
+ Filter filter = null;
+ if (StringUtils.isNotBlank(query.getFiql())) {
+ try {
+ FilterVisitor visitor = new FilterVisitor();
+ SearchCondition<SearchBean> sc = searchContext.getCondition(query.getFiql(), SearchBean.class);
+ sc.accept(visitor);
+
+ filter = visitor.getQuery();
+ } catch (Exception e) {
+ LOG.error("Invalid FIQL expression: {}", query.getFiql(), e);
+
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchExpression);
+ sce.getElements().add(query.getFiql());
+ sce.getElements().add(ExceptionUtils.getRootCauseMessage(e));
+ throw sce;
+ }
+ }
- Pair<SearchResult, List<ConnObjectTO>> list = logic.listConnObjects(key, anyTypeKey,
- listQuery.getSize(), listQuery.getPagedResultsCookie(), getOrderByClauses(listQuery.getOrderBy()));
+ Pair<SearchResult, List<ConnObjectTO>> list = logic.searchConnObjects(
+ filter,
+ key,
+ anyTypeKey,
+ query.getSize(),
+ query.getPagedResultsCookie(),
+ getOrderByClauses(query.getOrderBy()));
PagedConnObjectTOResult result = new PagedConnObjectTOResult();
if (list.getLeft() != null) {
@@ -111,7 +144,7 @@ public class ResourceServiceImpl extends AbstractServiceImpl implements Resource
if (StringUtils.isNotBlank(result.getPagedResultsCookie())) {
result.setNext(builder.
replaceQueryParam(PARAM_CONNID_PAGED_RESULTS_COOKIE, result.getPagedResultsCookie()).
- replaceQueryParam(PARAM_SIZE, listQuery.getSize()).
+ replaceQueryParam(PARAM_SIZE, query.getSize()).
build());
}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
index 6a25c5f..ab6f054 100644
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
@@ -18,10 +18,6 @@
*/
package org.apache.syncope.client.enduser.resources;
-import org.apache.syncope.client.enduser.model.UserRequestWrapper;
-
-import static org.apache.syncope.client.enduser.resources.BaseResource.LOG;
-
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
@@ -31,6 +27,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.syncope.client.enduser.SyncopeEnduserSession;
import org.apache.syncope.client.enduser.annotations.Resource;
+import org.apache.syncope.client.enduser.model.UserRequestWrapper;
import org.apache.syncope.common.lib.BaseBean;
import org.apache.syncope.common.lib.to.PagedResult;
import org.apache.syncope.common.lib.to.UserRequest;
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java
index 4c65768..fd2503a 100644
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java
@@ -18,8 +18,6 @@
*/
package org.apache.syncope.client.enduser.resources;
-import static org.apache.syncope.client.enduser.resources.BaseResource.LOG;
-
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
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 b7b3320..49c7c82 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
@@ -30,8 +30,6 @@ import java.util.List;
import java.util.Optional;
import java.util.UUID;
import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapContext;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@@ -44,6 +42,7 @@ import org.apache.syncope.common.lib.patch.LinkedAccountPatch;
import org.apache.syncope.common.lib.patch.UserPatch;
import org.apache.syncope.common.lib.policy.PullPolicyTO;
import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.ExecTO;
import org.apache.syncope.common.lib.to.ImplementationTO;
import org.apache.syncope.common.lib.to.LinkedAccountTO;
@@ -102,15 +101,11 @@ public class LinkedAccountITCase extends AbstractITCase {
assertEquals(ResourceOperation.CREATE, tasks.getResult().get(0).getOperation());
assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
- LdapContext ldapObj = (LdapContext) getLdapRemoteObject(
- RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectKeyValue);
+ ConnObjectTO ldapObj = resourceService.readConnObject(
+ RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), connObjectKeyValue);
assertNotNull(ldapObj);
-
- Attributes ldapAttrs = ldapObj.getAttributes("");
- assertEquals(
- user.getPlainAttr("email").get().getValues().get(0),
- ldapAttrs.get("mail").getAll().next().toString());
- assertEquals("LINKED_SURNAME", ldapAttrs.get("sn").getAll().next().toString());
+ assertEquals(user.getPlainAttr("email").get().getValues(), ldapObj.getAttr("mail").get().getValues());
+ assertEquals("LINKED_SURNAME", ldapObj.getAttr("sn").get().getValues().get(0));
// 3. update linked account
UserPatch userPatch = new UserPatch();
@@ -125,12 +120,11 @@ public class LinkedAccountITCase extends AbstractITCase {
assertEquals(1, user.getLinkedAccounts().size());
// 4 verify that account was updated on resource
- ldapObj = (LdapContext) getLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectKeyValue);
+ ldapObj = resourceService.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), connObjectKeyValue);
assertNotNull(ldapObj);
- ldapAttrs = ldapObj.getAttributes("");
- assertEquals("UPDATED_EMAIL@syncope.apache.org", ldapAttrs.get("mail").getAll().next().toString());
- assertEquals("UPDATED_SURNAME", ldapAttrs.get("sn").getAll().next().toString());
+ assertTrue(ldapObj.getAttr("mail").get().getValues().contains("UPDATED_EMAIL@syncope.apache.org"));
+ assertEquals("UPDATED_SURNAME", ldapObj.getAttr("sn").get().getValues().get(0));
// 5. remove linked account from user
userPatch = new UserPatch();
@@ -196,15 +190,11 @@ public class LinkedAccountITCase extends AbstractITCase {
assertEquals(ResourceOperation.CREATE, tasks.getResult().get(0).getOperation());
assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
- LdapContext ldapObj = (LdapContext) getLdapRemoteObject(
- RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD, connObjectKeyValue);
+ ConnObjectTO ldapObj = resourceService.readConnObject(
+ RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), connObjectKeyValue);
assertNotNull(ldapObj);
-
- Attributes ldapAttrs = ldapObj.getAttributes("");
- assertEquals(
- user.getPlainAttr("email").get().getValues().get(0),
- ldapAttrs.get("mail").getAll().next().toString());
- assertEquals("LINKED_SURNAME", ldapAttrs.get("sn").getAll().next().toString());
+ assertEquals(user.getPlainAttr("email").get().getValues(), ldapObj.getAttr("mail").get().getValues());
+ assertEquals("LINKED_SURNAME", ldapObj.getAttr("sn").get().getValues().get(0));
}
@Test
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
index 2515264..d892acf 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
@@ -20,34 +20,26 @@ package org.apache.syncope.fit.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
-import java.util.stream.Collectors;
import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.SerializationUtils;
-import org.apache.syncope.client.console.commons.ConnIdSpecialName;
import org.apache.syncope.client.lib.SyncopeClient;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.to.AnyObjectTO;
-import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.ItemTO;
import org.apache.syncope.common.lib.to.MappingTO;
import org.apache.syncope.common.lib.to.OrgUnitTO;
-import org.apache.syncope.common.lib.to.PagedConnObjectTOResult;
import org.apache.syncope.common.lib.to.ProvisionTO;
-import org.apache.syncope.common.lib.to.ResourceHistoryConfTO;
import org.apache.syncope.common.lib.to.ResourceTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.ClientExceptionType;
@@ -57,7 +49,6 @@ import org.apache.syncope.common.lib.types.EntityViolationType;
import org.apache.syncope.common.lib.types.ImplementationType;
import org.apache.syncope.common.lib.types.MappingPurpose;
import org.apache.syncope.common.lib.types.TraceLevel;
-import org.apache.syncope.common.rest.api.beans.ConnObjectTOListQuery;
import org.apache.syncope.common.rest.api.service.ResourceService;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.apache.syncope.fit.AbstractITCase;
@@ -514,96 +505,6 @@ public class ResourceITCase extends AbstractITCase {
}
@Test
- public void listConnObjects() {
- List<String> groupKeys = new ArrayList<>();
- for (int i = 0; i < 10; i++) {
- GroupTO group = GroupITCase.getSampleTO("group");
- group.getResources().add(RESOURCE_NAME_LDAP);
- group = createGroup(group).getEntity();
- groupKeys.add(group.getKey());
- }
-
- int totalRead = 0;
- Set<String> read = new HashSet<>();
- try {
- ConnObjectTOListQuery.Builder builder = new ConnObjectTOListQuery.Builder().size(10);
- PagedConnObjectTOResult list;
- do {
- list = null;
-
- boolean succeeded = false;
- // needed because ApacheDS seems to randomly fail when searching with cookie
- for (int i = 0; i < 5 && !succeeded; i++) {
- try {
- list = resourceService.listConnObjects(
- RESOURCE_NAME_LDAP,
- AnyTypeKind.GROUP.name(),
- builder.build());
- succeeded = true;
- } catch (SyncopeClientException e) {
- assertEquals(ClientExceptionType.ConnectorException, e.getType());
- }
- }
- assertNotNull(list);
-
- totalRead += list.getResult().size();
- read.addAll(list.getResult().stream().
- map(input -> input.getAttr(ConnIdSpecialName.NAME).get().getValues().get(0)).
- collect(Collectors.toList()));
-
- if (list.getPagedResultsCookie() != null) {
- builder.pagedResultsCookie(list.getPagedResultsCookie());
- }
- } while (list.getPagedResultsCookie() != null);
-
- assertEquals(totalRead, read.size());
- assertTrue(totalRead >= 10);
- } finally {
- groupKeys.forEach(key -> {
- groupService.delete(key);
- });
- }
- }
-
- @Test
- public void history() {
- List<ResourceHistoryConfTO> history = resourceHistoryService.list(RESOURCE_NAME_LDAP);
- assertNotNull(history);
- int pre = history.size();
-
- ResourceTO ldap = resourceService.read(RESOURCE_NAME_LDAP);
- TraceLevel originalTraceLevel = SerializationUtils.clone(ldap.getUpdateTraceLevel());
- assertEquals(TraceLevel.ALL, originalTraceLevel);
- ProvisionTO originalProvision = SerializationUtils.clone(ldap.getProvision(AnyTypeKind.USER.name()).get());
- assertEquals(ObjectClass.ACCOUNT_NAME, originalProvision.getObjectClass());
- boolean originalFlag = ldap.isRandomPwdIfNotProvided();
- assertTrue(originalFlag);
-
- ldap.setUpdateTraceLevel(TraceLevel.FAILURES);
- ldap.getProvision(AnyTypeKind.USER.name()).get().setObjectClass("ANOTHER");
- ldap.setRandomPwdIfNotProvided(false);
- resourceService.update(ldap);
-
- ldap = resourceService.read(RESOURCE_NAME_LDAP);
- assertNotEquals(originalTraceLevel, ldap.getUpdateTraceLevel());
- assertNotEquals(
- originalProvision.getObjectClass(), ldap.getProvision(AnyTypeKind.USER.name()).get().getObjectClass());
- assertNotEquals(originalFlag, ldap.isRandomPwdIfNotProvided());
-
- history = resourceHistoryService.list(RESOURCE_NAME_LDAP);
- assertEquals(pre + 1, history.size());
-
- resourceHistoryService.restore(history.get(0).getKey());
-
- ldap = resourceService.read(RESOURCE_NAME_LDAP);
- assertEquals(originalTraceLevel, ldap.getUpdateTraceLevel());
- assertEquals(
- originalProvision.getObjectClass(),
- ldap.getProvision(AnyTypeKind.USER.name()).get().getObjectClass());
- assertEquals(originalFlag, ldap.isRandomPwdIfNotProvided());
- }
-
- @Test
public void authorizations() {
SyncopeClient puccini = clientFactory.create("puccini", ADMIN_PWD);
ResourceService prs = puccini.getService(ResourceService.class);
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 4c5bb76..5eb40d2 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
@@ -25,8 +25,14 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.syncope.client.console.commons.ConnIdSpecialName;
import org.apache.syncope.client.lib.SyncopeClient;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.SyncopeConstants;
@@ -34,19 +40,24 @@ import org.apache.syncope.common.lib.patch.AnyObjectPatch;
import org.apache.syncope.common.lib.patch.AttrPatch;
import org.apache.syncope.common.lib.patch.MembershipPatch;
import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.search.ConnObjectTOFiqlSearchConditionBuilder;
import org.apache.syncope.common.lib.to.AnyObjectTO;
import org.apache.syncope.common.lib.to.AnyTypeTO;
+import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.PagedResult;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.PagedConnObjectTOResult;
import org.apache.syncope.common.lib.to.RoleTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery;
import org.apache.syncope.common.rest.api.service.RoleService;
import org.apache.syncope.fit.AbstractITCase;
import org.apache.syncope.fit.ElasticsearchDetector;
+import org.identityconnectors.framework.common.objects.Name;
import org.junit.jupiter.api.Test;
public class SearchITCase extends AbstractITCase {
@@ -447,6 +458,107 @@ public class SearchITCase extends AbstractITCase {
}
@Test
+ public void searchConnObjectsBrowsePagedResult() {
+ List<String> groupKeys = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ GroupTO group = GroupITCase.getSampleTO("group");
+ group.getResources().add(RESOURCE_NAME_LDAP);
+ group = createGroup(group).getEntity();
+ groupKeys.add(group.getKey());
+ }
+
+ int totalRead = 0;
+ Set<String> read = new HashSet<>();
+ try {
+ // 1. first search with no filters
+ ConnObjectTOQuery.Builder builder = new ConnObjectTOQuery.Builder().size(10);
+ PagedConnObjectTOResult matches;
+ do {
+ matches = null;
+
+ boolean succeeded = false;
+ // needed because ApacheDS seems to randomly fail when searching with cookie
+ for (int i = 0; i < 5 && !succeeded; i++) {
+ try {
+ matches = resourceService.searchConnObjects(
+ RESOURCE_NAME_LDAP,
+ AnyTypeKind.GROUP.name(),
+ builder.build());
+ succeeded = true;
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.ConnectorException, e.getType());
+ }
+ }
+ assertNotNull(matches);
+
+ totalRead += matches.getResult().size();
+ read.addAll(matches.getResult().stream().
+ map(input -> input.getAttr(ConnIdSpecialName.NAME).get().getValues().get(0)).
+ collect(Collectors.toList()));
+
+ if (matches.getPagedResultsCookie() != null) {
+ builder.pagedResultsCookie(matches.getPagedResultsCookie());
+ }
+ } while (matches.getPagedResultsCookie() != null);
+
+ assertEquals(totalRead, read.size());
+ assertTrue(totalRead >= 10);
+ } finally {
+ groupKeys.forEach(key -> {
+ groupService.delete(key);
+ });
+ }
+ }
+
+ @Test
+ public void searchConnObjectsWithFilter() {
+ ConnObjectTO user = resourceService.readConnObject(RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), "pullFromLDAP");
+ assertNotNull(user);
+
+ PagedConnObjectTOResult matches = resourceService.searchConnObjects(
+ RESOURCE_NAME_LDAP,
+ AnyTypeKind.USER.name(),
+ new ConnObjectTOQuery.Builder().size(100).fiql(new ConnObjectTOFiqlSearchConditionBuilder().
+ is("givenName").equalTo("pullFromLDAP").query()).build());
+ assertTrue(matches.getResult().contains(user));
+
+ matches = resourceService.searchConnObjects(
+ RESOURCE_NAME_LDAP,
+ AnyTypeKind.USER.name(),
+ new ConnObjectTOQuery.Builder().size(100).fiql(new ConnObjectTOFiqlSearchConditionBuilder().
+ is("mail").equalTo("pullFromLDAP*").query()).build());
+ assertTrue(matches.getResult().contains(user));
+
+ matches = resourceService.searchConnObjects(
+ RESOURCE_NAME_LDAP,
+ AnyTypeKind.USER.name(),
+ new ConnObjectTOQuery.Builder().size(100).fiql(new ConnObjectTOFiqlSearchConditionBuilder().
+ is("mail").equalTo("*@syncope.apache.org").query()).build());
+ assertTrue(matches.getResult().contains(user));
+
+ matches = resourceService.searchConnObjects(
+ RESOURCE_NAME_LDAP,
+ AnyTypeKind.USER.name(),
+ new ConnObjectTOQuery.Builder().size(100).fiql(new ConnObjectTOFiqlSearchConditionBuilder().
+ is("givenName").equalToIgnoreCase("pullfromldap").query()).build());
+ assertTrue(matches.getResult().contains(user));
+
+ matches = resourceService.searchConnObjects(
+ RESOURCE_NAME_LDAP,
+ AnyTypeKind.USER.name(),
+ new ConnObjectTOQuery.Builder().size(100).fiql(new ConnObjectTOFiqlSearchConditionBuilder().
+ is(Name.NAME).equalTo("uid=pullFromLDAP%252Cou=people%252Co=isp").query()).build());
+ assertTrue(matches.getResult().contains(user));
+
+ matches = resourceService.searchConnObjects(
+ RESOURCE_NAME_LDAP,
+ AnyTypeKind.USER.name(),
+ new ConnObjectTOQuery.Builder().size(100).fiql(new ConnObjectTOFiqlSearchConditionBuilder().
+ is("givenName").notEqualTo("pullFromLDAP").query()).build());
+ assertFalse(matches.getResult().contains(user));
+ }
+
+ @Test
public void issueSYNCOPE768() {
int usersWithNullable = userService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
fiql(SyncopeClient.getUserSearchConditionBuilder().is("ctype").nullValue().query()).build()).