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.ArrayList;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.stream.Collectors;
29 import org.apache.commons.lang3.BooleanUtils;
30 import org.apache.commons.lang3.exception.ExceptionUtils;
31 import org.apache.syncope.common.lib.request.AnyUR;
32 import org.apache.syncope.common.lib.request.StringPatchItem;
33 import org.apache.syncope.common.lib.to.AnyTO;
34 import org.apache.syncope.common.lib.to.Provision;
35 import org.apache.syncope.common.lib.to.ProvisioningReport;
36 import org.apache.syncope.common.lib.types.AuditElements;
37 import org.apache.syncope.common.lib.types.AuditElements.Result;
38 import org.apache.syncope.common.lib.types.ExecStatus;
39 import org.apache.syncope.common.lib.types.MatchingRule;
40 import org.apache.syncope.common.lib.types.PatchOperation;
41 import org.apache.syncope.common.lib.types.ResourceOperation;
42 import org.apache.syncope.common.lib.types.UnmatchingRule;
43 import org.apache.syncope.core.persistence.api.entity.Any;
44 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
45 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
46 import org.apache.syncope.core.persistence.api.entity.user.User;
47 import org.apache.syncope.core.provisioning.api.AuditManager;
48 import org.apache.syncope.core.provisioning.api.MappingManager;
49 import org.apache.syncope.core.provisioning.api.PropagationByResource;
50 import org.apache.syncope.core.provisioning.api.event.AfterHandlingEvent;
51 import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
52 import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
53 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
54 import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
55 import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
56 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePushResultHandler;
57 import org.apache.syncope.core.provisioning.java.job.AfterHandlingJob;
58 import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter;
59 import org.apache.syncope.core.spring.security.AuthContextUtils;
60 import org.identityconnectors.framework.common.objects.ConnectorObject;
61 import org.quartz.JobExecutionException;
62 import org.springframework.beans.factory.annotation.Autowired;
63 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
64 import org.springframework.transaction.annotation.Propagation;
65 import org.springframework.transaction.annotation.Transactional;
66
67 public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHandler<PushTask, PushActions>
68 implements SyncopePushResultHandler {
69
70 @Autowired
71 protected OutboundMatcher outboundMatcher;
72
73
74
75
76 @Autowired
77 protected NotificationManager notificationManager;
78
79
80
81
82 @Autowired
83 protected AuditManager auditManager;
84
85 @Autowired
86 protected MappingManager mappingManager;
87
88 @Autowired
89 protected SchedulerFactoryBean scheduler;
90
91 protected abstract String getName(Any<?> any);
92
93 protected void update(
94 final Any<?> any,
95 final Boolean enable,
96 final ConnectorObject beforeObj,
97 final ProvisioningReport result) {
98
99 boolean changepwd = any instanceof User;
100 List<String> ownedResources = getAnyUtils().getAllResources(any).stream().
101 map(ExternalResource::getKey).collect(Collectors.toList());
102
103 List<String> noPropResources = new ArrayList<>(ownedResources);
104 noPropResources.remove(profile.getTask().getResource().getKey());
105
106 PropagationByResource<String> propByRes = new PropagationByResource<>();
107 propByRes.add(ResourceOperation.UPDATE, profile.getTask().getResource().getKey());
108 propByRes.addOldConnObjectKey(profile.getTask().getResource().getKey(), beforeObj.getUid().getUidValue());
109
110 List<PropagationTaskInfo> taskInfos = propagationManager.getUpdateTasks(
111 any.getType().getKind(),
112 any.getKey(),
113 changepwd,
114 enable,
115 propByRes,
116 null,
117 null,
118 noPropResources);
119 if (!taskInfos.isEmpty()) {
120 taskInfos.get(0).setBeforeObj(Optional.of(beforeObj));
121 PropagationReporter reporter = new DefaultPropagationReporter();
122 taskExecutor.execute(taskInfos.get(0), reporter, securityProperties.getAdminUser());
123 reportPropagation(result, reporter);
124 }
125 }
126
127 protected void deprovision(final Any<?> any, final ConnectorObject beforeObj, final ProvisioningReport result) {
128 AnyTO before = getAnyTO(any);
129
130 List<String> noPropResources = new ArrayList<>(before.getResources());
131 noPropResources.remove(profile.getTask().getResource().getKey());
132
133 PropagationByResource<String> propByRes = new PropagationByResource<>();
134 propByRes.add(ResourceOperation.DELETE, profile.getTask().getResource().getKey());
135 propByRes.addOldConnObjectKey(profile.getTask().getResource().getKey(), beforeObj.getUid().getUidValue());
136
137 List<PropagationTaskInfo> taskInfos = propagationManager.getDeleteTasks(
138 any.getType().getKind(),
139 any.getKey(),
140 propByRes,
141 null,
142 noPropResources);
143 if (!taskInfos.isEmpty()) {
144 taskInfos.get(0).setBeforeObj(Optional.of(beforeObj));
145 PropagationReporter reporter = new DefaultPropagationReporter();
146 taskExecutor.execute(taskInfos.get(0), reporter, securityProperties.getAdminUser());
147 reportPropagation(result, reporter);
148 }
149 }
150
151 protected void provision(final Any<?> any, final Boolean enable, final ProvisioningReport result) {
152 AnyTO before = getAnyTO(any);
153
154 List<String> noPropResources = new ArrayList<>(before.getResources());
155 noPropResources.remove(profile.getTask().getResource().getKey());
156
157 PropagationByResource<String> propByRes = new PropagationByResource<>();
158 propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
159
160 List<PropagationTaskInfo> taskInfos = propagationManager.getCreateTasks(
161 any.getType().getKind(),
162 any.getKey(),
163 enable,
164 propByRes,
165 before.getVirAttrs(),
166 noPropResources);
167 if (!taskInfos.isEmpty()) {
168 taskInfos.get(0).setBeforeObj(Optional.empty());
169 PropagationReporter reporter = new DefaultPropagationReporter();
170 taskExecutor.execute(taskInfos.get(0), reporter, securityProperties.getAdminUser());
171 reportPropagation(result, reporter);
172 }
173 }
174
175 protected void link(final Any<?> any, final boolean unlink, final ProvisioningReport result) {
176 AnyUR req = getAnyUtils().newAnyUR(any.getKey());
177 req.getResources().add(new StringPatchItem.Builder().
178 operation(unlink ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
179 value(profile.getTask().getResource().getKey()).build());
180
181 update(req);
182
183 result.setStatus(ProvisioningReport.Status.SUCCESS);
184 }
185
186 protected void unassign(final Any<?> any, final ConnectorObject beforeObj, final ProvisioningReport result) {
187 AnyUR req = getAnyUtils().newAnyUR(any.getKey());
188 req.getResources().add(new StringPatchItem.Builder().
189 operation(PatchOperation.DELETE).
190 value(profile.getTask().getResource().getKey()).build());
191
192 update(req);
193
194 deprovision(any, beforeObj, result);
195 }
196
197 protected void assign(final Any<?> any, final Boolean enabled, final ProvisioningReport result) {
198 AnyUR req = getAnyUtils().newAnyUR(any.getKey());
199 req.getResources().add(new StringPatchItem.Builder().
200 operation(PatchOperation.ADD_REPLACE).
201 value(profile.getTask().getResource().getKey()).build());
202
203 update(req);
204
205 provision(any, enabled, result);
206 }
207
208 @Transactional(propagation = Propagation.REQUIRES_NEW)
209 @Override
210 public boolean handle(final String anyKey) {
211 Any<?> any = null;
212 try {
213 any = getAnyUtils().dao().authFind(anyKey);
214
215 Provision provision = profile.getTask().getResource().
216 getProvisionByAnyType(any.getType().getKey()).orElse(null);
217 if (provision == null) {
218 throw new JobExecutionException("No provision found on " + profile.getTask().getResource() + " for "
219 + any.getType().getKey());
220 }
221
222 doHandle(any, provision);
223 return true;
224 } catch (IgnoreProvisionException e) {
225 ProvisioningReport ignoreResult = profile.getResults().stream().
226 filter(report -> anyKey.equalsIgnoreCase(report.getKey())).
227 findFirst().
228 orElse(null);
229 if (ignoreResult == null) {
230 ignoreResult = new ProvisioningReport();
231 ignoreResult.setKey(anyKey);
232 ignoreResult.setAnyType(Optional.ofNullable(any).map(any1 -> any1.getType().getKey()).orElse(null));
233
234 profile.getResults().add(ignoreResult);
235 }
236
237 ignoreResult.setOperation(ResourceOperation.NONE);
238 ignoreResult.setStatus(ProvisioningReport.Status.IGNORE);
239 ignoreResult.setMessage(e.getMessage());
240
241 LOG.warn("Ignoring during push", e);
242 return true;
243 } catch (JobExecutionException e) {
244 LOG.error("Push failed", e);
245 return false;
246 }
247 }
248
249 protected void doHandle(final Any<?> any, final Provision provision) throws JobExecutionException {
250 ProvisioningReport result = new ProvisioningReport();
251 profile.getResults().add(result);
252
253 result.setKey(any.getKey());
254 result.setAnyType(any.getType().getKey());
255 result.setName(getName(any));
256
257 LOG.debug("Pushing {} with key {} towards {}",
258 any.getType().getKind(), any.getKey(), profile.getTask().getResource());
259
260
261 Set<String> moreAttrsToGet = new HashSet<>();
262 profile.getActions().forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, any)));
263 List<ConnectorObject> connObjs = outboundMatcher.match(
264 profile.getConnector(),
265 any,
266 profile.getTask().getResource(),
267 provision,
268 Optional.of(moreAttrsToGet.toArray(String[]::new)));
269 LOG.debug("Match(es) found for {} as {}: {}", any, provision.getObjectClass(), connObjs);
270
271 if (connObjs.size() > 1) {
272 switch (profile.getConflictResolutionAction()) {
273 case IGNORE:
274 throw new IgnoreProvisionException("More than one match found for "
275 + any.getKey() + ": " + connObjs);
276
277 case FIRSTMATCH:
278 connObjs = connObjs.subList(0, 1);
279 break;
280
281 case LASTMATCH:
282 connObjs = connObjs.subList(connObjs.size() - 1, connObjs.size());
283 break;
284
285 default:
286 }
287 }
288 ConnectorObject beforeObj = connObjs.isEmpty() ? null : connObjs.get(0);
289
290 if (profile.isDryRun()) {
291 if (beforeObj == null) {
292 result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule()));
293 } else {
294 result.setOperation(toResourceOperation(profile.getTask().getMatchingRule()));
295 }
296 result.setStatus(ProvisioningReport.Status.SUCCESS);
297 } else {
298 String operation = beforeObj == null
299 ? UnmatchingRule.toEventName(profile.getTask().getUnmatchingRule())
300 : MatchingRule.toEventName(profile.getTask().getMatchingRule());
301
302 boolean notificationsAvailable = notificationManager.notificationsAvailable(
303 AuditElements.EventCategoryType.PUSH,
304 any.getType().getKind().name(),
305 profile.getTask().getResource().getKey(),
306 operation);
307 boolean auditRequested = auditManager.auditRequested(
308 AuthContextUtils.getUsername(),
309 AuditElements.EventCategoryType.PUSH,
310 any.getType().getKind().name(),
311 profile.getTask().getResource().getKey(),
312 operation);
313
314 Object output = null;
315 Result resultStatus = null;
316
317 Boolean enable = any instanceof User && profile.getTask().isSyncStatus()
318 ? BooleanUtils.negate(((User) any).isSuspended())
319 : null;
320 try {
321 if (beforeObj == null) {
322 result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule()));
323
324 switch (profile.getTask().getUnmatchingRule()) {
325 case ASSIGN:
326 for (PushActions action : profile.getActions()) {
327 action.beforeAssign(profile, any);
328 }
329
330 if (!profile.getTask().isPerformCreate()) {
331 LOG.debug("PushTask not configured for create");
332 result.setStatus(ProvisioningReport.Status.IGNORE);
333 } else {
334 assign(any, enable, result);
335 }
336 break;
337
338 case PROVISION:
339 for (PushActions action : profile.getActions()) {
340 action.beforeProvision(profile, any);
341 }
342
343 if (!profile.getTask().isPerformCreate()) {
344 LOG.debug("PushTask not configured for create");
345 result.setStatus(ProvisioningReport.Status.IGNORE);
346 } else {
347 provision(any, enable, result);
348 }
349 break;
350
351 case UNLINK:
352 for (PushActions action : profile.getActions()) {
353 action.beforeUnlink(profile, any);
354 }
355
356 if (!profile.getTask().isPerformUpdate()) {
357 LOG.debug("PushTask not configured for update");
358 result.setStatus(ProvisioningReport.Status.IGNORE);
359 } else {
360 link(any, true, result);
361 }
362 break;
363
364 case IGNORE:
365 LOG.debug("Ignored any: {}", any);
366 result.setStatus(ProvisioningReport.Status.IGNORE);
367 break;
368
369 default:
370
371 }
372 } else {
373 result.setOperation(toResourceOperation(profile.getTask().getMatchingRule()));
374
375 switch (profile.getTask().getMatchingRule()) {
376 case UPDATE:
377 for (PushActions action : profile.getActions()) {
378 action.beforeUpdate(profile, any);
379 }
380 if (!profile.getTask().isPerformUpdate()) {
381 LOG.debug("PushTask not configured for update");
382 result.setStatus(ProvisioningReport.Status.IGNORE);
383 } else {
384 update(any, enable, beforeObj, result);
385 }
386 break;
387
388 case DEPROVISION:
389 for (PushActions action : profile.getActions()) {
390 action.beforeDeprovision(profile, any);
391 }
392
393 if (!profile.getTask().isPerformDelete()) {
394 LOG.debug("PushTask not configured for delete");
395 result.setStatus(ProvisioningReport.Status.IGNORE);
396 } else {
397 deprovision(any, beforeObj, result);
398 }
399 break;
400
401 case UNASSIGN:
402 for (PushActions action : profile.getActions()) {
403 action.beforeUnassign(profile, any);
404 }
405
406 if (!profile.getTask().isPerformDelete()) {
407 LOG.debug("PushTask not configured for delete");
408 result.setStatus(ProvisioningReport.Status.IGNORE);
409 } else {
410 unassign(any, beforeObj, result);
411 }
412 break;
413
414 case LINK:
415 for (PushActions action : profile.getActions()) {
416 action.beforeLink(profile, any);
417 }
418
419 if (!profile.getTask().isPerformUpdate()) {
420 LOG.debug("PushTask not configured for update");
421 result.setStatus(ProvisioningReport.Status.IGNORE);
422 } else {
423 link(any, false, result);
424 }
425 break;
426
427 case UNLINK:
428 for (PushActions action : profile.getActions()) {
429 action.beforeUnlink(profile, any);
430 }
431
432 if (!profile.getTask().isPerformUpdate()) {
433 LOG.debug("PushTask not configured for update");
434 result.setStatus(ProvisioningReport.Status.IGNORE);
435 } else {
436 link(any, true, result);
437 }
438
439 break;
440
441 case IGNORE:
442 LOG.debug("Ignored any: {}", any);
443 result.setStatus(ProvisioningReport.Status.IGNORE);
444 break;
445
446 default:
447
448 }
449 }
450
451 for (PushActions action : profile.getActions()) {
452 action.after(profile, any, result);
453 }
454
455 if (result.getStatus() == null) {
456 result.setStatus(ProvisioningReport.Status.SUCCESS);
457 }
458
459 if (notificationsAvailable || auditRequested) {
460 resultStatus = AuditElements.Result.SUCCESS;
461 output = outboundMatcher.match(
462 profile.getConnector(),
463 any,
464 profile.getTask().getResource(),
465 provision,
466 Optional.of(moreAttrsToGet.toArray(String[]::new)));
467 }
468 } catch (IgnoreProvisionException e) {
469 throw e;
470 } catch (Exception e) {
471 result.setStatus(ProvisioningReport.Status.FAILURE);
472 result.setMessage(ExceptionUtils.getRootCauseMessage(e));
473
474 if (notificationsAvailable || auditRequested) {
475 resultStatus = AuditElements.Result.FAILURE;
476 output = e;
477 }
478
479 LOG.warn("Error pushing {} towards {}", any, profile.getTask().getResource(), e);
480
481 for (PushActions action : profile.getActions()) {
482 action.onError(profile, any, result, e);
483 }
484
485 throw new JobExecutionException(e);
486 } finally {
487 if (notificationsAvailable || auditRequested) {
488 Map<String, Object> jobMap = new HashMap<>();
489 jobMap.put(AfterHandlingEvent.JOBMAP_KEY, new AfterHandlingEvent(
490 AuthContextUtils.getWho(),
491 AuditElements.EventCategoryType.PUSH,
492 any.getType().getKind().name(),
493 profile.getTask().getResource().getKey(),
494 operation,
495 resultStatus,
496 beforeObj,
497 output,
498 any));
499 AfterHandlingJob.schedule(scheduler, jobMap);
500 }
501 }
502 }
503 }
504
505 protected static void reportPropagation(final ProvisioningReport result, final PropagationReporter reporter) {
506 if (!reporter.getStatuses().isEmpty()) {
507 result.setStatus(toProvisioningReportStatus(reporter.getStatuses().get(0).getStatus()));
508 result.setMessage(reporter.getStatuses().get(0).getFailureReason());
509 }
510 }
511
512 protected static ResourceOperation toResourceOperation(final UnmatchingRule rule) {
513 switch (rule) {
514 case ASSIGN:
515 case PROVISION:
516 return ResourceOperation.CREATE;
517 default:
518 return ResourceOperation.NONE;
519 }
520 }
521
522 protected static ResourceOperation toResourceOperation(final MatchingRule rule) {
523 switch (rule) {
524 case UPDATE:
525 return ResourceOperation.UPDATE;
526 case DEPROVISION:
527 case UNASSIGN:
528 return ResourceOperation.DELETE;
529 default:
530 return ResourceOperation.NONE;
531 }
532 }
533
534 protected static ProvisioningReport.Status toProvisioningReportStatus(final ExecStatus status) {
535 switch (status) {
536 case FAILURE:
537 return ProvisioningReport.Status.FAILURE;
538
539 case SUCCESS:
540 return ProvisioningReport.Status.SUCCESS;
541
542 case CREATED:
543 case NOT_ATTEMPTED:
544 default:
545 return ProvisioningReport.Status.IGNORE;
546 }
547 }
548 }