1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.logic;
20
21 import java.lang.reflect.Method;
22 import java.time.OffsetDateTime;
23 import java.util.Collection;
24 import java.util.Comparator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Optional;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 import org.apache.commons.lang3.ArrayUtils;
31 import org.apache.commons.lang3.tuple.Pair;
32 import org.apache.syncope.common.lib.SyncopeClientException;
33 import org.apache.syncope.common.lib.request.GroupCR;
34 import org.apache.syncope.common.lib.request.GroupUR;
35 import org.apache.syncope.common.lib.request.StringPatchItem;
36 import org.apache.syncope.common.lib.to.ExecTO;
37 import org.apache.syncope.common.lib.to.GroupTO;
38 import org.apache.syncope.common.lib.to.PropagationStatus;
39 import org.apache.syncope.common.lib.to.ProvisioningResult;
40 import org.apache.syncope.common.lib.types.AnyTypeKind;
41 import org.apache.syncope.common.lib.types.ClientExceptionType;
42 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
43 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
44 import org.apache.syncope.common.lib.types.ImplementationEngine;
45 import org.apache.syncope.common.lib.types.JobType;
46 import org.apache.syncope.common.lib.types.PatchOperation;
47 import org.apache.syncope.common.lib.types.ProvisionAction;
48 import org.apache.syncope.common.lib.types.TaskType;
49 import org.apache.syncope.core.logic.api.LogicActions;
50 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
51 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
52 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
53 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
54 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
55 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
56 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
57 import org.apache.syncope.core.persistence.api.dao.UserDAO;
58 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
59 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
60 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
61 import org.apache.syncope.core.persistence.api.entity.Implementation;
62 import org.apache.syncope.core.persistence.api.entity.Realm;
63 import org.apache.syncope.core.persistence.api.entity.group.Group;
64 import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
65 import org.apache.syncope.core.provisioning.api.GroupProvisioningManager;
66 import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
67 import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
68 import org.apache.syncope.core.provisioning.api.job.JobManager;
69 import org.apache.syncope.core.provisioning.api.job.JobNamer;
70 import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
71 import org.apache.syncope.core.provisioning.java.job.GroupMemberProvisionTaskJobDelegate;
72 import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
73 import org.apache.syncope.core.spring.security.AuthContextUtils;
74 import org.apache.syncope.core.spring.security.SecurityProperties;
75 import org.quartz.JobDataMap;
76 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
77 import org.springframework.security.access.prepost.PreAuthorize;
78 import org.springframework.transaction.annotation.Transactional;
79
80
81
82
83
84 public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupCR, GroupUR> {
85
86 protected final UserDAO userDAO;
87
88 protected final GroupDAO groupDAO;
89
90 protected final SecurityProperties securityProperties;
91
92 protected final AnySearchDAO searchDAO;
93
94 protected final ImplementationDAO implementationDAO;
95
96 protected final TaskDAO taskDAO;
97
98 protected final GroupDataBinder binder;
99
100 protected final GroupProvisioningManager provisioningManager;
101
102 protected final TaskDataBinder taskDataBinder;
103
104 protected final JobManager jobManager;
105
106 protected final SchedulerFactoryBean scheduler;
107
108 protected final EntityFactory entityFactory;
109
110 public GroupLogic(
111 final RealmDAO realmDAO,
112 final AnyTypeDAO anyTypeDAO,
113 final TemplateUtils templateUtils,
114 final UserDAO userDAO,
115 final GroupDAO groupDAO,
116 final SecurityProperties securityProperties,
117 final AnySearchDAO searchDAO,
118 final ImplementationDAO implementationDAO,
119 final TaskDAO taskDAO,
120 final GroupDataBinder binder,
121 final GroupProvisioningManager provisioningManager,
122 final TaskDataBinder taskDataBinder,
123 final JobManager jobManager,
124 final SchedulerFactoryBean scheduler,
125 final EntityFactory entityFactory) {
126
127 super(realmDAO, anyTypeDAO, templateUtils);
128
129 this.userDAO = userDAO;
130 this.groupDAO = groupDAO;
131 this.securityProperties = securityProperties;
132 this.searchDAO = searchDAO;
133 this.implementationDAO = implementationDAO;
134 this.taskDAO = taskDAO;
135 this.binder = binder;
136 this.provisioningManager = provisioningManager;
137 this.taskDataBinder = taskDataBinder;
138 this.jobManager = jobManager;
139 this.scheduler = scheduler;
140 this.entityFactory = entityFactory;
141 }
142
143 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_READ + "')")
144 @Transactional(readOnly = true)
145 @Override
146 public GroupTO read(final String key) {
147 return binder.getGroupTO(key);
148 }
149
150 @PreAuthorize("isAuthenticated() and not(hasRole('" + IdRepoEntitlement.ANONYMOUS + "'))")
151 @Transactional(readOnly = true)
152 public List<GroupTO> own() {
153 if (securityProperties.getAdminUser().equals(AuthContextUtils.getUsername())) {
154 return List.of();
155 }
156
157 return userDAO.findAllGroups(userDAO.findByUsername(AuthContextUtils.getUsername())).stream().
158 map(group -> binder.getGroupTO(group, true)).collect(Collectors.toList());
159 }
160
161 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_SEARCH + "')")
162 @Transactional(readOnly = true)
163 @Override
164 public Pair<Integer, List<GroupTO>> search(
165 final SearchCond searchCond,
166 final int page, final int size, final List<OrderByClause> orderBy,
167 final String realm,
168 final boolean recursive,
169 final boolean details) {
170
171 Realm base = Optional.ofNullable(realmDAO.findByFullPath(realm)).
172 orElseThrow(() -> new NotFoundException("Realm " + realm));
173
174 Set<String> authRealms = RealmUtils.getEffective(
175 AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_SEARCH), realm);
176
177 SearchCond effectiveCond = searchCond == null ? groupDAO.getAllMatchingCond() : searchCond;
178
179 int count = searchDAO.count(base, recursive, authRealms, effectiveCond, AnyTypeKind.GROUP);
180
181 List<Group> matching = searchDAO.search(
182 base, recursive, authRealms, effectiveCond, page, size, orderBy, AnyTypeKind.GROUP);
183 List<GroupTO> result = matching.stream().
184 map(group -> binder.getGroupTO(group, details)).
185 collect(Collectors.toList());
186
187 return Pair.of(count, result);
188 }
189
190 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_CREATE + "')")
191 public ProvisioningResult<GroupTO> create(final GroupCR createReq, final boolean nullPriorityAsync) {
192 Pair<GroupCR, List<LogicActions>> before = beforeCreate(createReq);
193
194 if (before.getLeft().getRealm() == null) {
195 throw SyncopeClientException.build(ClientExceptionType.InvalidRealm);
196 }
197
198 Set<String> authRealms = RealmUtils.getEffective(
199 AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_CREATE),
200 before.getLeft().getRealm());
201 groupDAO.securityChecks(
202 authRealms,
203 null,
204 before.getLeft().getRealm());
205
206 Pair<String, List<PropagationStatus>> created = provisioningManager.create(
207 before.getLeft(), nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
208
209 return afterCreate(binder.getGroupTO(created.getKey()), created.getRight(), before.getRight());
210 }
211
212 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
213 @Override
214 public ProvisioningResult<GroupTO> update(final GroupUR req, final boolean nullPriorityAsync) {
215 GroupTO groupTO = binder.getGroupTO(req.getKey());
216 Pair<GroupUR, List<LogicActions>> before = beforeUpdate(req, groupTO.getRealm());
217
218 Set<String> authRealms = RealmUtils.getEffective(
219 AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_UPDATE),
220 groupTO.getRealm());
221 groupDAO.securityChecks(
222 authRealms,
223 before.getLeft().getKey(),
224 groupTO.getRealm());
225
226 Pair<GroupUR, List<PropagationStatus>> after = provisioningManager.update(
227 req, Set.of(), nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
228
229 ProvisioningResult<GroupTO> result = afterUpdate(
230 binder.getGroupTO(after.getLeft().getKey()),
231 after.getRight(),
232 before.getRight());
233
234
235 authRealms = RealmUtils.getEffective(
236 AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_UPDATE),
237 result.getEntity().getRealm());
238 groupDAO.securityChecks(
239 authRealms,
240 after.getLeft().getKey(),
241 result.getEntity().getRealm());
242
243 return result;
244 }
245
246 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_DELETE + "')")
247 @Override
248 public ProvisioningResult<GroupTO> delete(final String key, final boolean nullPriorityAsync) {
249 GroupTO group = binder.getGroupTO(key);
250 Pair<GroupTO, List<LogicActions>> before = beforeDelete(group);
251
252 Set<String> authRealms = RealmUtils.getEffective(
253 AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_DELETE),
254 before.getLeft().getRealm());
255 groupDAO.securityChecks(
256 authRealms,
257 before.getLeft().getKey(),
258 before.getLeft().getRealm());
259
260 List<Group> ownedGroups = groupDAO.findOwnedByGroup(before.getLeft().getKey());
261 if (!ownedGroups.isEmpty()) {
262 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.GroupOwnership);
263 sce.getElements().addAll(ownedGroups.stream().
264 map(g -> g.getKey() + ' ' + g.getName()).collect(Collectors.toList()));
265 throw sce;
266 }
267
268 List<PropagationStatus> statuses = provisioningManager.delete(
269 before.getLeft().getKey(), nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
270
271 GroupTO groupTO = new GroupTO();
272 groupTO.setKey(before.getLeft().getKey());
273
274 return afterDelete(groupTO, statuses, before.getRight());
275 }
276
277 protected GroupTO updateChecks(final String key) {
278 GroupTO group = binder.getGroupTO(key);
279
280 Set<String> authRealms = RealmUtils.getEffective(
281 AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_UPDATE),
282 group.getRealm());
283 groupDAO.securityChecks(
284 authRealms,
285 group.getKey(),
286 group.getRealm());
287
288 return group;
289 }
290
291 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
292 @Override
293 public GroupTO unlink(final String key, final Collection<String> resources) {
294 GroupTO groupTO = updateChecks(key);
295
296 GroupUR req = new GroupUR.Builder(key).
297 resources(resources.stream().
298 map(r -> new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(r).build()).
299 collect(Collectors.toList())).
300 udynMembershipCond(groupTO.getUDynMembershipCond()).
301 adynMembershipConds(groupTO.getADynMembershipConds()).
302 build();
303
304 return binder.getGroupTO(provisioningManager.unlink(req, AuthContextUtils.getUsername(), REST_CONTEXT));
305 }
306
307 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
308 @Override
309 public GroupTO link(final String key, final Collection<String> resources) {
310 GroupTO groupTO = updateChecks(key);
311
312 GroupUR req = new GroupUR.Builder(key).
313 resources(resources.stream().
314 map(r -> new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(r).build()).
315 collect(Collectors.toList())).
316 udynMembershipCond(groupTO.getUDynMembershipCond()).
317 adynMembershipConds(groupTO.getADynMembershipConds()).
318 build();
319
320 return binder.getGroupTO(provisioningManager.link(req, AuthContextUtils.getUsername(), REST_CONTEXT));
321 }
322
323 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
324 @Override
325 public ProvisioningResult<GroupTO> unassign(
326 final String key, final Collection<String> resources, final boolean nullPriorityAsync) {
327
328 GroupTO groupTO = updateChecks(key);
329
330 GroupUR req = new GroupUR.Builder(key).
331 resources(resources.stream().
332 map(r -> new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(r).build()).
333 collect(Collectors.toList())).
334 udynMembershipCond(groupTO.getUDynMembershipCond()).
335 adynMembershipConds(groupTO.getADynMembershipConds()).
336 build();
337
338 return update(req, nullPriorityAsync);
339 }
340
341 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
342 @Override
343 public ProvisioningResult<GroupTO> assign(
344 final String key,
345 final Collection<String> resources,
346 final boolean changepwd,
347 final String password,
348 final boolean nullPriorityAsync) {
349
350 GroupTO groupTO = updateChecks(key);
351
352 GroupUR req = new GroupUR.Builder(key).
353 resources(resources.stream().
354 map(r -> new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(r).build()).
355 collect(Collectors.toList())).
356 udynMembershipCond(groupTO.getUDynMembershipCond()).
357 adynMembershipConds(groupTO.getADynMembershipConds()).
358 build();
359
360 return update(req, nullPriorityAsync);
361 }
362
363 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
364 @Override
365 public ProvisioningResult<GroupTO> deprovision(
366 final String key,
367 final List<String> resources,
368 final boolean nullPriorityAsync) {
369
370 updateChecks(key);
371
372 List<PropagationStatus> statuses = provisioningManager.deprovision(
373 key, resources, nullPriorityAsync, AuthContextUtils.getUsername());
374
375 ProvisioningResult<GroupTO> result = new ProvisioningResult<>();
376 result.setEntity(binder.getGroupTO(key));
377 result.getPropagationStatuses().addAll(statuses);
378 result.getPropagationStatuses().sort(Comparator.comparing(item -> resources.indexOf(item.getResource())));
379 return result;
380 }
381
382 @PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
383 @Override
384 public ProvisioningResult<GroupTO> provision(
385 final String key,
386 final List<String> resources,
387 final boolean changePwd,
388 final String password,
389 final boolean nullPriorityAsync) {
390
391 updateChecks(key);
392
393 List<PropagationStatus> statuses = provisioningManager.provision(
394 key, resources, nullPriorityAsync, AuthContextUtils.getUsername());
395
396 ProvisioningResult<GroupTO> result = new ProvisioningResult<>();
397 result.setEntity(binder.getGroupTO(key));
398 result.getPropagationStatuses().addAll(statuses);
399 result.getPropagationStatuses().sort(Comparator.comparing(item -> resources.indexOf(item.getResource())));
400 return result;
401 }
402
403 @PreAuthorize("hasRole('" + IdRepoEntitlement.TASK_CREATE + "') "
404 + "and hasRole('" + IdRepoEntitlement.TASK_EXECUTE + "')")
405 @Transactional
406 public ExecTO provisionMembers(final String key, final ProvisionAction action) {
407 Group group = groupDAO.find(key);
408 if (group == null) {
409 throw new NotFoundException("Group " + key);
410 }
411
412 Implementation jobDelegate = implementationDAO.findByType(IdRepoImplementationType.TASKJOB_DELEGATE).stream().
413 filter(impl -> GroupMemberProvisionTaskJobDelegate.class.getName().equals(impl.getBody())).
414 findFirst().orElseGet(() -> {
415 Implementation groupMemberProvision = entityFactory.newEntity(Implementation.class);
416 groupMemberProvision.setKey(GroupMemberProvisionTaskJobDelegate.class.getSimpleName());
417 groupMemberProvision.setEngine(ImplementationEngine.JAVA);
418 groupMemberProvision.setType(IdRepoImplementationType.TASKJOB_DELEGATE);
419 groupMemberProvision.setBody(GroupMemberProvisionTaskJobDelegate.class.getName());
420 groupMemberProvision = implementationDAO.save(groupMemberProvision);
421 return groupMemberProvision;
422 });
423
424 String name = (action == ProvisionAction.DEPROVISION ? "de" : "")
425 + "provision members of group " + group.getName();
426 SchedTask task = taskDAO.<SchedTask>findByName(TaskType.SCHEDULED, name).
427 orElseGet(() -> {
428 SchedTask t = entityFactory.newEntity(SchedTask.class);
429 t.setName(name);
430 return t;
431 });
432 task.setActive(true);
433 task.setJobDelegate(jobDelegate);
434 task = taskDAO.save(task);
435
436 try {
437 Map<String, Object> jobDataMap = jobManager.register(
438 task,
439 null,
440 AuthContextUtils.getUsername());
441
442 jobDataMap.put(JobManager.DRY_RUN_JOBDETAIL_KEY, false);
443 jobDataMap.put(GroupMemberProvisionTaskJobDelegate.GROUP_KEY_JOBDETAIL_KEY, key);
444 jobDataMap.put(GroupMemberProvisionTaskJobDelegate.ACTION_JOBDETAIL_KEY, action);
445
446 scheduler.getScheduler().triggerJob(
447 JobNamer.getJobKey(task),
448 new JobDataMap(jobDataMap));
449 } catch (Exception e) {
450 LOG.error("While executing task {}", task, e);
451
452 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
453 sce.getElements().add(e.getMessage());
454 throw sce;
455 }
456
457 ExecTO result = new ExecTO();
458 result.setJobType(JobType.TASK);
459 result.setRefKey(task.getKey());
460 result.setRefDesc(taskDataBinder.buildRefDesc(task));
461 result.setStart(OffsetDateTime.now());
462 result.setStatus("JOB_FIRED");
463 result.setMessage("Job fired; waiting for results...");
464
465 return result;
466 }
467
468 @Override
469 protected GroupTO resolveReference(final Method method, final Object... args) throws UnresolvedReferenceException {
470 String key = null;
471
472 if (ArrayUtils.isNotEmpty(args)) {
473 for (int i = 0; key == null && i < args.length; i++) {
474 if (args[i] instanceof String) {
475 key = (String) args[i];
476 } else if (args[i] instanceof GroupTO) {
477 key = ((GroupTO) args[i]).getKey();
478 } else if (args[i] instanceof GroupUR) {
479 key = ((GroupUR) args[i]).getKey();
480 }
481 }
482 }
483
484 if (key != null) {
485 try {
486 return binder.getGroupTO(key);
487 } catch (Throwable ignore) {
488 LOG.debug("Unresolved reference", ignore);
489 throw new UnresolvedReferenceException(ignore);
490 }
491 }
492
493 throw new UnresolvedReferenceException();
494 }
495 }