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;
20  
21  import java.util.Collection;
22  import java.util.HashSet;
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.Collectors;
28  import org.apache.commons.lang3.tuple.Pair;
29  import org.apache.syncope.common.lib.request.PasswordPatch;
30  import org.apache.syncope.common.lib.request.StatusR;
31  import org.apache.syncope.common.lib.request.StringPatchItem;
32  import org.apache.syncope.common.lib.request.UserCR;
33  import org.apache.syncope.common.lib.request.UserUR;
34  import org.apache.syncope.common.lib.to.PropagationStatus;
35  import org.apache.syncope.common.lib.to.ProvisioningReport;
36  import org.apache.syncope.common.lib.types.AnyTypeKind;
37  import org.apache.syncope.common.lib.types.PatchOperation;
38  import org.apache.syncope.common.lib.types.ResourceOperation;
39  import org.apache.syncope.common.lib.types.StatusRType;
40  import org.apache.syncope.core.persistence.api.dao.UserDAO;
41  import org.apache.syncope.core.persistence.api.entity.user.User;
42  import org.apache.syncope.core.provisioning.api.PropagationByResource;
43  import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
44  import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
45  import org.apache.syncope.core.provisioning.api.VirAttrHandler;
46  import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
47  import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
48  import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
49  import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
50  import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
51  import org.identityconnectors.framework.common.objects.Attribute;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  import org.springframework.transaction.annotation.Propagation;
55  import org.springframework.transaction.annotation.Transactional;
56  
57  public class DefaultUserProvisioningManager implements UserProvisioningManager {
58  
59      protected static final Logger LOG = LoggerFactory.getLogger(UserProvisioningManager.class);
60  
61      protected final UserWorkflowAdapter uwfAdapter;
62  
63      protected final PropagationManager propagationManager;
64  
65      protected final PropagationTaskExecutor taskExecutor;
66  
67      protected final UserDAO userDAO;
68  
69      protected final VirAttrHandler virtAttrHandler;
70  
71      public DefaultUserProvisioningManager(
72              final UserWorkflowAdapter uwfAdapter,
73              final PropagationManager propagationManager,
74              final PropagationTaskExecutor taskExecutor,
75              final UserDAO userDAO,
76              final VirAttrHandler virtAttrHandler) {
77  
78          this.uwfAdapter = uwfAdapter;
79          this.propagationManager = propagationManager;
80          this.taskExecutor = taskExecutor;
81          this.userDAO = userDAO;
82          this.virtAttrHandler = virtAttrHandler;
83      }
84  
85      @Override
86      public Pair<String, List<PropagationStatus>> create(
87              final UserCR userCR, final boolean nullPriorityAsync, final String creator, final String context) {
88  
89          return create(userCR, false, null, Set.of(), nullPriorityAsync, creator, context);
90      }
91  
92      @Transactional(propagation = Propagation.REQUIRES_NEW)
93      @Override
94      public Pair<String, List<PropagationStatus>> create(
95              final UserCR userCR,
96              final boolean disablePwdPolicyCheck,
97              final Boolean enabled,
98              final Set<String> excludedResources,
99              final boolean nullPriorityAsync,
100             final String creator,
101             final String context) {
102 
103         UserWorkflowResult<Pair<String, Boolean>> created =
104                 uwfAdapter.create(userCR, disablePwdPolicyCheck, enabled, creator, context);
105 
106         List<PropagationTaskInfo> taskInfos = propagationManager.getUserCreateTasks(
107                 created.getResult().getLeft(),
108                 userCR.getPassword(),
109                 created.getResult().getRight(),
110                 created.getPropByRes(),
111                 created.getPropByLinkedAccount(),
112                 userCR.getVirAttrs(),
113                 excludedResources);
114         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync, creator);
115 
116         return Pair.of(created.getResult().getLeft(), propagationReporter.getStatuses());
117     }
118 
119     @Override
120     public Pair<UserUR, List<PropagationStatus>> update(
121             final UserUR userUR, final boolean nullPriorityAsync, final String updater, final String context) {
122 
123         Map<Pair<String, String>, Set<Attribute>> beforeAttrs = propagationManager.prepareAttrs(
124                 AnyTypeKind.USER,
125                 userUR.getKey(),
126                 Optional.ofNullable(userUR.getPassword()).map(PasswordPatch::getValue).orElse(null),
127                 userUR.getPassword() != null,
128                 null,
129                 Set.of());
130 
131         UserWorkflowResult<Pair<UserUR, Boolean>> updated = uwfAdapter.update(userUR, updater, context);
132 
133         List<PropagationTaskInfo> taskInfos = propagationManager.setAttributeDeltas(
134                 propagationManager.getUserUpdateTasks(updated),
135                 beforeAttrs,
136                 updated.getResult().getLeft());
137         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync, updater);
138 
139         return Pair.of(updated.getResult().getLeft(), propagationReporter.getStatuses());
140     }
141 
142     @Override
143     public Pair<UserUR, List<PropagationStatus>> update(
144             final UserUR userUR,
145             final Set<String> excludedResources,
146             final boolean nullPriorityAsync,
147             final String updater,
148             final String context) {
149 
150         return update(userUR, new ProvisioningReport(), null, excludedResources, nullPriorityAsync, updater, context);
151     }
152 
153     @Transactional(propagation = Propagation.REQUIRES_NEW)
154     @Override
155     public Pair<UserUR, List<PropagationStatus>> update(
156             final UserUR userUR,
157             final ProvisioningReport result,
158             final Boolean enabled,
159             final Set<String> excludedResources,
160             final boolean nullPriorityAsync,
161             final String updater,
162             final String context) {
163 
164         Map<Pair<String, String>, Set<Attribute>> beforeAttrs = propagationManager.prepareAttrs(
165                 AnyTypeKind.USER,
166                 userUR.getKey(),
167                 Optional.ofNullable(userUR.getPassword()).map(PasswordPatch::getValue).orElse(null),
168                 userUR.getPassword() != null,
169                 enabled,
170                 excludedResources);
171 
172         UserWorkflowResult<Pair<UserUR, Boolean>> updated;
173         try {
174             updated = uwfAdapter.update(userUR, updater, context);
175         } catch (Exception e) {
176             LOG.error("Update of user {} failed, trying to pull its status anyway (if configured)",
177                     userUR.getKey(), e);
178 
179             result.setStatus(ProvisioningReport.Status.FAILURE);
180             result.setMessage("Update failed, trying to pull status anyway (if configured)\n" + e.getMessage());
181 
182             updated = new UserWorkflowResult<>(
183                     Pair.of(userUR, false),
184                     new PropagationByResource<>(),
185                     new PropagationByResource<>(),
186                     new HashSet<>());
187         }
188 
189         if (enabled != null) {
190             User user = userDAO.find(userUR.getKey());
191 
192             UserWorkflowResult<String> enableUpdate = null;
193             if (user.isSuspended() == null) {
194                 enableUpdate = uwfAdapter.activate(userUR.getKey(), null, updater, context);
195             } else if (enabled && user.isSuspended()) {
196                 enableUpdate = uwfAdapter.reactivate(userUR.getKey(), updater, context);
197             } else if (!enabled && !user.isSuspended()) {
198                 enableUpdate = uwfAdapter.suspend(userUR.getKey(), updater, context);
199             }
200 
201             if (enableUpdate != null) {
202                 if (enableUpdate.getPropByRes() != null) {
203                     updated.getPropByRes().merge(enableUpdate.getPropByRes());
204                     updated.getPropByRes().purge();
205                 }
206                 updated.getPerformedTasks().addAll(enableUpdate.getPerformedTasks());
207             }
208         }
209 
210         List<PropagationTaskInfo> taskInfos = propagationManager.setAttributeDeltas(
211                 propagationManager.getUserUpdateTasks(
212                         updated,
213                         updated.getResult().getLeft().getPassword() != null,
214                         excludedResources),
215                 beforeAttrs,
216                 updated.getResult().getLeft());
217         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync, updater);
218 
219         return Pair.of(updated.getResult().getLeft(), propagationReporter.getStatuses());
220     }
221 
222     @Override
223     public List<PropagationStatus> delete(
224             final String key, final boolean nullPriorityAsync, final String eraser, final String context) {
225 
226         return delete(key, Set.of(), nullPriorityAsync, eraser, context);
227     }
228 
229     @Transactional(propagation = Propagation.REQUIRES_NEW)
230     @Override
231     public List<PropagationStatus> delete(
232             final String key,
233             final Set<String> excludedResources,
234             final boolean nullPriorityAsync,
235             final String eraser,
236             final String context) {
237 
238         PropagationByResource<String> propByRes = new PropagationByResource<>();
239         propByRes.set(ResourceOperation.DELETE, userDAO.findAllResourceKeys(key));
240 
241         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
242         userDAO.findLinkedAccounts(key).forEach(account -> propByLinkedAccount.add(
243                 ResourceOperation.DELETE,
244                 Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
245 
246         // Note here that we can only notify about "delete", not any other
247         // task defined in workflow process definition: this because this
248         // information could only be available after uwfAdapter.delete(), which
249         // will also effectively remove user from db, thus making virtually
250         // impossible by NotificationManager to fetch required user information
251         List<PropagationTaskInfo> taskInfos = propagationManager.getDeleteTasks(
252                 AnyTypeKind.USER,
253                 key,
254                 propByRes,
255                 propByLinkedAccount,
256                 excludedResources);
257         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync, eraser);
258 
259         uwfAdapter.delete(key, eraser, context);
260 
261         return propagationReporter.getStatuses();
262     }
263 
264     @Override
265     public String unlink(final UserUR userUR, final String updater, final String context) {
266         UserWorkflowResult<Pair<UserUR, Boolean>> updated = uwfAdapter.update(userUR, updater, context);
267         return updated.getResult().getLeft().getKey();
268     }
269 
270     @Override
271     public String link(final UserUR userUR, final String updater, final String context) {
272         return uwfAdapter.update(userUR, updater, context).getResult().getLeft().getKey();
273     }
274 
275     @Override
276     public Pair<String, List<PropagationStatus>> activate(
277             final StatusR statusR, final boolean nullPriorityAsync, final String updater, final String context) {
278 
279         UserWorkflowResult<String> updated = statusR.isOnSyncope()
280                 ? uwfAdapter.activate(statusR.getKey(), statusR.getToken(), updater, context)
281                 : new UserWorkflowResult<>(statusR.getKey(), null, null, statusR.getType().name().toLowerCase());
282 
283         return Pair.of(updated.getResult(), propagateStatus(statusR, nullPriorityAsync, updater));
284     }
285 
286     @Override
287     public Pair<String, List<PropagationStatus>> reactivate(
288             final StatusR statusR, final boolean nullPriorityAsync, final String updater, final String context) {
289 
290         UserWorkflowResult<String> updated = statusR.isOnSyncope()
291                 ? uwfAdapter.reactivate(statusR.getKey(), updater, context)
292                 : new UserWorkflowResult<>(statusR.getKey(), null, null, statusR.getType().name().toLowerCase());
293 
294         return Pair.of(updated.getResult(), propagateStatus(statusR, nullPriorityAsync, updater));
295     }
296 
297     @Override
298     public Pair<String, List<PropagationStatus>> suspend(
299             final StatusR statusR, final boolean nullPriorityAsync, final String updater, final String context) {
300 
301         UserWorkflowResult<String> updated = statusR.isOnSyncope()
302                 ? uwfAdapter.suspend(statusR.getKey(), updater, context)
303                 : new UserWorkflowResult<>(statusR.getKey(), null, null, statusR.getType().name().toLowerCase());
304 
305         return Pair.of(updated.getResult(), propagateStatus(statusR, nullPriorityAsync, updater));
306     }
307 
308     protected List<PropagationStatus> propagateStatus(
309             final StatusR statusR, final boolean nullPriorityAsync, final String updater) {
310 
311         PropagationByResource<String> propByRes = new PropagationByResource<>();
312         propByRes.addAll(ResourceOperation.UPDATE, statusR.getResources());
313         List<PropagationTaskInfo> taskInfos = propagationManager.getUpdateTasks(
314                 AnyTypeKind.USER,
315                 statusR.getKey(),
316                 false,
317                 statusR.getType() != StatusRType.SUSPEND,
318                 propByRes,
319                 null,
320                 null,
321                 null);
322         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync, updater);
323 
324         return propagationReporter.getStatuses();
325     }
326 
327     @Override
328     public void internalSuspend(final String key, final String updater, final String context) {
329         Pair<UserWorkflowResult<String>, Boolean> updated = uwfAdapter.internalSuspend(key, updater, context);
330 
331         // propagate suspension if and only if it is required by policy
332         if (updated != null && updated.getRight()) {
333             UserUR userUR = new UserUR();
334             userUR.setKey(updated.getLeft().getResult());
335 
336             List<PropagationTaskInfo> taskInfos = propagationManager.getUserUpdateTasks(new UserWorkflowResult<>(
337                     Pair.of(userUR, Boolean.FALSE),
338                     updated.getLeft().getPropByRes(),
339                     updated.getLeft().getPropByLinkedAccount(),
340                     updated.getLeft().getPerformedTasks()));
341             taskExecutor.execute(taskInfos, false, updater);
342         }
343     }
344 
345     @Override
346     public List<PropagationStatus> provision(
347             final String key,
348             final boolean changePwd,
349             final String password,
350             final Collection<String> resources,
351             final boolean nullPriorityAsync,
352             final String executor) {
353 
354         UserUR userUR = new UserUR();
355         userUR.setKey(key);
356         userUR.getResources().addAll(resources.stream().
357                 map(r -> new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(r).build()).
358                 collect(Collectors.toSet()));
359 
360         if (changePwd) {
361             PasswordPatch passwordPatch = new PasswordPatch();
362             passwordPatch.setOnSyncope(false);
363             passwordPatch.getResources().addAll(resources);
364             passwordPatch.setValue(password);
365             userUR.setPassword(passwordPatch);
366         }
367 
368         PropagationByResource<String> propByRes = new PropagationByResource<>();
369         propByRes.addAll(ResourceOperation.UPDATE, resources);
370 
371         UserWorkflowResult<Pair<UserUR, Boolean>> wfResult = new UserWorkflowResult<>(
372                 Pair.of(userUR, (Boolean) null), propByRes, null, "update");
373 
374         List<PropagationTaskInfo> taskInfos = propagationManager.getUserUpdateTasks(wfResult, changePwd, null);
375         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync, executor);
376 
377         return propagationReporter.getStatuses();
378     }
379 
380     @Override
381     public List<PropagationStatus> deprovision(
382             final String key,
383             final Collection<String> resources,
384             final boolean nullPriorityAsync,
385             final String executor) {
386 
387         PropagationByResource<String> propByRes = new PropagationByResource<>();
388         propByRes.set(ResourceOperation.DELETE, resources);
389 
390         PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
391         userDAO.findLinkedAccounts(key).stream().
392                 filter(account -> resources.contains(account.getResource().getKey())).
393                 forEach(account -> propByLinkedAccount.add(
394                 ResourceOperation.DELETE,
395                 Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
396 
397         List<PropagationTaskInfo> taskInfos = propagationManager.getDeleteTasks(
398                 AnyTypeKind.USER,
399                 key,
400                 propByRes,
401                 propByLinkedAccount,
402                 userDAO.findAllResourceKeys(key).stream().
403                         filter(resource -> !resources.contains(resource)).
404                         collect(Collectors.toList()));
405         PropagationReporter propagationReporter = taskExecutor.execute(taskInfos, nullPriorityAsync, executor);
406 
407         return propagationReporter.getStatuses();
408     }
409 
410     @Override
411     public void requestPasswordReset(final String key, final String updater, final String context) {
412         uwfAdapter.requestPasswordReset(key, updater, context);
413     }
414 
415     @Override
416     public void confirmPasswordReset(
417             final String key, final String token, final String password, final String updater, final String context) {
418 
419         UserWorkflowResult<Pair<UserUR, Boolean>> updated =
420                 uwfAdapter.confirmPasswordReset(key, token, password, updater, context);
421 
422         List<PropagationTaskInfo> taskInfos = propagationManager.getUserUpdateTasks(updated);
423         taskExecutor.execute(taskInfos, false, updater);
424     }
425 }