1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.provisioning.java.pushpull;
20
21 import java.text.ParseException;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Objects;
26 import java.util.Optional;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.stream.Collectors;
29 import java.util.stream.Stream;
30 import org.apache.syncope.common.lib.SyncopeConstants;
31 import org.apache.syncope.common.lib.to.Item;
32 import org.apache.syncope.common.lib.to.OrgUnit;
33 import org.apache.syncope.common.lib.to.Provision;
34 import org.apache.syncope.common.lib.types.AnyTypeKind;
35 import org.apache.syncope.common.lib.types.MatchType;
36 import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
37 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
38 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
39 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
40 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
41 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
42 import org.apache.syncope.core.persistence.api.dao.UserDAO;
43 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
44 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
45 import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
46 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
47 import org.apache.syncope.core.persistence.api.entity.Any;
48 import org.apache.syncope.core.persistence.api.entity.AnyType;
49 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
50 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
51 import org.apache.syncope.core.persistence.api.entity.DerSchema;
52 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
53 import org.apache.syncope.core.persistence.api.entity.Implementation;
54 import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
55 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
56 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
57 import org.apache.syncope.core.persistence.api.entity.Realm;
58 import org.apache.syncope.core.persistence.api.entity.VirSchema;
59 import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
60 import org.apache.syncope.core.provisioning.api.Connector;
61 import org.apache.syncope.core.provisioning.api.IntAttrName;
62 import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
63 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
64 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
65 import org.apache.syncope.core.provisioning.api.rules.PullCorrelationRule;
66 import org.apache.syncope.core.provisioning.api.rules.PullMatch;
67 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
68 import org.apache.syncope.core.spring.implementation.ImplementationManager;
69 import org.identityconnectors.framework.common.objects.Attribute;
70 import org.identityconnectors.framework.common.objects.AttributeUtil;
71 import org.identityconnectors.framework.common.objects.ConnectorObject;
72 import org.identityconnectors.framework.common.objects.Name;
73 import org.identityconnectors.framework.common.objects.ObjectClass;
74 import org.identityconnectors.framework.common.objects.SearchResult;
75 import org.identityconnectors.framework.common.objects.SyncDelta;
76 import org.identityconnectors.framework.common.objects.SyncDeltaBuilder;
77 import org.identityconnectors.framework.common.objects.SyncDeltaType;
78 import org.identityconnectors.framework.common.objects.SyncToken;
79 import org.identityconnectors.framework.common.objects.Uid;
80 import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
81 import org.identityconnectors.framework.spi.SearchResultsHandler;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
84 import org.springframework.transaction.annotation.Transactional;
85 import org.springframework.util.CollectionUtils;
86
87 @Transactional(readOnly = true)
88 public class InboundMatcher {
89
90 protected static final Logger LOG = LoggerFactory.getLogger(InboundMatcher.class);
91
92 protected final UserDAO userDAO;
93
94 protected final AnyObjectDAO anyObjectDAO;
95
96 protected final GroupDAO groupDAO;
97
98 protected final AnySearchDAO anySearchDAO;
99
100 protected final RealmDAO realmDAO;
101
102 protected final VirSchemaDAO virSchemaDAO;
103
104 protected final ImplementationDAO implementationDAO;
105
106 protected final VirAttrHandler virAttrHandler;
107
108 protected final IntAttrNameParser intAttrNameParser;
109
110 protected final AnyUtilsFactory anyUtilsFactory;
111
112 protected final Map<String, PullCorrelationRule> perContextPullCorrelationRules = new ConcurrentHashMap<>();
113
114 public InboundMatcher(
115 final UserDAO userDAO,
116 final AnyObjectDAO anyObjectDAO,
117 final GroupDAO groupDAO,
118 final AnySearchDAO anySearchDAO,
119 final RealmDAO realmDAO,
120 final VirSchemaDAO virSchemaDAO,
121 final ImplementationDAO implementationDAO,
122 final VirAttrHandler virAttrHandler,
123 final IntAttrNameParser intAttrNameParser,
124 final AnyUtilsFactory anyUtilsFactory) {
125
126 this.userDAO = userDAO;
127 this.anyObjectDAO = anyObjectDAO;
128 this.groupDAO = groupDAO;
129 this.anySearchDAO = anySearchDAO;
130 this.realmDAO = realmDAO;
131 this.virSchemaDAO = virSchemaDAO;
132 this.implementationDAO = implementationDAO;
133 this.virAttrHandler = virAttrHandler;
134 this.intAttrNameParser = intAttrNameParser;
135 this.anyUtilsFactory = anyUtilsFactory;
136 }
137
138 public Optional<PullMatch> match(
139 final AnyType anyType,
140 final String nameValue,
141 final ExternalResource resource,
142 final Connector connector) {
143
144 Optional<Provision> provision = resource.getProvisionByAnyType(anyType.getKey());
145 if (provision.isEmpty()) {
146 return Optional.empty();
147 }
148
149 Stream<Item> mapItems = Stream.concat(
150 provision.get().getMapping().getItems().stream(),
151 virSchemaDAO.find(resource.getKey(), anyType.getKey()).stream().map(VirSchema::asLinkingMappingItem));
152
153 List<ConnectorObject> found = new ArrayList<>();
154
155 try {
156 Name nameAttr = new Name(nameValue);
157 connector.search(
158 new ObjectClass(provision.get().getObjectClass()),
159 provision.get().isIgnoreCaseMatch()
160 ? FilterBuilder.equalsIgnoreCase(nameAttr)
161 : FilterBuilder.equalTo(nameAttr),
162 new SearchResultsHandler() {
163
164 @Override
165 public void handleResult(final SearchResult result) {
166
167 }
168
169 @Override
170 public boolean handle(final ConnectorObject connectorObject) {
171 return found.add(connectorObject);
172 }
173 }, MappingUtils.buildOperationOptions(mapItems));
174 } catch (Throwable t) {
175 LOG.warn("While searching for {} ...", nameValue, t);
176 }
177
178 Optional<PullMatch> result = Optional.empty();
179
180 if (found.isEmpty()) {
181 LOG.debug("No {} found on {} with {} {}", provision.get().getObjectClass(), resource, Name.NAME, nameValue);
182 } else {
183 if (found.size() > 1) {
184 LOG.warn("More than one {} found on {} with {} {} - taking first only",
185 provision.get().getObjectClass(), resource, Name.NAME, nameValue);
186 }
187
188 ConnectorObject connObj = found.iterator().next();
189 try {
190 List<PullMatch> matches = match(
191 new SyncDeltaBuilder().
192 setToken(new SyncToken("")).
193 setDeltaType(SyncDeltaType.CREATE_OR_UPDATE).
194 setObject(connObj).
195 build(),
196 resource,
197 provision.get(),
198 anyType.getKind());
199 if (matches.isEmpty()) {
200 LOG.debug("No matching {} found for {}, aborting", anyType.getKind(), connObj);
201 } else {
202 if (matches.size() > 1) {
203 LOG.warn("More than one {} found {} - taking first only", anyType.getKind(), matches);
204 }
205
206 result = matches.stream().filter(match -> match.getAny() != null).findFirst();
207 result.ifPresent(pullMatch -> virAttrHandler.setValues(pullMatch.getAny(), connObj));
208 }
209 } catch (IllegalArgumentException e) {
210 LOG.warn(e.getMessage());
211 }
212 }
213
214 return result;
215 }
216
217 protected List<Implementation> getTransformers(final Item item) {
218 return item.getTransformers().stream().
219 map(implementationDAO::find).
220 filter(Objects::nonNull).
221 collect(Collectors.toList());
222 }
223
224 public List<PullMatch> matchByConnObjectKeyValue(
225 final Item connObjectKeyItem,
226 final String connObjectKeyValue,
227 final AnyTypeKind anyTypeKind,
228 final boolean ignoreCaseMatch,
229 final ExternalResource resource) {
230
231 String finalConnObjectKeyValue = connObjectKeyValue;
232 for (ItemTransformer transformer
233 : MappingUtils.getItemTransformers(connObjectKeyItem, getTransformers(connObjectKeyItem))) {
234
235 List<Object> output = transformer.beforePull(
236 connObjectKeyItem,
237 null,
238 List.of(finalConnObjectKeyValue));
239 if (!CollectionUtils.isEmpty(output)) {
240 finalConnObjectKeyValue = output.get(0).toString();
241 }
242 }
243
244 List<PullMatch> noMatchResult = List.of(PullCorrelationRule.NO_MATCH);
245
246 IntAttrName intAttrName;
247 try {
248 intAttrName = intAttrNameParser.parse(connObjectKeyItem.getIntAttrName(), anyTypeKind);
249 } catch (ParseException e) {
250 LOG.error("Invalid intAttrName '{}' specified, ignoring", connObjectKeyItem.getIntAttrName(), e);
251 return noMatchResult;
252 }
253
254 AnyUtils anyUtils = anyUtilsFactory.getInstance(anyTypeKind);
255
256 List<Any<?>> anys = new ArrayList<>();
257
258 if (intAttrName.getField() != null) {
259 switch (intAttrName.getField()) {
260 case "key":
261 Optional.ofNullable(anyUtils.dao().find(finalConnObjectKeyValue)).ifPresent(anys::add);
262 break;
263
264 case "username":
265 if (anyTypeKind == AnyTypeKind.USER && ignoreCaseMatch) {
266 AnyCond cond = new AnyCond(AttrCond.Type.IEQ);
267 cond.setSchema("username");
268 cond.setExpression(finalConnObjectKeyValue);
269 anys.addAll(anySearchDAO.search(SearchCond.getLeaf(cond), AnyTypeKind.USER));
270 } else {
271 Optional.ofNullable(userDAO.findByUsername(finalConnObjectKeyValue)).ifPresent(anys::add);
272 }
273 break;
274
275 case "name":
276 if (anyTypeKind == AnyTypeKind.GROUP && ignoreCaseMatch) {
277 AnyCond cond = new AnyCond(AttrCond.Type.IEQ);
278 cond.setSchema("name");
279 cond.setExpression(finalConnObjectKeyValue);
280 anys.addAll(anySearchDAO.search(SearchCond.getLeaf(cond), AnyTypeKind.GROUP));
281 } else {
282 Optional.ofNullable(groupDAO.findByName(finalConnObjectKeyValue)).ifPresent(anys::add);
283 }
284
285 if (anyTypeKind == AnyTypeKind.ANY_OBJECT && ignoreCaseMatch) {
286 AnyCond cond = new AnyCond(AttrCond.Type.IEQ);
287 cond.setSchema("name");
288 cond.setExpression(finalConnObjectKeyValue);
289 anys.addAll(anySearchDAO.search(SearchCond.getLeaf(cond), AnyTypeKind.ANY_OBJECT));
290 } else {
291 anys.addAll(anyObjectDAO.findByName(finalConnObjectKeyValue));
292 }
293 break;
294
295 default:
296 }
297 } else if (intAttrName.getSchemaType() != null) {
298 switch (intAttrName.getSchemaType()) {
299 case PLAIN:
300 PlainAttrValue value = intAttrName.getSchema().isUniqueConstraint()
301 ? anyUtils.newPlainAttrUniqueValue()
302 : anyUtils.newPlainAttrValue();
303 try {
304 value.parseValue((PlainSchema) intAttrName.getSchema(), finalConnObjectKeyValue);
305 } catch (ParsingValidationException e) {
306 LOG.error("While parsing provided {} {}", Uid.NAME, value, e);
307 value.setStringValue(finalConnObjectKeyValue);
308 }
309
310 if (intAttrName.getSchema().isUniqueConstraint()) {
311 anyUtils.dao().findByPlainAttrUniqueValue((PlainSchema) intAttrName.getSchema(),
312 (PlainAttrUniqueValue) value, ignoreCaseMatch).
313 ifPresent(anys::add);
314 } else {
315 anys.addAll(anyUtils.dao().findByPlainAttrValue((PlainSchema) intAttrName.getSchema(),
316 value, ignoreCaseMatch));
317 }
318 break;
319
320 case DERIVED:
321 anys.addAll(anyUtils.dao().findByDerAttrValue((DerSchema) intAttrName.getSchema(),
322 finalConnObjectKeyValue, ignoreCaseMatch));
323 break;
324
325 default:
326 }
327 }
328
329 List<PullMatch> result = anys.stream().
330 map(any -> new PullMatch(MatchType.ANY, any)).
331 collect(Collectors.toList());
332
333 if (resource != null) {
334 userDAO.findLinkedAccount(resource, finalConnObjectKeyValue).
335 map(account -> new PullMatch(MatchType.LINKED_ACCOUNT, account)).
336 ifPresent(result::add);
337 }
338
339 return result.isEmpty() ? noMatchResult : result;
340 }
341
342 protected List<PullMatch> matchByCorrelationRule(
343 final SyncDelta syncDelta,
344 final Provision provision,
345 final PullCorrelationRule rule,
346 final AnyTypeKind type) {
347
348 List<PullMatch> result = new ArrayList<>();
349
350 try {
351 result.addAll(anySearchDAO.search(rule.getSearchCond(syncDelta, provision), type).stream().
352 map(any -> rule.matching(any, syncDelta, provision)).
353 collect(Collectors.toList()));
354 } catch (Throwable t) {
355 LOG.error("While searching via {}", rule.getClass().getName(), t);
356 }
357
358 if (result.isEmpty()) {
359 rule.unmatching(syncDelta, provision).ifPresent(result::add);
360 }
361
362 return result;
363 }
364
365 protected Optional<PullCorrelationRule> rule(final ExternalResource resource, final Provision provision) {
366 Optional<? extends PullCorrelationRuleEntity> correlationRule = resource.getPullPolicy() == null
367 ? Optional.empty()
368 : resource.getPullPolicy().getCorrelationRule(provision.getAnyType());
369
370 Optional<PullCorrelationRule> rule = Optional.empty();
371 if (correlationRule.isPresent()) {
372 Implementation impl = correlationRule.get().getImplementation();
373 try {
374 rule = ImplementationManager.buildPullCorrelationRule(
375 impl,
376 () -> perContextPullCorrelationRules.get(impl.getKey()),
377 instance -> perContextPullCorrelationRules.put(impl.getKey(), instance));
378 } catch (Exception e) {
379 LOG.error("While building {}", impl, e);
380 }
381 }
382
383 return rule;
384 }
385
386
387
388
389
390
391
392
393
394
395 public List<PullMatch> match(
396 final SyncDelta syncDelta,
397 final ExternalResource resource,
398 final Provision provision,
399 final AnyTypeKind anyTypeKind) {
400
401 Optional<PullCorrelationRule> rule = rule(resource, provision);
402
403 List<PullMatch> result = List.of();
404 try {
405 if (rule.isPresent()) {
406 result = matchByCorrelationRule(syncDelta, provision, rule.get(), anyTypeKind);
407 } else {
408 String connObjectKeyValue = null;
409
410 Optional<Item> connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
411 if (connObjectKeyItem.isPresent()) {
412 Attribute connObjectKeyAttr = syncDelta.getObject().
413 getAttributeByName(connObjectKeyItem.get().getExtAttrName());
414 if (connObjectKeyAttr != null) {
415 connObjectKeyValue = AttributeUtil.getStringValue(connObjectKeyAttr);
416 }
417
418 if (connObjectKeyValue == null) {
419 connObjectKeyValue = syncDelta.getUid().getUidValue();
420 }
421 }
422 if (connObjectKeyValue == null) {
423 result = List.of(PullCorrelationRule.NO_MATCH);
424 } else {
425 result = matchByConnObjectKeyValue(
426 connObjectKeyItem.get(),
427 connObjectKeyValue,
428 anyTypeKind,
429 provision.isIgnoreCaseMatch(),
430 resource);
431 }
432 }
433 } catch (RuntimeException e) {
434 LOG.error("Could not match {} with any existing {}", syncDelta, provision.getAnyType(), e);
435 }
436
437 if (result.size() == 1 && result.get(0).getMatchTarget() == MatchType.ANY) {
438 virAttrHandler.setValues(result.get(0).getAny(), syncDelta.getObject());
439 }
440
441 return result;
442 }
443
444
445
446
447
448
449
450
451 @Transactional(readOnly = true)
452 public List<Realm> match(final SyncDelta syncDelta, final OrgUnit orgUnit) {
453 String connObjectKey = null;
454
455 Optional<Item> connObjectKeyItem = orgUnit.getConnObjectKeyItem();
456 if (connObjectKeyItem.isPresent()) {
457 Attribute connObjectKeyAttr = syncDelta.getObject().
458 getAttributeByName(connObjectKeyItem.get().getExtAttrName());
459 if (connObjectKeyAttr != null) {
460 connObjectKey = AttributeUtil.getStringValue(connObjectKeyAttr);
461 }
462 }
463 if (connObjectKey == null) {
464 return List.of();
465 }
466
467 for (ItemTransformer transformer
468 : MappingUtils.getItemTransformers(connObjectKeyItem.get(), getTransformers(connObjectKeyItem.get()))) {
469
470 List<Object> output = transformer.beforePull(
471 connObjectKeyItem.get(),
472 null,
473 List.of(connObjectKey));
474 if (!CollectionUtils.isEmpty(output)) {
475 connObjectKey = output.get(0).toString();
476 }
477 }
478
479 List<Realm> result = new ArrayList<>();
480
481 Realm realm;
482 switch (connObjectKeyItem.get().getIntAttrName()) {
483 case "key":
484 realm = realmDAO.find(connObjectKey);
485 if (realm != null) {
486 result.add(realm);
487 }
488 break;
489
490 case "name":
491 if (orgUnit.isIgnoreCaseMatch()) {
492 result.addAll(realmDAO.findDescendants(SyncopeConstants.ROOT_REALM, connObjectKey, -1, -1));
493 } else {
494 result.addAll(realmDAO.findByName(connObjectKey).stream().collect(Collectors.toList()));
495 }
496 break;
497
498 case "fullpath":
499 realm = realmDAO.findByFullPath(connObjectKey);
500 if (realm != null) {
501 result.add(realm);
502 }
503 break;
504
505 default:
506 }
507
508 return result;
509 }
510 }