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.List;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.Set;
27 import java.util.stream.Stream;
28 import org.apache.commons.lang3.exception.ExceptionUtils;
29 import org.apache.commons.lang3.tuple.Pair;
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.ProvisioningReport;
34 import org.apache.syncope.common.lib.to.RealmTO;
35 import org.apache.syncope.common.lib.types.AuditElements;
36 import org.apache.syncope.common.lib.types.AuditElements.Result;
37 import org.apache.syncope.common.lib.types.ExecStatus;
38 import org.apache.syncope.common.lib.types.MatchingRule;
39 import org.apache.syncope.common.lib.types.ResourceOperation;
40 import org.apache.syncope.common.lib.types.UnmatchingRule;
41 import org.apache.syncope.core.persistence.api.entity.Realm;
42 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
43 import org.apache.syncope.core.provisioning.api.MappingManager;
44 import org.apache.syncope.core.provisioning.api.PropagationByResource;
45 import org.apache.syncope.core.provisioning.api.TimeoutException;
46 import org.apache.syncope.core.provisioning.api.event.AfterHandlingEvent;
47 import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
48 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
49 import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
50 import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
51 import org.apache.syncope.core.provisioning.api.pushpull.RealmPushResultHandler;
52 import org.apache.syncope.core.provisioning.java.job.AfterHandlingJob;
53 import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter;
54 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
55 import org.apache.syncope.core.spring.security.AuthContextUtils;
56 import org.identityconnectors.framework.common.objects.Attribute;
57 import org.identityconnectors.framework.common.objects.AttributeBuilder;
58 import org.identityconnectors.framework.common.objects.ConnectorObject;
59 import org.identityconnectors.framework.common.objects.ObjectClass;
60 import org.quartz.JobExecutionException;
61 import org.springframework.beans.factory.annotation.Autowired;
62 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
63 import org.springframework.transaction.annotation.Propagation;
64 import org.springframework.transaction.annotation.Transactional;
65
66 public class DefaultRealmPushResultHandler
67 extends AbstractRealmResultHandler<PushTask, PushActions>
68 implements RealmPushResultHandler {
69
70 @Autowired
71 private MappingManager mappingManager;
72
73 @Autowired
74 private SchedulerFactoryBean scheduler;
75
76 @Transactional(propagation = Propagation.REQUIRES_NEW)
77 @Override
78 public boolean handle(final String realmKey) {
79 Realm realm = null;
80 try {
81 realm = realmDAO.find(realmKey);
82 doHandle(realm);
83 return true;
84 } catch (IgnoreProvisionException e) {
85 ProvisioningReport result = new ProvisioningReport();
86 result.setOperation(ResourceOperation.NONE);
87 result.setAnyType(realm == null ? null : SyncopeConstants.REALM_ANYTYPE);
88 result.setStatus(ProvisioningReport.Status.IGNORE);
89 result.setKey(realmKey);
90 profile.getResults().add(result);
91
92 LOG.warn("Ignoring during push", e);
93 return true;
94 } catch (JobExecutionException e) {
95 LOG.error("Push failed", e);
96 return false;
97 }
98 }
99
100 private static void reportPropagation(final ProvisioningReport result, final PropagationReporter reporter) {
101 if (!reporter.getStatuses().isEmpty()) {
102 result.setStatus(toProvisioningReportStatus(reporter.getStatuses().get(0).getStatus()));
103 result.setMessage(reporter.getStatuses().get(0).getFailureReason());
104 }
105 }
106
107 private Realm update(final RealmTO realmTO, final ConnectorObject beforeObj, final ProvisioningReport result) {
108 Realm realm = realmDAO.findByFullPath(realmTO.getFullPath());
109
110 Map<Pair<String, String>, Set<Attribute>> beforeAttrs = propagationManager.prepareAttrs(realm);
111
112 PropagationByResource<String> propByRes = binder.update(realm, realmTO);
113 realm = realmDAO.save(realm);
114
115 List<PropagationTaskInfo> taskInfos = propagationManager.setAttributeDeltas(
116 propagationManager.createTasks(realm, propByRes, null),
117 beforeAttrs,
118 null);
119 if (!taskInfos.isEmpty()) {
120 taskInfos.get(0).setBeforeObj(Optional.ofNullable(beforeObj));
121 PropagationReporter reporter = new DefaultPropagationReporter();
122 taskExecutor.execute(taskInfos.get(0), reporter, securityProperties.getAdminUser());
123 reportPropagation(result, reporter);
124 }
125
126 return realm;
127 }
128
129 private void deprovision(final Realm realm, final ConnectorObject beforeObj, final ProvisioningReport result) {
130 List<String> noPropResources = new ArrayList<>(realm.getResourceKeys());
131 noPropResources.remove(profile.getTask().getResource().getKey());
132
133 PropagationByResource<String> propByRes = new PropagationByResource<>();
134 propByRes.addAll(ResourceOperation.DELETE, realm.getResourceKeys());
135
136 List<PropagationTaskInfo> taskInfos = propagationManager.createTasks(realm, propByRes, noPropResources);
137 if (!taskInfos.isEmpty()) {
138 taskInfos.get(0).setBeforeObj(Optional.ofNullable(beforeObj));
139 PropagationReporter reporter = new DefaultPropagationReporter();
140 taskExecutor.execute(taskInfos.get(0), reporter, securityProperties.getAdminUser());
141 reportPropagation(result, reporter);
142 }
143 }
144
145 private void provision(final Realm realm, final ProvisioningReport result) {
146 List<String> noPropResources = new ArrayList<>(realm.getResourceKeys());
147 noPropResources.remove(profile.getTask().getResource().getKey());
148
149 PropagationByResource<String> propByRes = new PropagationByResource<>();
150 propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
151
152 PropagationReporter reporter = taskExecutor.execute(
153 propagationManager.createTasks(realm, propByRes, noPropResources),
154 false, securityProperties.getAdminUser());
155 reportPropagation(result, reporter);
156 }
157
158 private void link(final Realm realm, final boolean unlink, final ProvisioningReport result) {
159 RealmTO realmTO = binder.getRealmTO(realm, true);
160 if (unlink) {
161 realmTO.getResources().remove(profile.getTask().getResource().getKey());
162 } else {
163 realmTO.getResources().add(profile.getTask().getResource().getKey());
164 }
165
166 update(realmTO, null, result);
167 }
168
169 private void unassign(final Realm realm, final ConnectorObject beforeObj, final ProvisioningReport result) {
170 RealmTO realmTO = binder.getRealmTO(realm, true);
171 realmTO.getResources().remove(profile.getTask().getResource().getKey());
172
173 deprovision(update(realmTO, beforeObj, result), beforeObj, result);
174 }
175
176 private void assign(final Realm realm, final ProvisioningReport result) {
177 RealmTO realmTO = binder.getRealmTO(realm, true);
178 realmTO.getResources().add(profile.getTask().getResource().getKey());
179
180 provision(update(realmTO, null, result), result);
181 }
182
183 protected ConnectorObject getRemoteObject(
184 final ObjectClass objectClass,
185 final String connObjectKey,
186 final String connObjectKeyValue,
187 final boolean ignoreCaseMatch,
188 final Stream<Item> mapItems) {
189
190 ConnectorObject obj = null;
191 try {
192 obj = profile.getConnector().getObject(
193 objectClass,
194 AttributeBuilder.build(connObjectKey, connObjectKeyValue),
195 ignoreCaseMatch,
196 MappingUtils.buildOperationOptions(mapItems));
197 } catch (TimeoutException toe) {
198 LOG.debug("Request timeout", toe);
199 throw toe;
200 } catch (RuntimeException ignore) {
201 LOG.debug("While resolving {}", connObjectKeyValue, ignore);
202 }
203
204 return obj;
205 }
206
207 private void doHandle(final Realm realm) throws JobExecutionException {
208 ProvisioningReport result = new ProvisioningReport();
209 profile.getResults().add(result);
210
211 result.setKey(realm.getKey());
212 result.setAnyType(SyncopeConstants.REALM_ANYTYPE);
213 result.setName(realm.getFullPath());
214
215 LOG.debug("Propagating Realm with key {} towards {}", realm.getKey(), profile.getTask().getResource());
216
217 Object output = null;
218 Result resultStatus = null;
219
220
221 OrgUnit orgUnit = profile.getTask().getResource().getOrgUnit();
222 Optional<Item> connObjectKey = orgUnit.getConnObjectKeyItem();
223 Optional<String> connObjecKeyValue = mappingManager.getConnObjectKeyValue(realm, orgUnit);
224
225 ConnectorObject beforeObj = null;
226 if (connObjectKey.isPresent() && connObjecKeyValue.isPresent()) {
227 beforeObj = getRemoteObject(
228 new ObjectClass(orgUnit.getObjectClass()),
229 connObjectKey.get().getExtAttrName(),
230 connObjecKeyValue.get(),
231 orgUnit.isIgnoreCaseMatch(),
232 orgUnit.getItems().stream());
233 } else {
234 LOG.debug("OrgUnitItem {} or its value {} are null", connObjectKey, connObjecKeyValue);
235 }
236
237 if (profile.isDryRun()) {
238 if (beforeObj == null) {
239 result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule()));
240 } else {
241 result.setOperation(toResourceOperation(profile.getTask().getMatchingRule()));
242 }
243 result.setStatus(ProvisioningReport.Status.SUCCESS);
244 } else {
245 String operation = beforeObj == null
246 ? UnmatchingRule.toEventName(profile.getTask().getUnmatchingRule())
247 : MatchingRule.toEventName(profile.getTask().getMatchingRule());
248
249 boolean notificationsAvailable = notificationManager.notificationsAvailable(
250 AuditElements.EventCategoryType.PUSH,
251 SyncopeConstants.REALM_ANYTYPE.toLowerCase(),
252 profile.getTask().getResource().getKey(),
253 operation);
254 boolean auditRequested = auditManager.auditRequested(
255 AuthContextUtils.getUsername(),
256 AuditElements.EventCategoryType.PUSH,
257 SyncopeConstants.REALM_ANYTYPE.toLowerCase(),
258 profile.getTask().getResource().getKey(),
259 operation);
260 try {
261 if (beforeObj == null) {
262 result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule()));
263
264 switch (profile.getTask().getUnmatchingRule()) {
265 case ASSIGN:
266 for (PushActions action : profile.getActions()) {
267 action.beforeAssign(profile, realm);
268 }
269
270 if (!profile.getTask().isPerformCreate()) {
271 LOG.debug("PushTask not configured for create");
272 result.setStatus(ProvisioningReport.Status.IGNORE);
273 } else {
274 assign(realm, result);
275 }
276
277 break;
278
279 case PROVISION:
280 for (PushActions action : profile.getActions()) {
281 action.beforeProvision(profile, realm);
282 }
283
284 if (!profile.getTask().isPerformCreate()) {
285 LOG.debug("PushTask not configured for create");
286 result.setStatus(ProvisioningReport.Status.IGNORE);
287 } else {
288 provision(realm, result);
289 }
290
291 break;
292
293 case UNLINK:
294 for (PushActions action : profile.getActions()) {
295 action.beforeUnlink(profile, realm);
296 }
297
298 if (!profile.getTask().isPerformUpdate()) {
299 LOG.debug("PushTask not configured for update");
300 result.setStatus(ProvisioningReport.Status.IGNORE);
301 } else {
302 link(realm, true, result);
303 }
304
305 break;
306
307 case IGNORE:
308 LOG.debug("Ignored any: {}", realm);
309 result.setStatus(ProvisioningReport.Status.IGNORE);
310 break;
311
312 default:
313
314 }
315 } else {
316 result.setOperation(toResourceOperation(profile.getTask().getMatchingRule()));
317
318 switch (profile.getTask().getMatchingRule()) {
319 case UPDATE:
320 for (PushActions action : profile.getActions()) {
321 action.beforeUpdate(profile, realm);
322 }
323 if (!profile.getTask().isPerformUpdate()) {
324 LOG.debug("PushTask not configured for update");
325 result.setStatus(ProvisioningReport.Status.IGNORE);
326 } else {
327 update(binder.getRealmTO(realm, true), beforeObj, result);
328 }
329
330 break;
331
332 case DEPROVISION:
333 for (PushActions action : profile.getActions()) {
334 action.beforeDeprovision(profile, realm);
335 }
336
337 if (!profile.getTask().isPerformDelete()) {
338 LOG.debug("PushTask not configured for delete");
339 result.setStatus(ProvisioningReport.Status.IGNORE);
340 } else {
341 deprovision(realm, beforeObj, result);
342 }
343
344 break;
345
346 case UNASSIGN:
347 for (PushActions action : profile.getActions()) {
348 action.beforeUnassign(profile, realm);
349 }
350
351 if (!profile.getTask().isPerformDelete()) {
352 LOG.debug("PushTask not configured for delete");
353 result.setStatus(ProvisioningReport.Status.IGNORE);
354 } else {
355 unassign(realm, beforeObj, result);
356 }
357
358 break;
359
360 case LINK:
361 for (PushActions action : profile.getActions()) {
362 action.beforeLink(profile, realm);
363 }
364
365 if (!profile.getTask().isPerformUpdate()) {
366 LOG.debug("PushTask not configured for update");
367 result.setStatus(ProvisioningReport.Status.IGNORE);
368 } else {
369 link(realm, false, result);
370 }
371
372 break;
373
374 case UNLINK:
375 for (PushActions action : profile.getActions()) {
376 action.beforeUnlink(profile, realm);
377 }
378
379 if (!profile.getTask().isPerformUpdate()) {
380 LOG.debug("PushTask not configured for update");
381 result.setStatus(ProvisioningReport.Status.IGNORE);
382 } else {
383 link(realm, true, result);
384 }
385
386 break;
387
388 case IGNORE:
389 LOG.debug("Ignored any: {}", realm);
390 result.setStatus(ProvisioningReport.Status.IGNORE);
391 break;
392
393 default:
394
395 }
396 }
397
398 for (PushActions action : profile.getActions()) {
399 action.after(profile, realm, result);
400 }
401
402 if (result.getStatus() == null) {
403 result.setStatus(ProvisioningReport.Status.SUCCESS);
404 }
405
406 if (notificationsAvailable || auditRequested) {
407 resultStatus = AuditElements.Result.SUCCESS;
408 if (connObjectKey.isPresent() && connObjecKeyValue.isPresent()) {
409 output = getRemoteObject(
410 new ObjectClass(orgUnit.getObjectClass()),
411 connObjectKey.get().getExtAttrName(),
412 connObjecKeyValue.get(),
413 orgUnit.isIgnoreCaseMatch(),
414 orgUnit.getItems().stream());
415 }
416 }
417 } catch (IgnoreProvisionException e) {
418 throw e;
419 } catch (Exception e) {
420 result.setStatus(ProvisioningReport.Status.FAILURE);
421 result.setMessage(ExceptionUtils.getRootCauseMessage(e));
422
423 if (notificationsAvailable || auditRequested) {
424 resultStatus = AuditElements.Result.FAILURE;
425 output = e;
426 }
427
428 LOG.warn("Error pushing {} towards {}", realm, profile.getTask().getResource(), e);
429
430 for (PushActions action : profile.getActions()) {
431 action.onError(profile, realm, result, e);
432 }
433
434 throw new JobExecutionException(e);
435 } finally {
436 if (notificationsAvailable || auditRequested) {
437 Map<String, Object> jobMap = new HashMap<>();
438 jobMap.put(AfterHandlingEvent.JOBMAP_KEY, new AfterHandlingEvent(
439 AuthContextUtils.getWho(),
440 AuditElements.EventCategoryType.PUSH,
441 SyncopeConstants.REALM_ANYTYPE.toLowerCase(),
442 profile.getTask().getResource().getKey(),
443 operation,
444 resultStatus,
445 beforeObj,
446 output,
447 realm));
448 AfterHandlingJob.schedule(scheduler, jobMap);
449 }
450 }
451 }
452 }
453
454 private static ResourceOperation toResourceOperation(final UnmatchingRule rule) {
455 switch (rule) {
456 case ASSIGN:
457 case PROVISION:
458 return ResourceOperation.CREATE;
459 default:
460 return ResourceOperation.NONE;
461 }
462 }
463
464 private static ResourceOperation toResourceOperation(final MatchingRule rule) {
465 switch (rule) {
466 case UPDATE:
467 return ResourceOperation.UPDATE;
468 case DEPROVISION:
469 case UNASSIGN:
470 return ResourceOperation.DELETE;
471 default:
472 return ResourceOperation.NONE;
473 }
474 }
475
476 private static ProvisioningReport.Status toProvisioningReportStatus(final ExecStatus status) {
477 switch (status) {
478 case FAILURE:
479 return ProvisioningReport.Status.FAILURE;
480
481 case SUCCESS:
482 return ProvisioningReport.Status.SUCCESS;
483
484 case CREATED:
485 case NOT_ATTEMPTED:
486 default:
487 return ProvisioningReport.Status.IGNORE;
488 }
489 }
490 }