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.util.HashSet;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.Set;
26 import org.apache.commons.lang3.BooleanUtils;
27 import org.apache.commons.lang3.exception.ExceptionUtils;
28 import org.apache.commons.lang3.tuple.Pair;
29 import org.apache.syncope.common.lib.Attr;
30 import org.apache.syncope.common.lib.request.AnyCR;
31 import org.apache.syncope.common.lib.request.AnyUR;
32 import org.apache.syncope.common.lib.request.LinkedAccountUR;
33 import org.apache.syncope.common.lib.request.UserCR;
34 import org.apache.syncope.common.lib.request.UserUR;
35 import org.apache.syncope.common.lib.to.AnyTO;
36 import org.apache.syncope.common.lib.to.LinkedAccountTO;
37 import org.apache.syncope.common.lib.to.PropagationStatus;
38 import org.apache.syncope.common.lib.to.Provision;
39 import org.apache.syncope.common.lib.to.ProvisioningReport;
40 import org.apache.syncope.common.lib.to.UserTO;
41 import org.apache.syncope.common.lib.types.AnyTypeKind;
42 import org.apache.syncope.common.lib.types.AuditElements;
43 import org.apache.syncope.common.lib.types.AuditElements.Result;
44 import org.apache.syncope.common.lib.types.MatchType;
45 import org.apache.syncope.common.lib.types.MatchingRule;
46 import org.apache.syncope.common.lib.types.PatchOperation;
47 import org.apache.syncope.common.lib.types.ResourceOperation;
48 import org.apache.syncope.common.lib.types.UnmatchingRule;
49 import org.apache.syncope.core.persistence.api.entity.Any;
50 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
51 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
52 import org.apache.syncope.core.persistence.api.entity.user.User;
53 import org.apache.syncope.core.provisioning.api.PropagationByResource;
54 import org.apache.syncope.core.provisioning.api.ProvisioningManager;
55 import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
56 import org.apache.syncope.core.provisioning.api.WorkflowResult;
57 import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
58 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
59 import org.apache.syncope.core.provisioning.api.pushpull.UserPullResultHandler;
60 import org.apache.syncope.core.provisioning.api.rules.PullMatch;
61 import org.identityconnectors.framework.common.objects.AttributeUtil;
62 import org.identityconnectors.framework.common.objects.SyncDelta;
63 import org.quartz.JobExecutionException;
64 import org.springframework.beans.factory.annotation.Autowired;
65
66 public class DefaultUserPullResultHandler extends AbstractPullResultHandler implements UserPullResultHandler {
67
68 @Autowired
69 private UserProvisioningManager userProvisioningManager;
70
71 @Override
72 protected AnyUtils getAnyUtils() {
73 return anyUtilsFactory.getInstance(AnyTypeKind.USER);
74 }
75
76 @Override
77 protected String getName(final AnyTO anyTO) {
78 return UserTO.class.cast(anyTO).getUsername();
79 }
80
81 @Override
82 protected String getName(final AnyCR anyCR) {
83 return UserCR.class.cast(anyCR).getUsername();
84 }
85
86 @Override
87 protected ProvisioningManager<?, ?> getProvisioningManager() {
88 return userProvisioningManager;
89 }
90
91 @Override
92 protected AnyTO getAnyTO(final Any<?> any) {
93 return userDataBinder.getUserTO((User) any, true);
94 }
95
96 @Override
97 protected WorkflowResult<? extends AnyUR> update(final AnyUR req) {
98 WorkflowResult<Pair<UserUR, Boolean>> update =
99 uwfAdapter.update((UserUR) req, profile.getExecutor(), getContext());
100 return new WorkflowResult<>(update.getResult().getLeft(), update.getPropByRes(), update.getPerformedTasks());
101 }
102
103 protected Boolean enabled(final SyncDelta delta) {
104 return profile.getTask().isSyncStatus() ? AttributeUtil.isEnabled(delta.getObject()) : null;
105 }
106
107 @Override
108 protected AnyTO doCreate(final AnyCR anyCR, final SyncDelta delta) {
109 Map.Entry<String, List<PropagationStatus>> created = userProvisioningManager.create(
110 UserCR.class.cast(anyCR),
111 true,
112 enabled(delta),
113 Set.of(profile.getTask().getResource().getKey()),
114 true,
115 profile.getExecutor(),
116 getContext());
117
118 return userDataBinder.getUserTO(created.getKey());
119 }
120
121 @Override
122 protected AnyUR doUpdate(
123 final AnyTO before,
124 final AnyUR req,
125 final SyncDelta delta,
126 final ProvisioningReport result) {
127
128 Pair<UserUR, List<PropagationStatus>> updated = userProvisioningManager.update(
129 UserUR.class.cast(req),
130 result,
131 enabled(delta),
132 Set.of(profile.getTask().getResource().getKey()),
133 true,
134 profile.getExecutor(),
135 getContext());
136
137 createRemediationIfNeeded(req, delta, result);
138
139 return updated.getLeft();
140 }
141
142 @Override
143 protected Result handleLinkedAccounts(
144 final SyncDelta delta,
145 final List<PullMatch> matches,
146 final Provision provision) throws JobExecutionException {
147
148 Result global = Result.SUCCESS;
149 for (PullMatch match : matches) {
150 if (match.getAny() == null) {
151 LOG.error("Could not find linking user, cannot process match {}", match);
152 return Result.FAILURE;
153 }
154 User user = (User) match.getAny();
155
156 Optional<? extends LinkedAccount> found =
157 user.getLinkedAccount(profile.getTask().getResource().getKey(), delta.getUid().getUidValue());
158 if (found.isPresent()) {
159 LinkedAccount account = found.get();
160
161 switch (delta.getDeltaType()) {
162 case CREATE:
163 case UPDATE:
164 case CREATE_OR_UPDATE:
165 switch (profile.getTask().getMatchingRule()) {
166 case UPDATE:
167 global = and(global, update(delta, account, provision));
168 break;
169
170 case DEPROVISION:
171 case UNASSIGN:
172 global = and(global, deprovision(profile.getTask().getMatchingRule(), delta, account));
173 break;
174
175 case LINK:
176 case UNLINK:
177 LOG.warn("{} not applicable to linked accounts, ignoring",
178 profile.getTask().getMatchingRule());
179 break;
180
181 case IGNORE:
182 global = and(global, ignore(delta, account, true));
183 break;
184
185 default:
186
187 }
188 break;
189
190 case DELETE:
191 global = and(global, delete(delta, account, provision));
192 break;
193
194 default:
195 }
196 } else {
197 switch (delta.getDeltaType()) {
198 case CREATE:
199 case UPDATE:
200 case CREATE_OR_UPDATE:
201 LinkedAccountTO accountTO = new LinkedAccountTO();
202 accountTO.setConnObjectKeyValue(delta.getUid().getUidValue());
203 accountTO.setResource(profile.getTask().getResource().getKey());
204
205 switch (profile.getTask().getUnmatchingRule()) {
206 case ASSIGN:
207 case PROVISION:
208 global = and(global, provision(
209 profile.getTask().getUnmatchingRule(), delta, user, accountTO, provision));
210 break;
211
212 case IGNORE:
213 global = and(global, ignore(delta, null, false));
214 break;
215
216 default:
217
218 }
219 break;
220
221 case DELETE:
222 end(AnyTypeKind.USER.name(),
223 ResourceOperation.DELETE.name().toLowerCase(),
224 AuditElements.Result.SUCCESS,
225 null,
226 null,
227 delta);
228 LOG.debug("No match found for deletion");
229 break;
230
231 default:
232 }
233 }
234 }
235
236 return global;
237 }
238
239 protected Result deprovision(
240 final MatchingRule matchingRule,
241 final SyncDelta delta,
242 final LinkedAccount account) throws JobExecutionException {
243
244 if (!profile.getTask().isPerformUpdate()) {
245 LOG.debug("PullTask not configured for update");
246 end(AnyTypeKind.USER.name(),
247 MatchingRule.toEventName(MatchingRule.UPDATE), Result.SUCCESS, null, null, delta);
248 return Result.SUCCESS;
249 }
250
251 LOG.debug("About to deprovision {}", account);
252
253 ProvisioningReport report = new ProvisioningReport();
254 report.setOperation(ResourceOperation.DELETE);
255 report.setAnyType(MatchType.LINKED_ACCOUNT.name());
256 report.setStatus(ProvisioningReport.Status.SUCCESS);
257 report.setKey(account.getKey());
258 report.setUidValue(account.getConnObjectKeyValue());
259
260 LinkedAccountTO before = userDataBinder.getLinkedAccountTO(account);
261
262 Result resultStatus = Result.SUCCESS;
263 if (!profile.isDryRun()) {
264 Object output = before;
265
266 try {
267 if (matchingRule == MatchingRule.UNASSIGN) {
268 for (PullActions action : profile.getActions()) {
269 action.beforeUnassign(profile, delta, before);
270 }
271 } else if (matchingRule == MatchingRule.DEPROVISION) {
272 for (PullActions action : profile.getActions()) {
273 action.beforeDeprovision(profile, delta, before);
274 }
275 }
276
277 PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
278 propByLinkedAccount.add(
279 ResourceOperation.DELETE,
280 Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()));
281 taskExecutor.execute(propagationManager.getDeleteTasks(
282 AnyTypeKind.USER,
283 account.getOwner().getKey(),
284 null,
285 propByLinkedAccount,
286 null),
287 false,
288 profile.getExecutor());
289
290 for (PullActions action : profile.getActions()) {
291 action.after(profile, delta, before, report);
292 }
293
294 resultStatus = Result.SUCCESS;
295
296 LOG.debug("Linked account {} successfully updated", account.getConnObjectKeyValue());
297 } catch (PropagationException e) {
298
299
300 LOG.error("Could not propagate linked acccount {}", account.getConnObjectKeyValue());
301 output = e;
302 resultStatus = Result.FAILURE;
303 } catch (Exception e) {
304 throwIgnoreProvisionException(delta, e);
305
306 report.setStatus(ProvisioningReport.Status.FAILURE);
307 report.setMessage(ExceptionUtils.getRootCauseMessage(e));
308 LOG.error("Could not update linked account {}", account, e);
309 output = e;
310 resultStatus = Result.FAILURE;
311 }
312
313 end(AnyTypeKind.USER.name(), MatchingRule.toEventName(matchingRule), resultStatus, before, output, delta);
314 profile.getResults().add(report);
315 }
316
317 return resultStatus;
318 }
319
320 protected Result provision(
321 final UnmatchingRule rule,
322 final SyncDelta delta,
323 final User user,
324 final LinkedAccountTO accountTO,
325 final Provision provision)
326 throws JobExecutionException {
327
328 if (!profile.getTask().isPerformCreate()) {
329 LOG.debug("PullTask not configured for create");
330 end(AnyTypeKind.USER.name(), UnmatchingRule.toEventName(rule), Result.SUCCESS, null, null, delta);
331 return Result.SUCCESS;
332 }
333
334 LOG.debug("About to create {}", accountTO);
335
336 ProvisioningReport report = new ProvisioningReport();
337 report.setOperation(ResourceOperation.CREATE);
338 report.setName(accountTO.getConnObjectKeyValue());
339 report.setUidValue(accountTO.getConnObjectKeyValue());
340 report.setAnyType(MatchType.LINKED_ACCOUNT.name());
341 report.setStatus(ProvisioningReport.Status.SUCCESS);
342
343 if (profile.isDryRun()) {
344 report.setKey(null);
345 end(AnyTypeKind.USER.name(), UnmatchingRule.toEventName(rule), Result.SUCCESS, null, null, delta);
346 return Result.SUCCESS;
347 }
348
349 UserTO owner = userDataBinder.getUserTO(user, false);
350 UserCR connObject = connObjectUtils.getAnyCR(
351 delta.getObject(), profile.getTask(), AnyTypeKind.USER, provision, false);
352
353 if (connObject.getUsername().equals(owner.getUsername())) {
354 accountTO.setUsername(null);
355 } else if (!connObject.getUsername().equals(accountTO.getUsername())) {
356 accountTO.setUsername(connObject.getUsername());
357 }
358
359 if (connObject.getPassword() != null) {
360 accountTO.setPassword(connObject.getPassword());
361 }
362
363 accountTO.setSuspended(BooleanUtils.isTrue(BooleanUtils.negate(enabled(delta))));
364
365 connObject.getPlainAttrs().forEach(connObjectAttr -> {
366 Optional<Attr> ownerAttr = owner.getPlainAttr(connObjectAttr.getSchema());
367 if (ownerAttr.isPresent() && ownerAttr.get().getValues().equals(connObjectAttr.getValues())) {
368 accountTO.getPlainAttrs().removeIf(attr -> connObjectAttr.getSchema().equals(attr.getSchema()));
369 } else {
370 accountTO.getPlainAttrs().add(connObjectAttr);
371 }
372 });
373
374 for (PullActions action : profile.getActions()) {
375 if (rule == UnmatchingRule.ASSIGN) {
376 action.beforeAssign(profile, delta, accountTO);
377 } else if (rule == UnmatchingRule.PROVISION) {
378 action.beforeProvision(profile, delta, accountTO);
379 }
380 }
381 report.setName(accountTO.getConnObjectKeyValue());
382
383 UserUR req = new UserUR();
384 req.setKey(user.getKey());
385 req.getLinkedAccounts().add(new LinkedAccountUR.Builder().
386 operation(PatchOperation.ADD_REPLACE).linkedAccountTO(accountTO).build());
387
388 Result resultStatus;
389 Object output;
390
391 try {
392 userProvisioningManager.update(
393 req,
394 report,
395 null,
396 Set.of(profile.getTask().getResource().getKey()),
397 true,
398 profile.getExecutor(),
399 getContext());
400
401 LinkedAccountTO created = userDAO.find(req.getKey()).
402 getLinkedAccount(accountTO.getResource(), accountTO.getConnObjectKeyValue()).
403 map(acct -> userDataBinder.getLinkedAccountTO(acct)).
404 orElse(null);
405
406 output = created;
407 resultStatus = Result.SUCCESS;
408
409 for (PullActions action : profile.getActions()) {
410 action.after(profile, delta, created, report);
411 }
412
413 LOG.debug("Linked account {} successfully created", accountTO.getConnObjectKeyValue());
414 } catch (PropagationException e) {
415
416
417 LOG.error("Could not propagate linked acccount {}", accountTO.getConnObjectKeyValue());
418 output = e;
419 resultStatus = Result.FAILURE;
420 } catch (Exception e) {
421 throwIgnoreProvisionException(delta, e);
422
423 report.setStatus(ProvisioningReport.Status.FAILURE);
424 report.setMessage(ExceptionUtils.getRootCauseMessage(e));
425 LOG.error("Could not create linked account {} ", accountTO.getConnObjectKeyValue(), e);
426 output = e;
427 resultStatus = Result.FAILURE;
428
429 if (profile.getTask().isRemediation()) {
430 createRemediation(provision.getAnyType(), null, null, req, report, delta);
431 }
432 }
433
434 end(AnyTypeKind.USER.name(), UnmatchingRule.toEventName(rule), resultStatus, null, output, delta);
435 profile.getResults().add(report);
436
437 return resultStatus;
438 }
439
440 protected Result update(
441 final SyncDelta delta,
442 final LinkedAccount account,
443 final Provision provision)
444 throws JobExecutionException {
445
446 if (!profile.getTask().isPerformUpdate()) {
447 LOG.debug("PullTask not configured for update");
448 end(AnyTypeKind.USER.name(),
449 MatchingRule.toEventName(MatchingRule.UPDATE), Result.SUCCESS, null, null, delta);
450 return Result.SUCCESS;
451 }
452
453 LOG.debug("About to update {}", account);
454
455 ProvisioningReport report = new ProvisioningReport();
456 report.setOperation(ResourceOperation.UPDATE);
457 report.setKey(account.getKey());
458 report.setUidValue(account.getConnObjectKeyValue());
459 report.setName(account.getConnObjectKeyValue());
460 report.setAnyType(MatchType.LINKED_ACCOUNT.name());
461 report.setStatus(ProvisioningReport.Status.SUCCESS);
462
463 Result resultStatus = Result.SUCCESS;
464 if (!profile.isDryRun()) {
465 LinkedAccountTO before = userDataBinder.getLinkedAccountTO(account);
466
467 UserTO owner = userDataBinder.getUserTO(account.getOwner(), false);
468 UserCR connObject = connObjectUtils.getAnyCR(
469 delta.getObject(), profile.getTask(), AnyTypeKind.USER, provision, false);
470
471 LinkedAccountTO update = userDataBinder.getLinkedAccountTO(account);
472
473 if (connObject.getUsername().equals(owner.getUsername())) {
474 update.setUsername(null);
475 } else if (!connObject.getUsername().equals(update.getUsername())) {
476 update.setUsername(connObject.getUsername());
477 }
478
479 if (connObject.getPassword() != null) {
480 update.setPassword(connObject.getPassword());
481 }
482
483 update.setSuspended(BooleanUtils.isTrue(BooleanUtils.negate(enabled(delta))));
484
485 Set<String> attrsToRemove = new HashSet<>();
486 connObject.getPlainAttrs().forEach(connObjectAttr -> {
487 Optional<Attr> ownerAttr = owner.getPlainAttr(connObjectAttr.getSchema());
488 if (ownerAttr.isPresent() && ownerAttr.get().getValues().equals(connObjectAttr.getValues())) {
489 attrsToRemove.add(connObjectAttr.getSchema());
490 } else {
491 Optional<Attr> updateAttr = update.getPlainAttr(connObjectAttr.getSchema());
492 if (updateAttr.isEmpty() || !updateAttr.get().getValues().equals(connObjectAttr.getValues())) {
493 attrsToRemove.add(connObjectAttr.getSchema());
494 update.getPlainAttrs().add(connObjectAttr);
495 }
496 }
497 });
498 update.getPlainAttrs().removeIf(attr -> attrsToRemove.contains(attr.getSchema()));
499
500 UserUR userUR = new UserUR();
501 userUR.setKey(account.getOwner().getKey());
502 userUR.getLinkedAccounts().add(new LinkedAccountUR.Builder().
503 operation(PatchOperation.ADD_REPLACE).linkedAccountTO(update).build());
504
505 for (PullActions action : profile.getActions()) {
506 action.beforeUpdate(profile, delta, before, userUR);
507 }
508
509 Object output;
510 try {
511 userProvisioningManager.update(
512 userUR,
513 report,
514 null,
515 Set.of(profile.getTask().getResource().getKey()),
516 true,
517 profile.getExecutor(),
518 getContext());
519 resultStatus = Result.SUCCESS;
520
521 LinkedAccountTO updated = userDAO.find(userUR.getKey()).
522 getLinkedAccount(account.getResource().getKey(), account.getConnObjectKeyValue()).
523 map(acct -> userDataBinder.getLinkedAccountTO(acct)).
524 orElse(null);
525 output = updated;
526
527 for (PullActions action : profile.getActions()) {
528 action.after(profile, delta, updated, report);
529 }
530
531 LOG.debug("Linked account {} successfully updated", account.getConnObjectKeyValue());
532 } catch (PropagationException e) {
533
534
535 LOG.error("Could not propagate linked acccount {}", account.getConnObjectKeyValue());
536 output = e;
537 resultStatus = Result.FAILURE;
538 } catch (Exception e) {
539 throwIgnoreProvisionException(delta, e);
540
541 report.setStatus(ProvisioningReport.Status.FAILURE);
542 report.setMessage(ExceptionUtils.getRootCauseMessage(e));
543 LOG.error("Could not update linked account {}", account, e);
544 output = e;
545 resultStatus = Result.FAILURE;
546
547 if (profile.getTask().isRemediation()) {
548 createRemediation(provision.getAnyType(), null, null, userUR, report, delta);
549 }
550 }
551
552 end(AnyTypeKind.USER.name(),
553 MatchingRule.toEventName(MatchingRule.UPDATE),
554 resultStatus, before, output, delta);
555 profile.getResults().add(report);
556 }
557
558 return resultStatus;
559 }
560
561 protected Result delete(
562 final SyncDelta delta,
563 final LinkedAccount account,
564 final Provision provision)
565 throws JobExecutionException {
566
567 if (!profile.getTask().isPerformDelete()) {
568 LOG.debug("PullTask not configured for delete");
569 end(AnyTypeKind.USER.name(),
570 ResourceOperation.DELETE.name().toLowerCase(), Result.SUCCESS, null, null, delta);
571 return Result.SUCCESS;
572 }
573
574 LOG.debug("About to delete {}", account);
575
576 Object output;
577 Result resultStatus = Result.FAILURE;
578
579 ProvisioningReport report = new ProvisioningReport();
580
581 try {
582 report.setKey(account.getKey());
583 report.setName(account.getConnObjectKeyValue());
584 report.setUidValue(account.getConnObjectKeyValue());
585 report.setOperation(ResourceOperation.DELETE);
586 report.setAnyType(MatchType.LINKED_ACCOUNT.name());
587 report.setStatus(ProvisioningReport.Status.SUCCESS);
588
589 if (!profile.isDryRun()) {
590 LinkedAccountTO before = userDataBinder.getLinkedAccountTO(account);
591
592 for (PullActions action : profile.getActions()) {
593 action.beforeDelete(profile, delta, before);
594 }
595
596 UserUR req = new UserUR();
597 req.setKey(account.getOwner().getKey());
598 req.getLinkedAccounts().add(new LinkedAccountUR.Builder().
599 operation(PatchOperation.DELETE).linkedAccountTO(before).build());
600
601 try {
602 userProvisioningManager.update(
603 req,
604 report,
605 null,
606 Set.of(profile.getTask().getResource().getKey()),
607 true,
608 profile.getExecutor(),
609 getContext());
610 resultStatus = Result.SUCCESS;
611
612 output = null;
613
614 for (PullActions action : profile.getActions()) {
615 action.after(profile, delta, before, report);
616 }
617 } catch (Exception e) {
618 throwIgnoreProvisionException(delta, e);
619
620 report.setStatus(ProvisioningReport.Status.FAILURE);
621 report.setMessage(ExceptionUtils.getRootCauseMessage(e));
622 LOG.error("Could not delete linked account {}", account, e);
623 output = e;
624
625 if (profile.getTask().isRemediation()) {
626 createRemediation(provision.getAnyType(), null, null, req, report, delta);
627 }
628 }
629
630 end(AnyTypeKind.USER.name(),
631 ResourceOperation.DELETE.name().toLowerCase(),
632 resultStatus, before, output, delta);
633 profile.getResults().add(report);
634 }
635 } catch (Exception e) {
636 LOG.error("Could not delete linked account {}", account, e);
637 }
638
639 return resultStatus;
640 }
641
642 protected Result ignore(
643 final SyncDelta delta,
644 final LinkedAccount account,
645 final boolean matching,
646 final String... message)
647 throws JobExecutionException {
648
649 LOG.debug("Linked account to ignore {}", delta.getObject().getUid().getUidValue());
650
651 ProvisioningReport report = new ProvisioningReport();
652 report.setName(delta.getUid().getUidValue());
653 report.setUidValue(delta.getUid().getUidValue());
654 report.setOperation(ResourceOperation.NONE);
655 report.setAnyType(MatchType.LINKED_ACCOUNT.name());
656 report.setStatus(ProvisioningReport.Status.SUCCESS);
657 if (message != null && message.length >= 1) {
658 report.setMessage(message[0]);
659 }
660 if (account != null) {
661 report.setKey(account.getKey());
662 }
663
664 end(AnyTypeKind.USER.name(),
665 matching
666 ? MatchingRule.toEventName(MatchingRule.IGNORE)
667 : UnmatchingRule.toEventName(UnmatchingRule.IGNORE),
668 AuditElements.Result.SUCCESS, null, null, delta);
669
670 profile.getResults().add(report);
671 return Result.SUCCESS;
672 }
673 }