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 2017/11/06 12:57:22 UTC

[3/6] syncope git commit: [SYNCOPE-152] SCIM filter

[SYNCOPE-152] SCIM filter


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/1c469ae1
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/1c469ae1
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/1c469ae1

Branch: refs/heads/2_0_X
Commit: 1c469ae175d50acd88282b6bb7081e453157bb6f
Parents: 99a9f1a
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Mon Nov 6 13:50:09 2017 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Mon Nov 6 13:50:09 2017 +0100

----------------------------------------------------------------------
 ext/scimv2/logic/pom.xml                        |  27 +++
 .../syncope/core/logic/scim/SCIMFilter.g4       |  77 ++++++++
 .../core/logic/scim/SCIMFilterErrorHandler.java |  42 +++++
 .../core/logic/scim/SearchCondConverter.java    |  56 ++++++
 .../core/logic/scim/SearchCondVisitor.java      | 174 +++++++++++++++++++
 .../syncope/core/logic/scim/SCIMFilterTest.java | 125 +++++++++++++
 .../syncope/ext/scimv2/api/data/Group.java      |   2 +-
 .../syncope/ext/scimv2/api/data/Member.java     |   2 +-
 .../syncope/ext/scimv2/api/data/SCIMError.java  |   5 +
 .../ext/scimv2/cxf/SCIMExceptionMapper.java     |  10 +-
 .../scimv2/cxf/service/AbstractSCIMService.java |   7 +-
 .../org/apache/syncope/fit/core/SCIMITCase.java |  56 ++++++
 pom.xml                                         |  14 ++
 13 files changed, 587 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/logic/pom.xml
----------------------------------------------------------------------
diff --git a/ext/scimv2/logic/pom.xml b/ext/scimv2/logic/pom.xml
index b127fb8..188d71e 100644
--- a/ext/scimv2/logic/pom.xml
+++ b/ext/scimv2/logic/pom.xml
@@ -35,6 +35,7 @@ under the License.
   
   <properties>
     <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
+    <antlr4.visitor>true</antlr4.visitor>
   </properties>
 
   <dependencies>
@@ -49,6 +50,17 @@ under the License.
       <artifactId>syncope-ext-scimv2-scim-rest-api</artifactId>
       <version>${project.version}</version>
     </dependency>
