View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.logic.scim;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Optional;
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.syncope.common.lib.scim.SCIMComplexConf;
27  import org.apache.syncope.common.lib.scim.SCIMConf;
28  import org.apache.syncope.common.lib.scim.SCIMUserAddressConf;
29  import org.apache.syncope.common.lib.scim.SCIMUserConf;
30  import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
31  import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
32  import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
33  import org.apache.syncope.ext.scimv2.api.type.Resource;
34  
35  /**
36   * Visits SCIM filter expression and produces {@link SearchCond}.
37   */
38  public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
39  
40      private static final List<String> MULTIVALUE = List.of(
41              "emails", "phoneNumbers", "ims", "photos", "addresses");
42  
43      private final Resource resource;
44  
45      private final SCIMConf conf;
46  
47      public SearchCondVisitor(final Resource resource, final SCIMConf conf) {
48          this.resource = resource;
49          this.conf = conf;
50      }
51  
52      @Override
53      public SearchCond visitScimFilter(final SCIMFilterParser.ScimFilterContext ctx) {
54          return visit(ctx.expression(0));
55      }
56  
57      private static boolean schemaEquals(final Resource resource, final String value, final String schema) {
58          return resource == null
59                  ? value.contains(":")
60                  ? StringUtils.substringAfterLast(value, ":").equalsIgnoreCase(schema)
61                  : value.equalsIgnoreCase(schema)
62                  : value.equalsIgnoreCase(schema) || (resource.schema() + ":" + value).equalsIgnoreCase(schema);
63      }
64  
65      public AttrCond createAttrCond(final String schema) {
66          AttrCond attrCond = null;
67  
68          if (schemaEquals(Resource.User, "userName", schema)) {
69              attrCond = new AnyCond();
70              attrCond.setSchema("username");
71          } else if (resource == Resource.Group && schemaEquals(Resource.Group, "displayName", schema)) {
72              attrCond = new AnyCond();
73              attrCond.setSchema("name");
74          } else if (schemaEquals(null, "meta.created", schema)) {
75              attrCond = new AnyCond();
76              attrCond.setSchema("creationDate");
77          } else if (schemaEquals(null, "meta.lastModified", schema)) {
78              attrCond = new AnyCond();
79              attrCond.setSchema("lastChangeDate");
80          }
81  
82          switch (resource) {
83              case User:
84                  if (conf.getUserConf() != null) {
85                      if (conf.getUserConf().getName() != null) {
86                          for (Map.Entry<String, String> entry : conf.getUserConf().getName().asMap().entrySet()) {
87                              if (schemaEquals(Resource.User, "name." + entry.getKey(), schema)) {
88                                  attrCond = new AttrCond();
89                                  attrCond.setSchema(entry.getValue());
90                              }
91                          }
92                      }
93  
94                      for (Map.Entry<String, String> entry : conf.getUserConf().asMap().entrySet()) {
95                          if (schemaEquals(Resource.User, entry.getKey(), schema)) {
96                              attrCond = new AttrCond();
97                              attrCond.setSchema(entry.getValue());
98                          }
99                      }
100 
101                     for (SCIMUserAddressConf address : conf.getUserConf().getAddresses()) {
102                         for (Map.Entry<String, String> entry : address.asMap().entrySet()) {
103                             if (schemaEquals(Resource.User, "addresses." + entry.getKey(), schema)) {
104                                 attrCond = new AttrCond();
105                                 attrCond.setSchema(entry.getValue());
106                             }
107                         }
108                     }
109                 }
110 
111                 if (conf.getEnterpriseUserConf() != null) {
112                     for (Map.Entry<String, String> entry : conf.getEnterpriseUserConf().asMap().entrySet()) {
113                         if (schemaEquals(Resource.EnterpriseUser, entry.getKey(), schema)) {
114                             attrCond = new AttrCond();
115                             attrCond.setSchema(entry.getValue());
116                         }
117                     }
118 
119                     if (conf.getEnterpriseUserConf().getManager() != null
120                             && conf.getEnterpriseUserConf().getManager().getKey() != null) {
121 
122                         attrCond = new AttrCond();
123                         attrCond.setSchema(conf.getEnterpriseUserConf().getManager().getKey());
124                     }
125                 }
126                 break;
127 
128             case Group:
129                 if (conf.getGroupConf() != null) {
130                     for (Map.Entry<String, String> entry : conf.getGroupConf().asMap().entrySet()) {
131                         if (schemaEquals(Resource.Group, entry.getKey(), schema)) {
132                             attrCond = new AttrCond();
133                             attrCond.setSchema(entry.getValue());
134                         }
135                     }
136                 }
137                 break;
138 
139             default:
140         }
141 
142         if (attrCond == null) {
143             throw new IllegalArgumentException("Could not match " + schema + " for " + resource);
144         }
145 
146         return attrCond;
147     }
148 
149     private static SearchCond setOperator(final AttrCond attrCond, final String operator) {
150         switch (operator) {
151             case "eq":
152             default:
153                 attrCond.setType(AttrCond.Type.IEQ);
154                 break;
155 
156             case "ne":
157                 attrCond.setType(AttrCond.Type.IEQ);
158                 break;
159 
160             case "sw":
161                 attrCond.setType(AttrCond.Type.ILIKE);
162                 attrCond.setExpression(attrCond.getExpression() + "%");
163                 break;
164 
165             case "co":
166                 attrCond.setType(AttrCond.Type.ILIKE);
167                 attrCond.setExpression("%" + attrCond.getExpression() + "%");
168                 break;
169 
170             case "ew":
171                 attrCond.setType(AttrCond.Type.ILIKE);
172                 attrCond.setExpression("%" + attrCond.getExpression());
173                 break;
174 
175             case "gt":
176                 attrCond.setType(AttrCond.Type.GT);
177                 break;
178 
179             case "ge":
180                 attrCond.setType(AttrCond.Type.GE);
181                 break;
182 
183             case "lt":
184                 attrCond.setType(AttrCond.Type.LT);
185                 break;
186 
187             case "le":
188                 attrCond.setType(AttrCond.Type.LE);
189                 break;
190         }
191 
192         return "ne".equals(operator)
193                 ? SearchCond.getNotLeaf(attrCond)
194                 : SearchCond.getLeaf(attrCond);
195     }
196 
197     private <E extends Enum<?>> SearchCond complex(
198             final String operator, final String left, final String right, final List<SCIMComplexConf<E>> items) {
199 
200         if (left.endsWith(".type")) {
201             Optional<SCIMComplexConf<E>> item = items.stream().
202                     filter(object -> object.getType().name().equals(StringUtils.strip(right, "\""))).findFirst();
203             if (item.isPresent()) {
204                 AttrCond attrCond = new AttrCond();
205                 attrCond.setSchema(item.get().getValue());
206                 attrCond.setType(AttrCond.Type.ISNOTNULL);
207                 return SearchCond.getLeaf(attrCond);
208             }
209         } else if (!conf.getUserConf().getEmails().isEmpty()
210                 && (MULTIVALUE.contains(left) || left.endsWith(".value"))) {
211 
212             List<SearchCond> orConds = new ArrayList<>();
213             items.forEach(item -> {
214                 AttrCond cond = new AttrCond();
215                 cond.setSchema(item.getValue());
216                 cond.setExpression(StringUtils.strip(right, "\""));
217                 orConds.add(setOperator(cond, operator));
218             });
219             if (!orConds.isEmpty()) {
220                 return SearchCond.getOr(orConds);
221             }
222         }
223 
224         return null;
225     }
226 
227     private SearchCond addresses(
228             final String operator, final String left, final String right, final List<SCIMUserAddressConf> items) {
229 
230         if (left.endsWith(".type") && "eq".equals(operator)) {
231             Optional<SCIMUserAddressConf> item = items.stream().
232                     filter(object -> object.getType().name().equals(StringUtils.strip(right, "\""))).findFirst();
233             if (item.isPresent()) {
234                 AttrCond attrCond = new AttrCond();
235                 attrCond.setSchema(item.get().getFormatted());
236                 attrCond.setType(AttrCond.Type.ISNOTNULL);
237                 return SearchCond.getLeaf(attrCond);
238             }
239         } else if (!conf.getUserConf().getEmails().isEmpty()
240                 && (MULTIVALUE.contains(left) || left.endsWith(".value"))) {
241 
242             List<SearchCond> orConds = new ArrayList<>();
243             items.forEach(item -> {
244                 AttrCond cond = new AttrCond();
245                 cond.setSchema(item.getFormatted());
246                 cond.setExpression(StringUtils.strip(right, "\""));
247                 orConds.add(setOperator(cond, operator));
248             });
249             if (!orConds.isEmpty()) {
250                 return SearchCond.getOr(orConds);
251             }
252         }
253 
254         return null;
255     }
256 
257     private SearchCond transform(final String operator, final String left, final String right) {
258         SearchCond result = null;
259 
260         if (MULTIVALUE.contains(StringUtils.substringBefore(left, "."))) {
261             if (conf.getUserConf() == null) {
262                 throw new IllegalArgumentException("No " + SCIMUserConf.class.getName() + " provided, cannot continue");
263             }
264 
265             switch (StringUtils.substringBefore(left, ".")) {
266                 case "emails":
267                     result = complex(operator, left, right, conf.getUserConf().getEmails());
268                     break;
269 
270                 case "phoneNumbers":
271                     result = complex(operator, left, right, conf.getUserConf().getPhoneNumbers());
272                     break;
273 
274                 case "ims":
275                     result = complex(operator, left, right, conf.getUserConf().getIms());
276                     break;
277 
278                 case "photos":
279                     result = complex(operator, left, right, conf.getUserConf().getPhotos());
280                     break;
281 
282                 case "addresses":
283                     result = addresses(operator, left, right, conf.getUserConf().getAddresses());
284                     break;
285 
286                 default:
287             }
288         }
289 
290         if (result == null) {
291             AttrCond attrCond = createAttrCond(left);
292             attrCond.setExpression(StringUtils.strip(right, "\""));
293             result = setOperator(attrCond, operator);
294         }
295 
296         if (result == null) {
297             throw new IllegalArgumentException(
298                     "Could not handle (" + left + " " + operator + " " + right + ") for " + resource);
299         }
300         return result;
301     }
302 
303     @Override
304     public SearchCond visitEXPR_OPER_EXPR(final SCIMFilterParser.EXPR_OPER_EXPRContext ctx) {
305         return transform(ctx.operator().getText(), ctx.expression(0).getText(), ctx.expression(1).getText());
306     }
307 
308     @Override
309     public SearchCond visitATTR_OPER_CRITERIA(final SCIMFilterParser.ATTR_OPER_CRITERIAContext ctx) {
310         return transform(ctx.operator().getText(), ctx.ATTRNAME().getText(), ctx.criteria().getText());
311     }
312 
313     @Override
314     public SearchCond visitATTR_OPER_EXPR(final SCIMFilterParser.ATTR_OPER_EXPRContext ctx) {
315         return transform(ctx.operator().getText(), ctx.ATTRNAME().getText(), ctx.expression().getText());
316     }
317 
318     @Override
319     public SearchCond visitATTR_PR(final SCIMFilterParser.ATTR_PRContext ctx) {
320         AttrCond cond = createAttrCond(ctx.ATTRNAME().getText());
321         cond.setType(AttrCond.Type.ISNOTNULL);
322         return SearchCond.getLeaf(cond);
323     }
324 
325     @Override
326     public SearchCond visitLPAREN_EXPR_RPAREN(final SCIMFilterParser.LPAREN_EXPR_RPARENContext ctx) {
327         return visit(ctx.expression());
328     }
329 
330     @Override
331     public SearchCond visitNOT_EXPR(final SCIMFilterParser.NOT_EXPRContext ctx) {
332         SearchCond cond = visit(ctx.expression());
333         Optional<AnyCond> anyCond = cond.getLeaf(AnyCond.class);
334         if (anyCond.isPresent()) {
335             if (anyCond.get().getType() == AttrCond.Type.ISNULL) {
336                 anyCond.get().setType(AttrCond.Type.ISNOTNULL);
337             } else if (anyCond.get().getType() == AttrCond.Type.ISNOTNULL) {
338                 anyCond.get().setType(AttrCond.Type.ISNULL);
339             }
340         } else {
341             Optional<AttrCond> attrCond = cond.getLeaf(AttrCond.class);
342             if (attrCond.isPresent()) {
343                 if (attrCond.get().getType() == AnyCond.Type.ISNULL) {
344                     attrCond.get().setType(AnyCond.Type.ISNOTNULL);
345                 } else if (attrCond.get().getType() == AnyCond.Type.ISNOTNULL) {
346                     attrCond.get().setType(AnyCond.Type.ISNULL);
347                 }
348             } else {
349                 cond = SearchCond.getNotLeaf(cond);
350             }
351         }
352 
353         return cond;
354     }
355 
356     @Override
357     public SearchCond visitEXPR_AND_EXPR(final SCIMFilterParser.EXPR_AND_EXPRContext ctx) {
358         return SearchCond.getAnd(visit(ctx.expression(0)), visit(ctx.expression(1)));
359     }
360 
361     @Override
362     public SearchCond visitEXPR_OR_EXPR(final SCIMFilterParser.EXPR_OR_EXPRContext ctx) {
363         return SearchCond.getOr(visit(ctx.expression(0)), visit(ctx.expression(1)));
364     }
365 }