1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.persistence.jpa.dao;
20
21 import java.beans.PropertyDescriptor;
22 import java.lang.annotation.Annotation;
23 import java.lang.reflect.Method;
24 import java.time.OffsetDateTime;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Optional;
28 import java.util.Set;
29 import java.util.regex.Pattern;
30 import java.util.stream.Collectors;
31 import javax.persistence.Entity;
32 import javax.validation.ValidationException;
33 import javax.validation.constraints.Max;
34 import javax.validation.constraints.Min;
35 import org.apache.commons.lang3.BooleanUtils;
36 import org.apache.commons.lang3.ClassUtils;
37 import org.apache.syncope.common.lib.SyncopeConstants;
38 import org.apache.syncope.common.lib.types.AnyTypeKind;
39 import org.apache.syncope.common.lib.types.AttrSchemaType;
40 import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
41 import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
42 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
43 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
44 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
45 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
46 import org.apache.syncope.core.persistence.api.dao.UserDAO;
47 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
48 import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
49 import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
50 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
51 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
52 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
53 import org.apache.syncope.core.persistence.api.dao.search.RelationshipCond;
54 import org.apache.syncope.core.persistence.api.dao.search.RelationshipTypeCond;
55 import org.apache.syncope.core.persistence.api.dao.search.ResourceCond;
56 import org.apache.syncope.core.persistence.api.dao.search.RoleCond;
57 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
58 import org.apache.syncope.core.persistence.api.entity.Any;
59 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
60 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
61 import org.apache.syncope.core.persistence.api.entity.GroupableRelatable;
62 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
63 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
64 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
65 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
66 import org.apache.syncope.core.persistence.api.entity.group.Group;
67 import org.apache.syncope.core.persistence.api.entity.user.User;
68 import org.apache.syncope.core.persistence.jpa.entity.JPAPlainSchema;
69 import org.springframework.beans.BeanUtils;
70 import org.springframework.transaction.annotation.Transactional;
71
72 public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
73
74 protected final UserDAO userDAO;
75
76 protected final GroupDAO groupDAO;
77
78 protected final AnyObjectDAO anyObjectDAO;
79
80 protected final RealmDAO realmDAO;
81
82 protected final PlainSchemaDAO plainSchemaDAO;
83
84 protected final AnyUtilsFactory anyUtilsFactory;
85
86 protected final PlainAttrValidationManager validator;
87
88 public JPAAnyMatchDAO(
89 final UserDAO userDAO,
90 final GroupDAO groupDAO,
91 final AnyObjectDAO anyObjectDAO,
92 final RealmDAO realmDAO,
93 final PlainSchemaDAO plainSchemaDAO,
94 final AnyUtilsFactory anyUtilsFactory,
95 final PlainAttrValidationManager validator) {
96
97 this.userDAO = userDAO;
98 this.groupDAO = groupDAO;
99 this.anyObjectDAO = anyObjectDAO;
100 this.realmDAO = realmDAO;
101 this.plainSchemaDAO = plainSchemaDAO;
102 this.anyUtilsFactory = anyUtilsFactory;
103 this.validator = validator;
104 }
105
106
107
108
109
110
111
112
113
114 @Transactional(readOnly = true)
115 @Override
116 public <T extends Any<?>> boolean matches(final T any, final SearchCond cond) {
117 boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
118 switch (cond.getType()) {
119 case LEAF:
120 case NOT_LEAF:
121 Boolean match = cond.getLeaf(AnyTypeCond.class).
122 filter(leaf -> AnyTypeKind.ANY_OBJECT == any.getType().getKind()).
123 map(leaf -> matches(any, leaf, not)).
124 orElse(null);
125
126 if (match == null) {
127 match = cond.getLeaf(RelationshipTypeCond.class).
128 filter(leaf -> any instanceof GroupableRelatable).
129 map(leaf -> matches((GroupableRelatable) any, leaf, not)).
130 orElse(null);
131 }
132
133 if (match == null) {
134 match = cond.getLeaf(RelationshipCond.class).
135 filter(leaf -> any instanceof GroupableRelatable).
136 map(leaf -> matches((GroupableRelatable) any, leaf, not)).
137 orElse(null);
138 }
139
140 if (match == null) {
141 match = cond.getLeaf(MembershipCond.class).
142 filter(leaf -> any instanceof GroupableRelatable).
143 map(leaf -> matches((GroupableRelatable) any, leaf, not)).
144 orElse(null);
145 }
146
147 if (match == null) {
148 match = cond.getLeaf(RoleCond.class).
149 filter(leaf -> any instanceof User).
150 map(leaf -> matches((User) any, leaf, not)).
151 orElse(null);
152 }
153
154 if (match == null) {
155 match = cond.getLeaf(DynRealmCond.class).
156 map(leaf -> matches(any, leaf, not)).
157 orElse(null);
158 }
159
160 if (match == null) {
161 match = cond.getLeaf(MemberCond.class).
162 filter(leaf -> any instanceof Group).
163 map(leaf -> matches((Group) any, leaf, not)).
164 orElse(null);
165 }
166
167 if (match == null) {
168 match = cond.getLeaf(ResourceCond.class).
169 map(leaf -> matches(any, leaf, not)).
170 orElse(null);
171 }
172
173 if (match == null) {
174 match = cond.getLeaf(AnyCond.class).
175 map(value -> matches(any, value, not)).
176 orElseGet(() -> cond.getLeaf(AttrCond.class).
177 map(leaf -> matches(any, leaf, not)).
178 orElse(null));
179 }
180
181 if (match == null) {
182 match = cond.getLeaf(AttrCond.class).
183 map(leaf -> matches(any, leaf, not)).
184 orElse(null);
185 }
186
187 return BooleanUtils.toBoolean(match);
188
189 case AND:
190 return matches(any, cond.getLeft()) && matches(any, cond.getRight());
191
192 case OR:
193 return matches(any, cond.getLeft()) || matches(any, cond.getRight());
194
195 default:
196 }
197
198 return false;
199 }
200
201 protected boolean matches(final Any<?> any, final AnyTypeCond cond, final boolean not) {
202 boolean equals = any.getType().getKey().equals(cond.getAnyTypeKey());
203 return not ? !equals : equals;
204 }
205
206 protected boolean matches(
207 final GroupableRelatable<?, ?, ?, ?, ?> any, final RelationshipTypeCond cond, final boolean not) {
208
209 boolean found = any.getRelationships().stream().
210 anyMatch(rel -> rel.getType().getKey().equals(cond.getRelationshipTypeKey()));
211 return not ? !found : found;
212 }
213
214 protected boolean matches(
215 final GroupableRelatable<?, ?, ?, ?, ?> any, final RelationshipCond cond, final boolean not) {
216
217 Set<String> candidates = SyncopeConstants.UUID_PATTERN.matcher(cond.getAnyObject()).matches()
218 ? Optional.ofNullable(cond.getAnyObject()).map(Set::of).orElse(Set.of())
219 : anyObjectDAO.findByName(cond.getAnyObject()).stream().
220 map(AnyObject::getKey).collect(Collectors.toSet());
221
222 boolean found = any.getRelationships().stream().
223 map(r -> r.getRightEnd().getKey()).
224 filter(candidates::contains).
225 count() > 0;
226
227 return not ? !found : found;
228 }
229
230 protected boolean matches(
231 final GroupableRelatable<?, ?, ?, ?, ?> any, final MembershipCond cond, final boolean not) {
232
233 final String group = SyncopeConstants.UUID_PATTERN.matcher(cond.getGroup()).matches()
234 ? cond.getGroup()
235 : groupDAO.findKey(cond.getGroup());
236
237 boolean found = any.getMembership(group).isPresent()
238 || (any instanceof User
239 ? userDAO.findDynGroups(any.getKey())
240 : anyObjectDAO.findDynGroups(any.getKey())).stream().
241 anyMatch(item -> item.getKey().equals(group));
242 return not ? !found : found;
243 }
244
245 protected boolean matches(final User user, final RoleCond cond, final boolean not) {
246 boolean found = userDAO.findAllRoles(user).stream().anyMatch(role -> role.getKey().equals(cond.getRole()));
247 return not ? !found : found;
248 }
249
250 protected boolean matches(final Any<?> any, final DynRealmCond cond, final boolean not) {
251 boolean found = anyUtilsFactory.getInstance(any).dao().findDynRealms(any.getKey()).stream().
252 anyMatch(dynRealm -> dynRealm.equals(cond.getDynRealm()));
253 return not ? !found : found;
254 }
255
256 protected boolean matches(final Group group, final MemberCond cond, final boolean not) {
257 boolean found = false;
258
259 GroupableRelatable<?, ?, ?, ?, ?> any = userDAO.find(cond.getMember());
260 if (any == null) {
261 any = anyObjectDAO.find(cond.getMember());
262 if (any != null) {
263 found = groupDAO.findAMemberships(group).stream().
264 anyMatch(memb -> memb.getLeftEnd().getKey().equals(cond.getMember()))
265 || groupDAO.findADynMembers(group).contains(cond.getMember());
266 }
267 } else {
268 found = groupDAO.findUMemberships(group).stream().
269 anyMatch(memb -> memb.getLeftEnd().getKey().equals(cond.getMember()))
270 || groupDAO.findUDynMembers(group).contains(cond.getMember());
271 }
272
273 return not ? !found : found;
274 }
275
276 protected boolean matches(final Any<?> any, final ResourceCond cond, final boolean not) {
277 boolean found = anyUtilsFactory.getInstance(any).getAllResources(any).stream().
278 anyMatch(resource -> resource.getKey().equals(cond.getResource()));
279 return not ? !found : found;
280 }
281
282 @SuppressWarnings({ "unchecked", "rawtypes" })
283 protected boolean matches(
284 final List<? extends PlainAttrValue> anyAttrValues,
285 final PlainAttrValue attrValue,
286 final PlainSchema schema,
287 final AttrCond cond) {
288
289 return anyAttrValues.stream().anyMatch(item -> {
290 switch (cond.getType()) {
291 case EQ:
292 return attrValue.getValue().equals(item.getValue());
293
294 case IEQ:
295 if (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum) {
296 return attrValue.getStringValue().equalsIgnoreCase(item.getStringValue());
297 } else {
298 LOG.error("IEQ is only compatible with string or enum schemas");
299 return false;
300 }
301
302 case LIKE:
303 case ILIKE:
304 if (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum) {
305 StringBuilder output = new StringBuilder();
306 for (char c : cond.getExpression().toLowerCase().toCharArray()) {
307 if (c == '%') {
308 output.append(".*");
309 } else if (Character.isLetter(c)) {
310 output.append('[').
311 append(c).
312 append(Character.toUpperCase(c)).
313 append(']');
314 } else {
315 output.append(c);
316 }
317 }
318 return (cond.getType() == AttrCond.Type.LIKE
319 ? Pattern.compile(output.toString())
320 : Pattern.compile(output.toString(), Pattern.CASE_INSENSITIVE)).
321 matcher(item.getStringValue()).matches();
322 } else {
323 LOG.error("LIKE is only compatible with string or enum schemas");
324 return false;
325 }
326
327 case GT:
328 return item.<Comparable>getValue().compareTo(attrValue.getValue()) > 0;
329
330 case GE:
331 return item.<Comparable>getValue().compareTo(attrValue.getValue()) >= 0;
332
333 case LT:
334 return item.<Comparable>getValue().compareTo(attrValue.getValue()) < 0;
335
336 case LE:
337 return item.<Comparable>getValue().compareTo(attrValue.getValue()) <= 0;
338
339 default:
340 return false;
341 }
342 });
343 }
344
345 protected boolean matches(final Any<?> any, final AttrCond cond, final boolean not) {
346 PlainSchema schema = plainSchemaDAO.find(cond.getSchema());
347 if (schema == null) {
348 LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
349 return false;
350 }
351
352 @SuppressWarnings("unchecked")
353 Optional<PlainAttr<?>> attr = (Optional<PlainAttr<?>>) any.getPlainAttr(cond.getSchema());
354
355 boolean found;
356 switch (cond.getType()) {
357 case ISNULL:
358 found = attr.isEmpty();
359 break;
360
361 case ISNOTNULL:
362 found = attr.isPresent();
363 break;
364
365 default:
366 PlainAttrValue attrValue = anyUtilsFactory.getInstance(any).newPlainAttrValue();
367 try {
368 if (cond.getType() != AttrCond.Type.LIKE
369 && cond.getType() != AttrCond.Type.ILIKE
370 && cond.getType() != AttrCond.Type.ISNULL
371 && cond.getType() != AttrCond.Type.ISNOTNULL) {
372
373 validator.validate(schema, cond.getExpression(), attrValue);
374 }
375 } catch (ValidationException e) {
376 LOG.error("Could not validate expression '" + cond.getExpression() + '\'', e);
377 return false;
378 }
379
380 found = attr.map(a -> matches(a.getValues(), attrValue, schema, cond)).orElse(false);
381 }
382 return not ? !found : found;
383 }
384
385 protected boolean matches(final Any<?> any, final AnyCond cond, final boolean not) {
386
387 if ("key".equals(cond.getSchema())) {
388 cond.setSchema("id");
389 }
390
391 PropertyDescriptor pd;
392 Object anyAttrValue;
393 try {
394 pd = BeanUtils.getPropertyDescriptor(any.getClass(), cond.getSchema());
395 if (pd == null) {
396 LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
397 return false;
398 }
399
400 anyAttrValue = pd.getReadMethod().invoke(any);
401 } catch (Exception e) {
402 LOG.error("While accessing {}.{}", any, cond.getSchema(), e);
403 return false;
404 }
405
406 boolean found;
407 switch (cond.getType()) {
408 case ISNULL:
409 found = anyAttrValue == null;
410 break;
411
412 case ISNOTNULL:
413 found = anyAttrValue != null;
414 break;
415
416 default:
417 PlainSchema schema = new JPAPlainSchema();
418 schema.setKey(pd.getName());
419 for (AttrSchemaType attrSchemaType : AttrSchemaType.values()) {
420 if (pd.getPropertyType().isAssignableFrom(attrSchemaType.getType())) {
421 schema.setType(attrSchemaType);
422 }
423 }
424
425
426 boolean foundBooleanMin = false;
427 boolean foundBooleanMax = false;
428 if (Integer.class.equals(pd.getPropertyType())) {
429 for (Annotation annotation : pd.getPropertyType().getAnnotations()) {
430 if (Min.class.equals(annotation.annotationType())) {
431 foundBooleanMin = ((Min) annotation).value() == 0;
432 } else if (Max.class.equals(annotation.annotationType())) {
433 foundBooleanMax = ((Max) annotation).value() == 1;
434 }
435 }
436 }
437 if (foundBooleanMin && foundBooleanMax) {
438 schema.setType(AttrSchemaType.Boolean);
439 }
440
441
442 if (pd.getPropertyType().getAnnotation(Entity.class) != null) {
443 Method relMethod = null;
444 try {
445 relMethod = ClassUtils.getPublicMethod(pd.getPropertyType(), "getKey", new Class<?>[0]);
446 } catch (Exception e) {
447 LOG.error("Could not find {}#getKey", pd.getPropertyType(), e);
448 }
449
450 if (relMethod != null && String.class.isAssignableFrom(relMethod.getReturnType())) {
451 cond.setSchema(cond.getSchema() + "_id");
452 schema.setType(AttrSchemaType.String);
453 }
454 }
455
456 AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
457
458 PlainAttrValue attrValue = anyUtils.newPlainAttrValue();
459 if (cond.getType() != AttrCond.Type.LIKE
460 && cond.getType() != AttrCond.Type.ILIKE
461 && cond.getType() != AttrCond.Type.ISNULL
462 && cond.getType() != AttrCond.Type.ISNOTNULL) {
463
464 try {
465 validator.validate(schema, cond.getExpression(), attrValue);
466 } catch (ValidationException e) {
467 LOG.error("Could not validate expression '" + cond.getExpression() + '\'', e);
468 return false;
469 }
470 }
471
472 List<PlainAttrValue> anyAttrValues = new ArrayList<>();
473 anyAttrValues.add(anyUtils.newPlainAttrValue());
474 if (anyAttrValue instanceof String) {
475 anyAttrValues.get(0).setStringValue((String) anyAttrValue);
476 } else if (anyAttrValue instanceof Long) {
477 anyAttrValues.get(0).setLongValue((Long) anyAttrValue);
478 } else if (anyAttrValue instanceof Double) {
479 anyAttrValues.get(0).setDoubleValue((Double) anyAttrValue);
480 } else if (anyAttrValue instanceof Boolean) {
481 anyAttrValues.get(0).setBooleanValue((Boolean) anyAttrValue);
482 } else if (anyAttrValue instanceof OffsetDateTime) {
483 anyAttrValues.get(0).setDateValue((OffsetDateTime) anyAttrValue);
484 } else if (anyAttrValue instanceof byte[]) {
485 anyAttrValues.get(0).setBinaryValue((byte[]) anyAttrValue);
486 }
487
488 found = matches(anyAttrValues, attrValue, schema, cond);
489 }
490 return not ? !found : found;
491 }
492 }