+    
+    <dependency>
+      <groupId>org.antlr</groupId>
+      <artifactId>antlr4-runtime</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
@@ -56,6 +68,21 @@ under the License.
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
+        <configuration>
+          <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
+        </configuration>
+      </plugin>
+      
+      <plugin>
+        <groupId>org.antlr</groupId>
+        <artifactId>antlr4-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>antlr4</goal>
+            </goals>
+          </execution>
+        </executions>
       </plugin>
     </plugins>
   </build>

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/logic/src/main/antlr4/org/apache/syncope/core/logic/scim/SCIMFilter.g4
----------------------------------------------------------------------
diff --git a/ext/scimv2/logic/src/main/antlr4/org/apache/syncope/core/logic/scim/SCIMFilter.g4 b/ext/scimv2/logic/src/main/antlr4/org/apache/syncope/core/logic/scim/SCIMFilter.g4
new file mode 100644
index 0000000..ffddf32
--- /dev/null
+++ b/ext/scimv2/logic/src/main/antlr4/org/apache/syncope/core/logic/scim/SCIMFilter.g4
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+grammar SCIMFilter;
+
+options
+{
+  language = Java;
+}
+
+scimFilter
+ : expression* EOF
+ ;
+
+expression
+ : NOT WS+? expression                        # NOT_EXPR
+ | expression WS+? AND WS+? expression        # EXPR_AND_EXPR
+ | expression WS+? OR WS+ expression          # EXPR_OR_EXPR
+ | expression WS+? operator WS+? expression   # EXPR_OPER_EXPR
+ | ATTRNAME WS+? PR                           # ATTR_PR
+ | ATTRNAME WS+? operator WS+? expression     # ATTR_OPER_EXPR
+ | ATTRNAME WS+? operator WS+? criteria       # ATTR_OPER_CRITERIA
+ | LPAREN WS*? expression WS*? RPAREN         # LPAREN_EXPR_RPAREN
+ | ATTRNAME LBRAC WS*? expression WS*? RBRAC  # LBRAC_EXPR_RBRAC
+ ;
+
+criteria : '"' .+? '"';
+
+operator
+ : EQ | NE | CO | SW | EW | GT | LT | GE | LE
+ ;
+
+EQ : [eE][qQ];
+NE : [nN][eE];
+CO : [cC][oO];
+SW : [sS][wW];
+EW : [eE][wW];
+GT : [gG][tT];
+LT : [lL][tT];
+GE : [gG][eE];
+LE : [lL][eE];
+
+NOT : [nN][oO][tT];
+
+AND : [aA][nN][dD];
+OR  : [oO][rR];
+
+PR : [pP][rR];
+
+LPAREN : '(';
+RPAREN : ')';
+
+LBRAC : '[';
+RBRAC : ']';
+
+WS : ' ';
+
+ATTRNAME : [-_.:a-zA-Z0-9]+;
+
+ANY : ~('"' | '(' | ')' | '[' | ']');
+
+EOL : [\t\r\n\u000C]+ -> skip;

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SCIMFilterErrorHandler.java
----------------------------------------------------------------------
diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SCIMFilterErrorHandler.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SCIMFilterErrorHandler.java
new file mode 100644
index 0000000..db2bfbd
--- /dev/null
+++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SCIMFilterErrorHandler.java
@@ -0,0 +1,42 @@
+/*
+ * 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.logic.scim;
+
+import org.antlr.v4.runtime.DefaultErrorStrategy;
+import org.antlr.v4.runtime.InputMismatchException;
+import org.antlr.v4.runtime.Parser;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Token;
+
+public class SCIMFilterErrorHandler extends DefaultErrorStrategy {
+
+    @Override
+    public void recover(final Parser recognizer, final RecognitionException e) {
+        throw e;
+    }
+
+    @Override
+    public Token recoverInline(final Parser recognizer) throws RecognitionException {
+        throw new InputMismatchException(recognizer);
+    }
+
+    @Override
+    public void sync(final Parser recognizer) throws RecognitionException {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondConverter.java
----------------------------------------------------------------------
diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondConverter.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondConverter.java
new file mode 100644
index 0000000..fd45f43
--- /dev/null
+++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondConverter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.logic.scim;
+
+import org.antlr.v4.runtime.CharStreams;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.ext.scimv2.api.SCIMBadRequestException;
+import org.apache.syncope.ext.scimv2.api.type.ErrorType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Converts SCIM filter expressions to Syncope's {@link SearchCond}.
+ */
+public final class SearchCondConverter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SearchCondConverter.class);
+
+    public static SearchCond convert(final String filter) {
+        SCIMFilterParser parser = new SCIMFilterParser(new CommonTokenStream(
+                new SCIMFilterLexer(CharStreams.fromString(filter))));
+        parser.setBuildParseTree(true);
+        parser.setTrimParseTree(true);
+        parser.setProfile(true);
+        parser.removeErrorListeners();
+        parser.setErrorHandler(new SCIMFilterErrorHandler());
+
+        try {
+            return new SearchCondVisitor().visit(parser.scimFilter());
+        } catch (Exception e) {
+            LOG.error("Could not parse {}", filter, e);
+            throw new SCIMBadRequestException(ErrorType.invalidFilter, e.getMessage());
+        }
+    }
+
+    private SearchCondConverter() {
+        // empty constructor for static utility class        
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
----------------------------------------------------------------------
diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
new file mode 100644
index 0000000..1882505
--- /dev/null
+++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
@@ -0,0 +1,174 @@
+/*
+ * 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.logic.scim;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.ext.scimv2.api.type.Resource;
+
+/**
+ * Visits SCIM filter expression and produces {@link SearchCond}.
+ */
+public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
+
+    @Override
+    public SearchCond visitScimFilter(final SCIMFilterParser.ScimFilterContext ctx) {
+        return visit(ctx.expression(0));
+    }
+
+    private AttributeCond createAttributeCond(final String schema) {
+        AttributeCond attributeCond;
+        if ("userName".equalsIgnoreCase(schema)
+                || (Resource.User.schema() + ":userName").equalsIgnoreCase(schema)) {
+
+            attributeCond = new AnyCond();
+            attributeCond.setSchema("username");
+        } else if ("displayName".equalsIgnoreCase(schema)
+                || (Resource.Group.schema() + ":displayName").equalsIgnoreCase(schema)) {
+
+            attributeCond = new AnyCond();
+            attributeCond.setSchema("name");
+        } else if ("meta.created".equals(schema)) {
+            attributeCond = new AnyCond();
+            attributeCond.setSchema("creationDate");
+        } else if ("meta.lastModified".equals(schema)) {
+            attributeCond = new AnyCond();
+            attributeCond.setSchema("lastChangeDate");
+        } else {
+            attributeCond = new AttributeCond();
+            attributeCond.setSchema(schema);
+        }
+
+        return attributeCond;
+    }
+
+    private SearchCond transform(final String operator, final String left, final String right) {
+        AttributeCond attributeCond = createAttributeCond(left);
+        attributeCond.setExpression(StringUtils.strip(right, "\""));
+
+        switch (operator) {
+            case "eq":
+            default:
+                attributeCond.setType(AttributeCond.Type.IEQ);
+                break;
+
+            case "ne":
+                attributeCond.setType(AttributeCond.Type.IEQ);
+                break;
+
+            case "sw":
+                attributeCond.setType(AttributeCond.Type.ILIKE);
+                attributeCond.setExpression(attributeCond.getExpression() + "%");
+                break;
+
+            case "co":
+                attributeCond.setType(AttributeCond.Type.ILIKE);
+                attributeCond.setExpression("%" + attributeCond.getExpression() + "%");
+                break;
+
+            case "ew":
+                attributeCond.setType(AttributeCond.Type.ILIKE);
+                attributeCond.setExpression("%" + attributeCond.getExpression());
+                break;
+
+            case "gt":
+                attributeCond.setType(AttributeCond.Type.GT);
+                break;
+
+            case "ge":
+                attributeCond.setType(AttributeCond.Type.GE);
+                break;
+
+            case "lt":
+                attributeCond.setType(AttributeCond.Type.LT);
+                break;
+
+            case "le":
+                attributeCond.setType(AttributeCond.Type.LE);
+                break;
+
+        }
+
+        return "ne".equals(operator)
+                ? SearchCond.getNotLeafCond(attributeCond)
+                : SearchCond.getLeafCond(attributeCond);
+    }
+
+    @Override
+    public SearchCond visitEXPR_OPER_EXPR(final SCIMFilterParser.EXPR_OPER_EXPRContext ctx) {
+        return transform(ctx.operator().getText(), ctx.expression(0).getText(), ctx.expression(1).getText());
+    }
+
+    @Override
+    public SearchCond visitATTR_OPER_CRITERIA(final SCIMFilterParser.ATTR_OPER_CRITERIAContext ctx) {
+        return transform(ctx.operator().getText(), ctx.ATTRNAME().getText(), ctx.criteria().getText());
+    }
+
+    @Override
+    public SearchCond visitATTR_OPER_EXPR(final SCIMFilterParser.ATTR_OPER_EXPRContext ctx) {
+        return transform(ctx.operator().getText(), ctx.ATTRNAME().getText(), ctx.expression().getText());
+    }
+
+    @Override
+    public SearchCond visitATTR_PR(final SCIMFilterParser.ATTR_PRContext ctx) {
+        AttributeCond cond = createAttributeCond(ctx.ATTRNAME().getText());
+        cond.setType(AttributeCond.Type.ISNOTNULL);
+        return SearchCond.getLeafCond(cond);
+    }
+
+    @Override
+    public SearchCond visitLPAREN_EXPR_RPAREN(final SCIMFilterParser.LPAREN_EXPR_RPARENContext ctx) {
+        return visit(ctx.expression());
+    }
+
+    @Override
+    public SearchCond visitNOT_EXPR(final SCIMFilterParser.NOT_EXPRContext ctx) {
+        SearchCond cond = visit(ctx.expression());
+        if (cond.getAttributeCond() != null) {
+            if (cond.getAttributeCond().getType() == AttributeCond.Type.ISNULL) {
+                cond.getAttributeCond().setType(AttributeCond.Type.ISNOTNULL);
+            } else if (cond.getAttributeCond().getType() == AttributeCond.Type.ISNOTNULL) {
+                cond.getAttributeCond().setType(AttributeCond.Type.ISNULL);
+            }
+        } else if (cond.getAnyCond() != null) {
+            if (cond.getAnyCond().getType() == AnyCond.Type.ISNULL) {
+                cond.getAnyCond().setType(AnyCond.Type.ISNOTNULL);
+            } else if (cond.getAnyCond().getType() == AnyCond.Type.ISNOTNULL) {
+                cond.getAnyCond().setType(AnyCond.Type.ISNULL);
+            }
+        } else {
+            cond = SearchCond.getNotLeafCond(cond);
+        }
+
+        return cond;
+    }
+
+    @Override
+    public SearchCond visitEXPR_AND_EXPR(final SCIMFilterParser.EXPR_AND_EXPRContext ctx) {
+        return SearchCond.getAndCond(visit(ctx.expression(0)), visit(ctx.expression(1)));
+    }
+
+    @Override
+    public SearchCond visitEXPR_OR_EXPR(final SCIMFilterParser.EXPR_OR_EXPRContext ctx) {
+        return SearchCond.getOrCond(visit(ctx.expression(0)), visit(ctx.expression(1)));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java
----------------------------------------------------------------------
diff --git a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java
new file mode 100644
index 0000000..08dbf1d
--- /dev/null
+++ b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.logic.scim;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.junit.Test;
+
+public class SCIMFilterTest {
+
+    @Test
+    public void eq() {
+        SearchCond cond = SearchCondConverter.convert("userName eq \"bjensen\"");
+        assertNotNull(cond);
+        assertNotNull(cond.getAnyCond());
+        assertEquals("username", cond.getAnyCond().getSchema());
+        assertEquals(AttributeCond.Type.IEQ, cond.getAnyCond().getType());
+        assertEquals("bjensen", cond.getAnyCond().getExpression());
+    }
+
+    @Test
+    public void sw() {
+        SearchCond cond = SearchCondConverter.convert("userName sw \"J\"");
+        assertNotNull(cond);
+        assertNotNull(cond.getAnyCond());
+        assertEquals("username", cond.getAnyCond().getSchema());
+        assertEquals(AttributeCond.Type.ILIKE, cond.getAnyCond().getType());
+        assertEquals("J%", cond.getAnyCond().getExpression());
+
+        SearchCond fqn = SearchCondConverter.convert("urn:ietf:params:scim:schemas:core:2.0:User:userName sw \"J\"");
+        assertEquals(cond, fqn);
+    }
+
+    @Test
+    public void pr() {
+        SearchCond cond = SearchCondConverter.convert("title pr");
+        assertNotNull(cond);
+        assertNotNull(cond.getAttributeCond());
+        assertEquals("title", cond.getAttributeCond().getSchema());
+        assertEquals(AttributeCond.Type.ISNOTNULL, cond.getAttributeCond().getType());
+        assertNull(cond.getAttributeCond().getExpression());
+    }
+
+    @Test
+    public void gt() {
+        SearchCond cond = SearchCondConverter.convert("meta.lastModified gt \"2011-05-13T04:42:34Z\"");
+        assertNotNull(cond);
+        assertNotNull(cond.getAnyCond());
+        assertEquals("lastChangeDate", cond.getAnyCond().getSchema());
+        assertEquals(AttributeCond.Type.GT, cond.getAnyCond().getType());
+        assertEquals("2011-05-13T04:42:34Z", cond.getAnyCond().getExpression());
+    }
+
+    @Test
+    public void not() {
+        SearchCond cond = SearchCondConverter.convert("not (title pr)");
+        assertNotNull(cond);
+        assertNotNull(cond.getAttributeCond());
+        assertEquals("title", cond.getAttributeCond().getSchema());
+        assertEquals(AttributeCond.Type.ISNULL, cond.getAttributeCond().getType());
+        assertNull(cond.getAttributeCond().getExpression());
+    }
+
+    @Test
+    public void and() {
+        SearchCond cond = SearchCondConverter.convert("title pr and userName sw \"J\"");
+        assertNotNull(cond);
+        assertEquals(SearchCond.Type.AND, cond.getType());
+
+        SearchCond left = cond.getLeftSearchCond();
+        assertNotNull(left);
+        assertNotNull(left.getAttributeCond());
+        assertEquals("title", left.getAttributeCond().getSchema());
+        assertEquals(AttributeCond.Type.ISNOTNULL, left.getAttributeCond().getType());
+        assertNull(left.getAttributeCond().getExpression());
+
+        SearchCond right = cond.getRightSearchCond();
+        assertNotNull(right);
+        assertNotNull(right.getAnyCond());
+        assertEquals("username", right.getAnyCond().getSchema());
+        assertEquals(AttributeCond.Type.ILIKE, right.getAnyCond().getType());
+        assertEquals("J%", right.getAnyCond().getExpression());
+    }
+
+    @Test
+    public void or() {
+        SearchCond cond = SearchCondConverter.convert("title pr or displayName eq \"Other\"");
+        assertNotNull(cond);
+        assertEquals(SearchCond.Type.OR, cond.getType());
+
+        SearchCond left = cond.getLeftSearchCond();
+        assertNotNull(left);
+        assertNotNull(left.getAttributeCond());
+        assertEquals("title", left.getAttributeCond().getSchema());
+        assertEquals(AttributeCond.Type.ISNOTNULL, left.getAttributeCond().getType());
+        assertNull(left.getAttributeCond().getExpression());
+
+        SearchCond right = cond.getRightSearchCond();
+        assertNotNull(right);
+        assertNotNull(right.getAnyCond());
+        assertEquals("name", right.getAnyCond().getSchema());
+        assertEquals(AttributeCond.Type.IEQ, right.getAnyCond().getType());
+        assertEquals("Other", right.getAnyCond().getExpression());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Group.java
----------------------------------------------------------------------
diff --git a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Group.java b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Group.java
index f64b837..f84d559 100644
--- a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Group.java
+++ b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Group.java
@@ -39,7 +39,7 @@ public class Group extends Reference {
             @JsonProperty("display") final String display,
             @JsonProperty("type") final Function type) {
 
-        super(value, ref, display);
+        super(value, display, ref);
         this.type = type;
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Member.java
----------------------------------------------------------------------
diff --git a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Member.java b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Member.java
index aa2f5dd..902217d 100644
--- a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Member.java
+++ b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Member.java
@@ -37,7 +37,7 @@ public class Member extends Reference {
             @JsonProperty("display") final String display,
             @JsonProperty("type") final Resource type) {
 
-        super(value, ref, display);
+        super(value, display, ref);
         this.type = type;
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMError.java
----------------------------------------------------------------------
diff --git a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMError.java b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMError.java
index d7112d1..f0548c4 100644
--- a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMError.java
+++ b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMError.java
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonFormat.Shape;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.Arrays;
 import java.util.List;
+import org.apache.syncope.ext.scimv2.api.SCIMBadRequestException;
 import org.apache.syncope.ext.scimv2.api.type.ErrorType;
 import org.apache.syncope.ext.scimv2.api.type.Resource;
 
@@ -40,6 +41,10 @@ public class SCIMError extends SCIMBean {
     @JsonFormat(shape = Shape.STRING)
     private final int status = 400;
 
+    public SCIMError(final SCIMBadRequestException ex) {
+        this(ex.getErrorType(), ex.getMessage());
+    }
+
     @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
     public SCIMError(
             @JsonProperty("scimType") final ErrorType scimType,

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/SCIMExceptionMapper.java
----------------------------------------------------------------------
diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/SCIMExceptionMapper.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/SCIMExceptionMapper.java
index e525e77..4525d82 100644
--- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/SCIMExceptionMapper.java
+++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/SCIMExceptionMapper.java
@@ -41,6 +41,7 @@ import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.apache.syncope.core.workflow.api.WorkflowException;
 import org.apache.syncope.ext.scimv2.api.ConflictException;
 import org.apache.syncope.ext.scimv2.api.PayloadTooLargeException;
+import org.apache.syncope.ext.scimv2.api.SCIMBadRequestException;
 import org.apache.syncope.ext.scimv2.api.data.SCIMError;
 import org.apache.syncope.ext.scimv2.api.type.ErrorType;
 import org.identityconnectors.framework.common.exceptions.ConfigurationException;
@@ -108,9 +109,7 @@ public class SCIMExceptionMapper implements ExceptionMapper<Exception> {
                 && ENTITYEXISTS_EXCLASS.isAssignableFrom(ex.getCause().getClass())) {
 
             builder = builder(ClientExceptionType.EntityExists, ExceptionUtils.getRootCauseMessage(ex));
-        } else if (ex instanceof DataIntegrityViolationException
-                || JPASYSTEM_EXCLASS.isAssignableFrom(ex.getClass())) {
-
+        } else if (ex instanceof DataIntegrityViolationException || JPASYSTEM_EXCLASS.isAssignableFrom(ex.getClass())) {
             builder = builder(ClientExceptionType.DataIntegrityViolation, ExceptionUtils.getRootCauseMessage(ex));
         } else if (CONNECTOR_EXCLASS.isAssignableFrom(ex.getClass())) {
             builder = builder(ClientExceptionType.ConnectorException, ExceptionUtils.getRootCauseMessage(ex));
@@ -125,7 +124,8 @@ public class SCIMExceptionMapper implements ExceptionMapper<Exception> {
             }
             // ...or just report as InternalServerError
             if (builder == null) {
-                builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR);
+                builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR).
+                        entity(ExceptionUtils.getRootCauseMessage(ex));
             }
         }
 
@@ -188,6 +188,8 @@ public class SCIMExceptionMapper implements ExceptionMapper<Exception> {
             return builder(ClientExceptionType.InvalidValues, ExceptionUtils.getRootCauseMessage(ex));
         } else if (ex instanceof MalformedPathException) {
             return builder(ClientExceptionType.InvalidPath, ExceptionUtils.getRootCauseMessage(ex));
+        } else if (ex instanceof SCIMBadRequestException) {
+            return Response.status(Response.Status.BAD_REQUEST).entity(new SCIMError((SCIMBadRequestException) ex));
         }
 
         return null;

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractSCIMService.java
----------------------------------------------------------------------
diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractSCIMService.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractSCIMService.java
index 41703a2..f2361d2 100644
--- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractSCIMService.java
+++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractSCIMService.java
@@ -33,6 +33,7 @@ import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.core.logic.AbstractAnyLogic;
 import org.apache.syncope.core.logic.GroupLogic;
 import org.apache.syncope.core.logic.UserLogic;
+import org.apache.syncope.core.logic.scim.SearchCondConverter;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
@@ -190,11 +191,9 @@ abstract class AbstractSCIMService<R extends SCIMResource> implements SCIMServic
             throw new UnsupportedOperationException();
         }
 
-        int page = startIndex == null || startIndex <= 1 ? 1 : (startIndex / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
-
         Pair<Integer, ? extends List<? extends AnyTO>> result = anyLogic(type).search(
-                null,
-                page,
+                StringUtils.isBlank(filter) ? null : SearchCondConverter.convert(filter),
+                startIndex == null || startIndex <= 1 ? 1 : (startIndex / AnyDAO.DEFAULT_PAGE_SIZE) + 1,
                 AnyDAO.DEFAULT_PAGE_SIZE,
                 Collections.<OrderByClause>emptyList(),
                 SyncopeConstants.ROOT_REALM,

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
index 2031da8..d7e19a2 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
@@ -26,13 +26,18 @@ import static org.junit.Assert.assertTrue;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import java.io.IOException;
+import java.text.SimpleDateFormat;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.ext.scimv2.api.SCIMConstants;
 import org.apache.syncope.ext.scimv2.api.data.ListResponse;
 import org.apache.syncope.ext.scimv2.api.data.ResourceType;
@@ -50,6 +55,16 @@ public class SCIMITCase extends AbstractITCase {
 
     public static final String SCIM_ADDRESS = "http://localhost:9080/syncope/scim/v2";
 
+    private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
+
+        @Override
+        protected SimpleDateFormat initialValue() {
+            SimpleDateFormat sdf = new SimpleDateFormat();
+            sdf.applyPattern(SyncopeConstants.DEFAULT_DATE_PATTERN);
+            return sdf;
+        }
+    };
+
     private WebClient webClient() {
         return WebClient.create(SCIM_ADDRESS, Arrays.asList(new JacksonSCIMJsonProvider())).
                 accept(SCIMConstants.APPLICATION_SCIM_JSON_TYPE).
@@ -159,4 +174,45 @@ public class SCIMITCase extends AbstractITCase {
             assertNotNull(group.getDisplayName());
         }
     }
+
+    @Test
+    public void search() {
+        Assume.assumeTrue(SCIMDetector.isSCIMAvailable(webClient()));
+
+        // eq
+        Response response = webClient().path("Groups").query("filter", "displayName eq \"additional\"").get();
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+        assertEquals(
+                SCIMConstants.APPLICATION_SCIM_JSON,
+                StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";"));
+
+        ListResponse<SCIMGroup> groups = response.readEntity(new GenericType<ListResponse<SCIMGroup>>() {
+        });
+        assertNotNull(groups);
+        assertEquals(1, groups.getTotalResults());
+
+        SCIMGroup additional = groups.getResources().get(0);
+        assertEquals("additional", additional.getDisplayName());
+
+        // gt
+        UserTO newUser = userService.create(UserITCase.getUniqueSampleTO("scimsearch@syncope.apache.org")).readEntity(
+                new GenericType<ProvisioningResult<UserTO>>() {
+        }).getEntity();
+
+        Date value = new Date(newUser.getCreationDate().getTime() - 1000);
+        response = webClient().path("Users").query("filter", "meta.created gt \""
+                + DATE_FORMAT.get().format(value) + "\"").get();
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+        assertEquals(
+                SCIMConstants.APPLICATION_SCIM_JSON,
+                StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";"));
+
+        ListResponse<SCIMUser> users = response.readEntity(new GenericType<ListResponse<SCIMUser>>() {
+        });
+        assertNotNull(users);
+        assertEquals(1, users.getTotalResults());
+
+        SCIMUser newSCIMUser = users.getResources().get(0);
+        assertEquals(newUser.getUsername(), newSCIMUser.getUserName());
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/1c469ae1/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 9d96234..c3133e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -466,6 +466,8 @@ under the License.
     <tycho.version>0.23.1</tycho.version>
     <netbeans.version>RELEASE82</netbeans.version>
 
+    <antlr4.version>4.7</antlr4.version>
+
     <testds.port>1389</testds.port>
     <testdb.webport>9082</testdb.webport>
 
@@ -1656,6 +1658,12 @@ under the License.
         <version>${netbeans.version}</version>
       </dependency>
 
+      <dependency>
+        <groupId>org.antlr</groupId>
+        <artifactId>antlr4-runtime</artifactId>
+        <version>${antlr4.version}</version>
+      </dependency>
+
       <!-- TEST -->
       <dependency>
         <groupId>com.github.detro</groupId>
@@ -1986,6 +1994,12 @@ under the License.
           <artifactId>exec-maven-plugin</artifactId>
           <version>1.6.0</version>
         </plugin>
+        
+        <plugin>
+          <groupId>org.antlr</groupId>
+          <artifactId>antlr4-maven-plugin</artifactId>
+          <version>${antlr4.version}</version>
+        </plugin>
       </plugins>
     </pluginManagement>