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