blob: 357bea81e7ae0fa4f147702812729dda61bedbb7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.core.logic;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.request.GroupCR;
import org.apache.syncope.common.lib.request.GroupUR;
import org.apache.syncope.common.lib.request.StringPatchItem;
import org.apache.syncope.common.lib.to.ExecTO;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.PropagationStatus;
import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.ProvisionAction;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.IdRepoImplementationType;
import org.apache.syncope.common.lib.types.ImplementationEngine;
import org.apache.syncope.common.lib.types.JobType;
import org.apache.syncope.common.lib.types.PatchOperation;
import org.apache.syncope.common.lib.types.IdRepoEntitlement;
import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.TaskDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
import org.apache.syncope.core.provisioning.api.GroupProvisioningManager;
import org.apache.syncope.core.provisioning.api.LogicActions;
import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
import org.apache.syncope.core.provisioning.api.job.JobManager;
import org.apache.syncope.core.provisioning.api.job.JobNamer;
import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
import org.apache.syncope.core.provisioning.java.job.GroupMemberProvisionTaskJobDelegate;
import org.apache.syncope.core.provisioning.java.job.TaskJob;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.spring.security.SecurityProperties;
import org.quartz.JobDataMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* Note that this controller does not extend {@link AbstractTransactionalLogic}, hence does not provide any
* Spring's Transactional logic at class level.
*/
@Component
public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupCR, GroupUR> {
@Autowired
protected UserDAO userDAO;
@Autowired
protected GroupDAO groupDAO;
@Autowired
protected SecurityProperties securityProperties;
@Autowired
protected AnySearchDAO searchDAO;
@Autowired
protected ImplementationDAO implementationDAO;
@Autowired
protected TaskDAO taskDAO;
@Autowired
protected GroupDataBinder binder;
@Autowired
protected GroupProvisioningManager provisioningManager;
@Autowired
protected TaskDataBinder taskDataBinder;
@Autowired
protected ConfParamOps confParamOps;
@Autowired
protected JobManager jobManager;
@Autowired
protected SchedulerFactoryBean scheduler;
@Autowired
protected EntityFactory entityFactory;
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_READ + "')")
@Transactional(readOnly = true)
@Override
public GroupTO read(final String key) {
return binder.getGroupTO(key);
}
@PreAuthorize("isAuthenticated() and not(hasRole('" + IdRepoEntitlement.ANONYMOUS + "'))")
@Transactional(readOnly = true)
public List<GroupTO> own() {
if (securityProperties.getAdminUser().equals(AuthContextUtils.getUsername())) {
return List.of();
}
return userDAO.findAllGroups(userDAO.findByUsername(AuthContextUtils.getUsername())).stream().
map(group -> binder.getGroupTO(group, true)).collect(Collectors.toList());
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_SEARCH + "')")
@Transactional(readOnly = true)
@Override
public Pair<Integer, List<GroupTO>> search(
final SearchCond searchCond,
final int page, final int size, final List<OrderByClause> orderBy,
final String realm,
final boolean details) {
Set<String> authRealms = RealmUtils.getEffective(
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_SEARCH), realm);
SearchCond effectiveCond = searchCond == null ? groupDAO.getAllMatchingCond() : searchCond;
int count = searchDAO.count(authRealms, effectiveCond, AnyTypeKind.GROUP);
List<Group> matching = searchDAO.search(
authRealms, effectiveCond, page, size, orderBy, AnyTypeKind.GROUP);
List<GroupTO> result = matching.stream().
map(group -> binder.getGroupTO(group, details)).
collect(Collectors.toList());
return Pair.of(count, result);
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_CREATE + "')")
public ProvisioningResult<GroupTO> create(final GroupCR createReq, final boolean nullPriorityAsync) {
Pair<GroupCR, List<LogicActions>> before = beforeCreate(createReq);
if (before.getLeft().getRealm() == null) {
throw SyncopeClientException.build(ClientExceptionType.InvalidRealm);
}
Set<String> authRealms = RealmUtils.getEffective(
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_CREATE),
before.getLeft().getRealm());
groupDAO.securityChecks(
authRealms,
null,
before.getLeft().getRealm());
Pair<String, List<PropagationStatus>> created = provisioningManager.create(
before.getLeft(), nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
return afterCreate(binder.getGroupTO(created.getKey()), created.getRight(), before.getRight());
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
@Override
public ProvisioningResult<GroupTO> update(final GroupUR req, final boolean nullPriorityAsync) {
GroupTO groupTO = binder.getGroupTO(req.getKey());
Pair<GroupUR, List<LogicActions>> before = beforeUpdate(req, groupTO.getRealm());
Set<String> authRealms = RealmUtils.getEffective(
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_UPDATE),
groupTO.getRealm());
groupDAO.securityChecks(
authRealms,
before.getLeft().getKey(),
groupTO.getRealm());
Pair<GroupUR, List<PropagationStatus>> after =
provisioningManager.update(req, nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
ProvisioningResult<GroupTO> result = afterUpdate(
binder.getGroupTO(after.getLeft().getKey()),
after.getRight(),
before.getRight());
// check if group can still be managed by the caller
groupDAO.securityChecks(
authRealms,
after.getLeft().getKey(),
result.getEntity().getRealm());
return result;
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_DELETE + "')")
@Override
public ProvisioningResult<GroupTO> delete(final String key, final boolean nullPriorityAsync) {
GroupTO group = binder.getGroupTO(key);
Pair<GroupTO, List<LogicActions>> before = beforeDelete(group);
Set<String> authRealms = RealmUtils.getEffective(
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_DELETE),
before.getLeft().getRealm());
groupDAO.securityChecks(
authRealms,
before.getLeft().getKey(),
before.getLeft().getRealm());
List<Group> ownedGroups = groupDAO.findOwnedByGroup(before.getLeft().getKey());
if (!ownedGroups.isEmpty()) {
SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.GroupOwnership);
sce.getElements().addAll(ownedGroups.stream().
map(g -> g.getKey() + ' ' + g.getName()).collect(Collectors.toList()));
throw sce;
}
List<PropagationStatus> statuses = provisioningManager.delete(
before.getLeft().getKey(), nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
GroupTO groupTO = new GroupTO();
groupTO.setKey(before.getLeft().getKey());
return afterDelete(groupTO, statuses, before.getRight());
}
protected GroupTO updateChecks(final String key) {
GroupTO group = binder.getGroupTO(key);
Set<String> authRealms = RealmUtils.getEffective(
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_UPDATE),
group.getRealm());
groupDAO.securityChecks(
authRealms,
group.getKey(),
group.getRealm());
return group;
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
@Override
public GroupTO unlink(final String key, final Collection<String> resources) {
GroupTO groupTO = updateChecks(key);
GroupUR req = new GroupUR();
req.setKey(key);
req.getResources().addAll(resources.stream().
map(r -> new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(r).build()).
collect(Collectors.toList()));
req.setUDynMembershipCond(groupTO.getUDynMembershipCond());
req.getADynMembershipConds().putAll(groupTO.getADynMembershipConds());
return binder.getGroupTO(provisioningManager.unlink(req, AuthContextUtils.getUsername(), REST_CONTEXT));
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
@Override
public GroupTO link(final String key, final Collection<String> resources) {
GroupTO groupTO = updateChecks(key);
GroupUR req = new GroupUR();
req.setKey(key);
req.getResources().addAll(resources.stream().
map(r -> new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(r).build()).
collect(Collectors.toList()));
req.getADynMembershipConds().putAll(groupTO.getADynMembershipConds());
req.setUDynMembershipCond(groupTO.getUDynMembershipCond());
return binder.getGroupTO(provisioningManager.link(req, AuthContextUtils.getUsername(), REST_CONTEXT));
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
@Override
public ProvisioningResult<GroupTO> unassign(
final String key, final Collection<String> resources, final boolean nullPriorityAsync) {
GroupTO groupTO = updateChecks(key);
GroupUR req = new GroupUR();
req.setKey(key);
req.getResources().addAll(resources.stream().
map(r -> new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(r).build()).
collect(Collectors.toList()));
req.getADynMembershipConds().putAll(groupTO.getADynMembershipConds());
req.setUDynMembershipCond(groupTO.getUDynMembershipCond());
return update(req, nullPriorityAsync);
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
@Override
public ProvisioningResult<GroupTO> assign(
final String key,
final Collection<String> resources,
final boolean changepwd,
final String password,
final boolean nullPriorityAsync) {
GroupTO groupTO = updateChecks(key);
GroupUR req = new GroupUR();
req.setKey(key);
req.getResources().addAll(resources.stream().
map(r -> new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(r).build()).
collect(Collectors.toList()));
req.getADynMembershipConds().putAll(groupTO.getADynMembershipConds());
req.setUDynMembershipCond(groupTO.getUDynMembershipCond());
return update(req, nullPriorityAsync);
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
@Override
public ProvisioningResult<GroupTO> deprovision(
final String key, final Collection<String> resources, final boolean nullPriorityAsync) {
updateChecks(key);
List<PropagationStatus> statuses = provisioningManager.deprovision(
key, resources, nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
ProvisioningResult<GroupTO> result = new ProvisioningResult<>();
result.setEntity(binder.getGroupTO(key));
result.getPropagationStatuses().addAll(statuses);
return result;
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.GROUP_UPDATE + "')")
@Override
public ProvisioningResult<GroupTO> provision(
final String key,
final Collection<String> resources,
final boolean changePwd,
final String password,
final boolean nullPriorityAsync) {
updateChecks(key);
List<PropagationStatus> statuses = provisioningManager.provision(
key, resources, nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
ProvisioningResult<GroupTO> result = new ProvisioningResult<>();
result.setEntity(binder.getGroupTO(key));
result.getPropagationStatuses().addAll(statuses);
return result;
}
@PreAuthorize("hasRole('" + IdRepoEntitlement.TASK_CREATE + "') "
+ "and hasRole('" + IdRepoEntitlement.TASK_EXECUTE + "')")
@Transactional
public ExecTO provisionMembers(final String key, final ProvisionAction action) {
Group group = groupDAO.find(key);
if (group == null) {
throw new NotFoundException("Group " + key);
}
Implementation jobDelegate = implementationDAO.findByType(IdRepoImplementationType.TASKJOB_DELEGATE).stream().
filter(impl -> GroupMemberProvisionTaskJobDelegate.class.getName().equals(impl.getBody())).
findFirst().orElseGet(() -> {
Implementation groupMemberProvision = entityFactory.newEntity(Implementation.class);
groupMemberProvision.setKey(GroupMemberProvisionTaskJobDelegate.class.getSimpleName());
groupMemberProvision.setEngine(ImplementationEngine.JAVA);
groupMemberProvision.setType(IdRepoImplementationType.TASKJOB_DELEGATE);
groupMemberProvision.setBody(GroupMemberProvisionTaskJobDelegate.class.getName());
groupMemberProvision = implementationDAO.save(groupMemberProvision);
return groupMemberProvision;
});
SchedTask task = entityFactory.newEntity(SchedTask.class);
task.setName((action == ProvisionAction.DEPROVISION ? "de" : "")
+ "provision members of group " + group.getName());
task.setActive(true);
task.setJobDelegate(jobDelegate);
task = taskDAO.save(task);
try {
Map<String, Object> jobDataMap = jobManager.register(
task,
null,
confParamOps.get(AuthContextUtils.getDomain(), "tasks.interruptMaxRetries", 1L, Long.class),
AuthContextUtils.getUsername());
jobDataMap.put(TaskJob.DRY_RUN_JOBDETAIL_KEY, false);
jobDataMap.put(GroupMemberProvisionTaskJobDelegate.GROUP_KEY_JOBDETAIL_KEY, key);
jobDataMap.put(GroupMemberProvisionTaskJobDelegate.ACTION_JOBDETAIL_KEY, action);
scheduler.getScheduler().triggerJob(
JobNamer.getJobKey(task),
new JobDataMap(jobDataMap));
} catch (Exception e) {
LOG.error("While executing task {}", task, e);
SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Scheduling);
sce.getElements().add(e.getMessage());
throw sce;
}
ExecTO result = new ExecTO();
result.setJobType(JobType.TASK);
result.setRefKey(task.getKey());
result.setRefDesc(taskDataBinder.buildRefDesc(task));
result.setStart(new Date());
result.setStatus("JOB_FIRED");
result.setMessage("Job fired; waiting for results...");
return result;
}
@Override
protected GroupTO resolveReference(final Method method, final Object... args) throws UnresolvedReferenceException {
String key = null;
if (ArrayUtils.isNotEmpty(args)) {
for (int i = 0; key == null && i < args.length; i++) {
if (args[i] instanceof String) {
key = (String) args[i];
} else if (args[i] instanceof GroupTO) {
key = ((GroupTO) args[i]).getKey();
} else if (args[i] instanceof GroupUR) {
key = ((GroupUR) args[i]).getKey();
}
}
}
if (key != null) {
try {
return binder.getGroupTO(key);
} catch (Throwable ignore) {
LOG.debug("Unresolved reference", ignore);
throw new UnresolvedReferenceException(ignore);
}
}
throw new UnresolvedReferenceException();
}
}