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.ArrayList;
22  import java.util.List;
23  import java.util.Optional;
24  import java.util.stream.Collectors;
25  import org.apache.commons.lang3.BooleanUtils;
26  import org.apache.commons.lang3.exception.ExceptionUtils;
27  import org.apache.commons.lang3.tuple.Pair;
28  import org.apache.syncope.common.lib.request.AnyUR;
29  import org.apache.syncope.common.lib.request.UserUR;
30  import org.apache.syncope.common.lib.to.AnyTO;
31  import org.apache.syncope.common.lib.to.Provision;
32  import org.apache.syncope.common.lib.to.ProvisioningReport;
33  import org.apache.syncope.common.lib.types.AnyTypeKind;
34  import org.apache.syncope.common.lib.types.MatchType;
35  import org.apache.syncope.common.lib.types.MatchingRule;
36  import org.apache.syncope.common.lib.types.ResourceOperation;
37  import org.apache.syncope.common.lib.types.UnmatchingRule;
38  import org.apache.syncope.core.persistence.api.entity.Any;
39  import org.apache.syncope.core.persistence.api.entity.AnyUtils;
40  import org.apache.syncope.core.persistence.api.entity.ExternalResource;
41  import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
42  import org.apache.syncope.core.persistence.api.entity.user.User;
43  import org.apache.syncope.core.provisioning.api.PropagationByResource;
44  import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
45  import org.apache.syncope.core.provisioning.api.WorkflowResult;
46  import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
47  import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
48  import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
49  import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
50  import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
51  import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter;
52  import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
53  import org.identityconnectors.framework.common.objects.ConnectorObject;
54  import org.quartz.JobExecutionException;
55  import org.springframework.transaction.annotation.Propagation;
56  import org.springframework.transaction.annotation.Transactional;
57  
58  public class DefaultUserPushResultHandler extends AbstractPushResultHandler implements UserPushResultHandler {
59  
60      @Override
61      protected AnyUtils getAnyUtils() {
62          return anyUtilsFactory.getInstance(AnyTypeKind.USER);
63      }
64  
65      @Override
66      protected String getName(final Any<?> any) {
67          return User.class.cast(any).getUsername();
68      }
69  
70      @Override
71      protected AnyTO getAnyTO(final Any<?> any) {
72          return userDataBinder.getUserTO((User) any, true);
73      }
74  
75      @Override
76      protected void provision(final Any<?> any, final Boolean enabled, final ProvisioningReport result) {
77          AnyTO before = getAnyTO(any);
78  
79          List<String> noPropResources = new ArrayList<>(before.getResources());
80          noPropResources.remove(profile.getTask().getResource().getKey());
81  
82          PropagationByResource<String> propByRes = new PropagationByResource<>();
83          propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
84  
85          PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
86          ((User) any).getLinkedAccounts(profile.getTask().getResource().getKey()).
87                  forEach(account -> propByLinkedAccount.add(
88                  ResourceOperation.CREATE,
89                  Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
90  
91          PropagationReporter reporter = taskExecutor.execute(propagationManager.getUserCreateTasks(
92                  before.getKey(),
93                  null,
94                  enabled,
95                  propByRes,
96                  propByLinkedAccount,
97                  before.getVirAttrs(),
98                  noPropResources),
99                  false,
100                 profile.getExecutor());
101         reportPropagation(result, reporter);
102     }
103 
104     @Override
105     protected void update(
106             final Any<?> any,
107             final Boolean enable,
108             final ConnectorObject beforeObj,
109             final ProvisioningReport result) {
110 
111         List<String> ownedResources = getAnyUtils().getAllResources(any).stream().
112                 map(ExternalResource::getKey).collect(Collectors.toList());
113 
114         List<String> noPropResources = new ArrayList<>(ownedResources);
115         noPropResources.remove(profile.getTask().getResource().getKey());
116 
117         PropagationByResource<String> propByRes = new PropagationByResource<>();
118         propByRes.add(ResourceOperation.UPDATE, profile.getTask().getResource().getKey());
119         propByRes.addOldConnObjectKey(profile.getTask().getResource().getKey(), beforeObj.getUid().getUidValue());
120 
121         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
122         ((User) any).getLinkedAccounts(profile.getTask().getResource().getKey()).
123                 forEach(account -> propByLinkedAccount.add(
124                 ResourceOperation.UPDATE,
125                 Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
126 
127         List<PropagationTaskInfo> taskInfos = propagationManager.getUpdateTasks(
128                 any.getType().getKind(),
129                 any.getKey(),
130                 true,
131                 enable,
132                 propByRes,
133                 propByLinkedAccount,
134                 null,
135                 noPropResources);
136         if (!taskInfos.isEmpty()) {
137             taskInfos.get(0).setBeforeObj(Optional.of(beforeObj));
138             PropagationReporter reporter = new DefaultPropagationReporter();
139             taskExecutor.execute(taskInfos.get(0), reporter, profile.getExecutor());
140             reportPropagation(result, reporter);
141         }
142     }
143 
144     @Override
145     protected void deprovision(final Any<?> any, final ConnectorObject beforeObj, final ProvisioningReport result) {
146         AnyTO before = getAnyTO(any);
147 
148         List<String> noPropResources = new ArrayList<>(before.getResources());
149         noPropResources.remove(profile.getTask().getResource().getKey());
150 
151         PropagationByResource<String> propByRes = new PropagationByResource<>();
152         propByRes.add(ResourceOperation.DELETE, profile.getTask().getResource().getKey());
153         propByRes.addOldConnObjectKey(profile.getTask().getResource().getKey(), beforeObj.getUid().getUidValue());
154 
155         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
156         ((User) any).getLinkedAccounts(profile.getTask().getResource().getKey()).
157                 forEach(account -> propByLinkedAccount.add(
158                 ResourceOperation.DELETE,
159                 Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
160 
161         List<PropagationTaskInfo> taskInfos = propagationManager.getDeleteTasks(
162                 any.getType().getKind(),
163                 any.getKey(),
164                 propByRes,
165                 propByLinkedAccount,
166                 noPropResources);
167         if (!taskInfos.isEmpty()) {
168             taskInfos.get(0).setBeforeObj(Optional.of(beforeObj));
169             PropagationReporter reporter = new DefaultPropagationReporter();
170             taskExecutor.execute(taskInfos.get(0), reporter, profile.getExecutor());
171             reportPropagation(result, reporter);
172         }
173     }
174 
175     @Override
176     protected WorkflowResult<? extends AnyUR> update(final AnyUR req) {
177         WorkflowResult<Pair<UserUR, Boolean>> update =
178                 uwfAdapter.update((UserUR) req, profile.getExecutor(), getContext());
179         return new WorkflowResult<>(update.getResult().getLeft(), update.getPropByRes(), update.getPerformedTasks());
180     }
181 
182     @Transactional(propagation = Propagation.REQUIRES_NEW)
183     @Override
184     public boolean handle(final LinkedAccount account, final Provision provision) {
185         try {
186             doHandle(account, provision);
187             return true;
188         } catch (IgnoreProvisionException e) {
189             ProvisioningReport ignoreResult = profile.getResults().stream().
190                     filter(report -> account.getKey().equalsIgnoreCase(report.getKey())).
191                     findFirst().
192                     orElse(null);
193             if (ignoreResult == null) {
194                 ignoreResult = new ProvisioningReport();
195                 ignoreResult.setKey(account.getKey());
196                 ignoreResult.setAnyType(MatchType.LINKED_ACCOUNT.name());
197                 ignoreResult.setUidValue(account.getConnObjectKeyValue());
198 
199                 profile.getResults().add(ignoreResult);
200             }
201 
202             ignoreResult.setOperation(ResourceOperation.NONE);
203             ignoreResult.setStatus(ProvisioningReport.Status.IGNORE);
204             ignoreResult.setMessage(e.getMessage());
205 
206             LOG.warn("Ignoring during push", e);
207             return true;
208         } catch (JobExecutionException e) {
209             LOG.error("Push failed", e);
210             return false;
211         }
212     }
213 
214     protected void doHandle(final LinkedAccount account, final Provision provision) throws JobExecutionException {
215         ProvisioningReport result = new ProvisioningReport();
216         profile.getResults().add(result);
217 
218         result.setKey(account.getKey());
219         result.setAnyType(MatchType.LINKED_ACCOUNT.name());
220         result.setUidValue(account.getConnObjectKeyValue());
221         result.setName(account.getConnObjectKeyValue());
222 
223         LOG.debug("Pushing linked account {} towards {}", account.getKey(), profile.getTask().getResource());
224 
225         // Try to read remote object BEFORE any actual operation
226         Optional<ConnectorObject> connObj = MappingUtils.getConnObjectKeyItem(provision).
227                 flatMap(connObjectKeyItem -> outboundMatcher.matchByConnObjectKeyValue(
228                 profile.getConnector(),
229                 connObjectKeyItem,
230                 account.getConnObjectKeyValue(),
231                 profile.getTask().getResource(),
232                 provision,
233                 Optional.empty(),
234                 Optional.empty()));
235         LOG.debug("Match found for linked account {} as {}: {}", account, provision.getObjectClass(), connObj);
236 
237         ConnectorObject beforeObj = connObj.orElse(null);
238 
239         if (profile.isDryRun()) {
240             if (beforeObj == null) {
241                 result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule()));
242             } else {
243                 result.setOperation(toResourceOperation(profile.getTask().getMatchingRule()));
244             }
245             result.setStatus(ProvisioningReport.Status.SUCCESS);
246         } else {
247             Boolean enable = profile.getTask().isSyncStatus()
248                     ? BooleanUtils.negate(account.isSuspended())
249                     : null;
250             try {
251                 if (beforeObj == null) {
252                     result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule()));
253 
254                     switch (profile.getTask().getUnmatchingRule()) {
255                         case ASSIGN:
256                         case PROVISION:
257                             for (PushActions action : profile.getActions()) {
258                                 if (profile.getTask().getUnmatchingRule() == UnmatchingRule.ASSIGN) {
259                                     action.beforeAssign(profile, account);
260                                 } else {
261                                     action.beforeProvision(profile, account);
262                                 }
263                             }
264 
265                             if (!profile.getTask().isPerformCreate()) {
266                                 LOG.debug("PushTask not configured for create");
267                                 result.setStatus(ProvisioningReport.Status.IGNORE);
268                             } else {
269                                 provision(account, enable, result);
270                             }
271                             break;
272 
273                         case UNLINK:
274                             LOG.warn("{} not applicable to linked accounts, ignoring",
275                                     profile.getTask().getUnmatchingRule());
276                             break;
277 
278                         case IGNORE:
279                             result.setStatus(ProvisioningReport.Status.IGNORE);
280                             break;
281 
282                         default:
283                         // do nothing
284                     }
285                 } else {
286                     result.setOperation(toResourceOperation(profile.getTask().getMatchingRule()));
287 
288                     switch (profile.getTask().getMatchingRule()) {
289                         case UPDATE:
290                             for (PushActions action : profile.getActions()) {
291                                 action.beforeUpdate(profile, account);
292                             }
293                             if (!profile.getTask().isPerformUpdate()) {
294                                 LOG.debug("PushTask not configured for update");
295                                 result.setStatus(ProvisioningReport.Status.IGNORE);
296                             } else {
297                                 update(account, enable, beforeObj, ResourceOperation.UPDATE, result);
298                             }
299                             break;
300 
301                         case UNASSIGN:
302                         case DEPROVISION:
303                             for (PushActions action : profile.getActions()) {
304                                 if (profile.getTask().getMatchingRule() == MatchingRule.UNASSIGN) {
305                                     action.beforeUnassign(profile, account);
306                                 } else {
307                                     action.beforeDeprovision(profile, account);
308                                 }
309                             }
310 
311                             if (!profile.getTask().isPerformDelete()) {
312                                 LOG.debug("PushTask not configured for delete");
313                                 result.setStatus(ProvisioningReport.Status.IGNORE);
314                             } else {
315                                 update(account, enable, beforeObj, ResourceOperation.DELETE, result);
316                             }
317                             break;
318 
319                         case LINK:
320                         case UNLINK:
321                             LOG.warn("{} not applicable to linked accounts, ignoring",
322                                     profile.getTask().getMatchingRule());
323                             break;
324 
325                         case IGNORE:
326                             result.setStatus(ProvisioningReport.Status.IGNORE);
327                             break;
328 
329                         default:
330                         // do nothing
331                     }
332                 }
333 
334                 for (PushActions action : profile.getActions()) {
335                     action.after(profile, account, result);
336                 }
337 
338                 if (result.getStatus() == null) {
339                     result.setStatus(ProvisioningReport.Status.SUCCESS);
340                 }
341             } catch (IgnoreProvisionException e) {
342                 throw e;
343             } catch (Exception e) {
344                 result.setStatus(ProvisioningReport.Status.FAILURE);
345                 result.setMessage(ExceptionUtils.getRootCauseMessage(e));
346 
347                 LOG.warn("Error pushing linked account {} towards {}", account, profile.getTask().getResource(), e);
348 
349                 for (PushActions action : profile.getActions()) {
350                     action.onError(profile, account, result, e);
351                 }
352 
353                 throw new JobExecutionException(e);
354             }
355         }
356     }
357 
358     protected void provision(
359             final LinkedAccount account,
360             final Boolean enable,
361             final ProvisioningReport result) {
362 
363         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
364         propByLinkedAccount.add(
365                 ResourceOperation.CREATE,
366                 Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()));
367 
368         List<PropagationTaskInfo> taskInfos = propagationManager.getUserCreateTasks(
369                 account.getOwner().getKey(),
370                 null,
371                 enable,
372                 new PropagationByResource<>(),
373                 propByLinkedAccount,
374                 null,
375                 null);
376         if (!taskInfos.isEmpty()) {
377             taskInfos.get(0).setBeforeObj(Optional.empty());
378             PropagationReporter reporter = new DefaultPropagationReporter();
379             taskExecutor.execute(taskInfos.get(0), reporter, profile.getExecutor());
380             reportPropagation(result, reporter);
381         }
382     }
383 
384     protected void update(
385             final LinkedAccount account,
386             final Boolean enable,
387             final ConnectorObject beforeObj,
388             final ResourceOperation operation,
389             final ProvisioningReport result) {
390 
391         UserUR req = new UserUR();
392         req.setKey(account.getOwner().getKey());
393 
394         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
395         propByLinkedAccount.add(operation, Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()));
396 
397         List<PropagationTaskInfo> taskInfos = propagationManager.getUserUpdateTasks(
398                 new UserWorkflowResult<>(
399                         Pair.of(req, enable),
400                         new PropagationByResource<>(),
401                         propByLinkedAccount,
402                         ""));
403         if (!taskInfos.isEmpty()) {
404             taskInfos.get(0).setBeforeObj(Optional.empty());
405             PropagationReporter reporter = new DefaultPropagationReporter();
406             taskExecutor.execute(taskInfos.get(0), reporter, profile.getExecutor());
407             reportPropagation(result, reporter);
408         }
409     }
410 }