1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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 }