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.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                             // do nothing
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                             // do nothing
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                 // A propagation failure doesn't imply a pull failure.
299                 // The propagation exception status will be reported into the propagation task execution.
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             // A propagation failure doesn't imply a pull failure.
416             // The propagation exception status will be reported into the propagation task execution.
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                 // A propagation failure doesn't imply a pull failure.
534                 // The propagation exception status will be reported into the propagation task execution.
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 }