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