/* * 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.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.Predicate; import org.apache.commons.collections4.Transformer; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.patch.GroupPatch; import org.apache.syncope.common.lib.patch.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.to.TypeExtensionTO; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.BulkMembersActionType; import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.lib.types.JobType; import org.apache.syncope.common.lib.types.PatchOperation; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.core.persistence.api.dao.AnySearchDAO; import org.apache.syncope.core.persistence.api.dao.ConfDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; 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.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.DelegatedAdministrationException; 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, GroupPatch> { @Autowired protected GroupDAO groupDAO; @Autowired protected UserDAO userDAO; @Autowired protected AnySearchDAO searchDAO; @Autowired protected TaskDAO taskDAO; @Autowired protected ConfDAO confDAO; @Autowired protected GroupDataBinder binder; @Autowired protected GroupProvisioningManager provisioningManager; @Autowired protected TaskDataBinder taskDataBinder; @Autowired protected JobManager jobManager; @Autowired protected SchedulerFactoryBean scheduler; @Autowired protected EntityFactory entityFactory; @Override protected void securityChecks(final Set<String> effectiveRealms, final String realm, final String key) { if (!IterableUtils.matchesAny(effectiveRealms, new Predicate<String>() { @Override public boolean evaluate(final String ownedRealm) { return realm.startsWith(ownedRealm) || ownedRealm.equals(RealmUtils.getGroupOwnerRealm(realm, key)); } })) { throw new DelegatedAdministrationException(AnyTypeKind.GROUP, key); } } @PreAuthorize("hasRole('" + StandardEntitlement.GROUP_READ + "')") @Transactional(readOnly = true) @Override public GroupTO read(final String key) { return binder.getGroupTO(key); } @PreAuthorize("isAuthenticated() and not(hasRole('" + StandardEntitlement.ANONYMOUS + "'))") @Transactional(readOnly = true) public List<GroupTO> own() { return CollectionUtils.collect( userDAO.findAllGroups(userDAO.findByUsername(AuthContextUtils.getUsername())), new Transformer<Group, GroupTO>() { @Override public GroupTO transform(final Group input) { return binder.getGroupTO(input, true); } }, new ArrayList<GroupTO>()); } @PreAuthorize("isAuthenticated()") @Transactional(readOnly = true) public TypeExtensionTO readTypeExtension(final String key, final String anyTypeKey) { Group group = groupDAO.find(key); if (group == null) { throw new NotFoundException("Group " + key); } GroupTO groupTO = binder.getGroupTO(group, false); return groupTO.getTypeExtension(anyTypeKey); } @PreAuthorize("isAuthenticated()") @Transactional(readOnly = true) @Override public int count(final String realm) { return groupDAO.count(getEffectiveRealms(SyncopeConstants.FULL_ADMIN_REALMS, realm)); } @PreAuthorize("isAuthenticated()") @Transactional(readOnly = true) @Override public List<GroupTO> list( final int page, final int size, final List<OrderByClause> orderBy, final String realm, final boolean details) { return CollectionUtils.collect(groupDAO.findAll( getEffectiveRealms(SyncopeConstants.FULL_ADMIN_REALMS, realm), page, size, orderBy), new Transformer<Group, GroupTO>() { @Override public GroupTO transform(final Group input) { return binder.getGroupTO(input, details); } }, new ArrayList<GroupTO>()); } @PreAuthorize("isAuthenticated()") @Transactional(readOnly = true) @Override public int searchCount(final SearchCond searchCondition, final String realm) { return searchDAO.count( getEffectiveRealms(SyncopeConstants.FULL_ADMIN_REALMS, realm), searchCondition, AnyTypeKind.GROUP); } @PreAuthorize("isAuthenticated()") @Transactional(readOnly = true) @Override public List<GroupTO> search(final SearchCond searchCondition, final int page, final int size, final List<OrderByClause> orderBy, final String realm, final boolean details) { List<Group> matchingGroups = searchDAO.search( getEffectiveRealms(SyncopeConstants.FULL_ADMIN_REALMS, realm), searchCondition, page, size, orderBy, AnyTypeKind.GROUP); return CollectionUtils.collect(matchingGroups, new Transformer<Group, GroupTO>() { @Override public GroupTO transform(final Group input) { return binder.getGroupTO(input, details); } }, new ArrayList<GroupTO>()); } @PreAuthorize("hasRole('" + StandardEntitlement.GROUP_CREATE + "')") @Override public ProvisioningResult<GroupTO> create(final GroupTO groupTO, final boolean nullPriorityAsync) { Pair<GroupTO, List<LogicActions>> before = beforeCreate(groupTO); if (before.getLeft().getRealm() == null) { throw SyncopeClientException.build(ClientExceptionType.InvalidRealm); } Set<String> effectiveRealms = getEffectiveRealms( AuthContextUtils.getAuthorizations().get(StandardEntitlement.GROUP_CREATE), before.getLeft().getRealm()); securityChecks(effectiveRealms, before.getLeft().getRealm(), null); Pair<String, List<PropagationStatus>> created = provisioningManager.create(before.getLeft(), nullPriorityAsync); return after(binder.getGroupTO(created.getKey()), created.getRight(), before.getRight()); } @PreAuthorize("hasRole('" + StandardEntitlement.GROUP_UPDATE + "')") @Override public ProvisioningResult<GroupTO> update(final GroupPatch groupPatch, final boolean nullPriorityAsync) { GroupTO groupTO = binder.getGroupTO(groupPatch.getKey()); Pair<GroupPatch, List<LogicActions>> before = beforeUpdate(groupPatch, groupTO.getRealm()); if (before.getLeft().getRealm() != null && StringUtils.isNotBlank(before.getLeft().getRealm().getValue())) { Set<String> effectiveRealms = getEffectiveRealms( AuthContextUtils.getAuthorizations().get(StandardEntitlement.USER_UPDATE), before.getLeft().getRealm().getValue()); securityChecks(effectiveRealms, before.getLeft().getRealm().getValue(), before.getLeft().getKey()); } Pair<String, List<PropagationStatus>> updated = provisioningManager.update(groupPatch, nullPriorityAsync); return after(binder.getGroupTO(updated.getKey()), updated.getRight(), before.getRight()); } @PreAuthorize("hasRole('" + StandardEntitlement.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> effectiveRealms = getEffectiveRealms( AuthContextUtils.getAuthorizations().get(StandardEntitlement.GROUP_DELETE), before.getLeft().getRealm()); securityChecks(effectiveRealms, before.getLeft().getRealm(), before.getLeft().getKey()); List<Group> ownedGroups = groupDAO.findOwnedByGroup(before.getLeft().getKey()); if (!ownedGroups.isEmpty()) { SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.GroupOwnership); sce.getElements().addAll(CollectionUtils.collect(ownedGroups, new Transformer<Group, String>() { @Override public String transform(final Group group) { return group.getKey() + " " + group.getName(); } }, new ArrayList<String>())); throw sce; } List<PropagationStatus> statuses = provisioningManager.delete(before.getLeft().getKey(), nullPriorityAsync); GroupTO groupTO = new GroupTO(); groupTO.setKey(before.getLeft().getKey()); return after(groupTO, statuses, before.getRight()); } @PreAuthorize("hasRole('" + StandardEntitlement.GROUP_UPDATE + "')") @Override public GroupTO unlink(final String key, final Collection<String> resources) { // security checks GroupTO group = binder.getGroupTO(key); Set<String> effectiveRealms = getEffectiveRealms( AuthContextUtils.getAuthorizations().get(StandardEntitlement.GROUP_UPDATE), group.getRealm()); securityChecks(effectiveRealms, group.getRealm(), group.getKey()); GroupPatch patch = new GroupPatch(); patch.setKey(key); patch.getResources().addAll(CollectionUtils.collect(resources, new Transformer<String, StringPatchItem>() { @Override public StringPatchItem transform(final String resource) { return new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(resource).build(); } })); return binder.getGroupTO(provisioningManager.unlink(patch)); } @PreAuthorize("hasRole('" + StandardEntitlement.GROUP_UPDATE + "')") @Override public GroupTO link(final String key, final Collection<String> resources) { // security checks GroupTO group = binder.getGroupTO(key); Set<String> effectiveRealms = getEffectiveRealms( AuthContextUtils.getAuthorizations().get(StandardEntitlement.GROUP_UPDATE), group.getRealm()); securityChecks(effectiveRealms, group.getRealm(), group.getKey()); GroupPatch patch = new GroupPatch(); patch.setKey(key); patch.getResources().addAll(CollectionUtils.collect(resources, new Transformer<String, StringPatchItem>() { @Override public StringPatchItem transform(final String resource) { return new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(resource).build(); } })); return binder.getGroupTO(provisioningManager.link(patch)); } @PreAuthorize("hasRole('" + StandardEntitlement.GROUP_UPDATE + "')") @Override public ProvisioningResult<GroupTO> unassign( final String key, final Collection<String> resources, final boolean nullPriorityAsync) { // security checks GroupTO group = binder.getGroupTO(key); Set<String> effectiveRealms = getEffectiveRealms( AuthContextUtils.getAuthorizations().get(StandardEntitlement.GROUP_UPDATE), group.getRealm()); securityChecks(effectiveRealms, group.getRealm(), group.getKey()); GroupPatch patch = new GroupPatch(); patch.setKey(key); patch.getResources().addAll(CollectionUtils.collect(resources, new Transformer<String, StringPatchItem>() { @Override public StringPatchItem transform(final String resource) { return new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(resource).build(); } })); return update(patch, nullPriorityAsync); } @PreAuthorize("hasRole('" + StandardEntitlement.GROUP_UPDATE + "')") @Override public ProvisioningResult<GroupTO> assign( final String key, final Collection<String> resources, final boolean changepwd, final String password, final boolean nullPriorityAsync) { // security checks GroupTO group = binder.getGroupTO(key); Set<String> effectiveRealms = getEffectiveRealms( AuthContextUtils.getAuthorizations().get(StandardEntitlement.GROUP_UPDATE), group.getRealm()); securityChecks(effectiveRealms, group.getRealm(), group.getKey()); GroupPatch patch = new GroupPatch(); patch.setKey(key); patch.getResources().addAll(CollectionUtils.collect(resources, new Transformer<String, StringPatchItem>() { @Override public StringPatchItem transform(final String resource) { return new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(resource).build(); } })); return update(patch, nullPriorityAsync); } @PreAuthorize("hasRole('" + StandardEntitlement.GROUP_UPDATE + "')") @Override public ProvisioningResult<GroupTO> deprovision( final String key, final Collection<String> resources, final boolean nullPriorityAsync) { // security checks GroupTO group = binder.getGroupTO(key); Set<String> effectiveRealms = getEffectiveRealms( AuthContextUtils.getAuthorizations().get(StandardEntitlement.GROUP_UPDATE), group.getRealm()); securityChecks(effectiveRealms, group.getRealm(), group.getKey()); List<PropagationStatus> statuses = provisioningManager.deprovision(key, resources, nullPriorityAsync); ProvisioningResult<GroupTO> result = new ProvisioningResult<>(); result.setEntity(binder.getGroupTO(key)); result.getPropagationStatuses().addAll(statuses); return result; } @PreAuthorize("hasRole('" + StandardEntitlement.GROUP_UPDATE + "')") @Override public ProvisioningResult<GroupTO> provision( final String key, final Collection<String> resources, final boolean changePwd, final String password, final boolean nullPriorityAsync) { // security checks GroupTO group = binder.getGroupTO(key); Set<String> effectiveRealms = getEffectiveRealms( AuthContextUtils.getAuthorizations().get(StandardEntitlement.GROUP_UPDATE), group.getRealm()); securityChecks(effectiveRealms, group.getRealm(), group.getKey()); List<PropagationStatus> statuses = provisioningManager.provision(key, resources, nullPriorityAsync); ProvisioningResult<GroupTO> result = new ProvisioningResult<>(); result.setEntity(binder.getGroupTO(key)); result.getPropagationStatuses().addAll(statuses); return result; } @PreAuthorize("hasRole('" + StandardEntitlement.TASK_CREATE + "') " + "and hasRole('" + StandardEntitlement.TASK_EXECUTE + "')") @Transactional public ExecTO bulkMembersAction(final String key, final BulkMembersActionType actionType) { Group group = groupDAO.find(key); if (group == null) { throw new NotFoundException("Group " + key); } SchedTask task = entityFactory.newEntity(SchedTask.class); task.setName("Bulk member provision for group " + group.getName()); task.setActive(true); task.setJobDelegateClassName(GroupMemberProvisionTaskJobDelegate.class.getName()); task = taskDAO.save(task); try { Map<String, Object> jobDataMap = jobManager.register( task, null, confDAO.find("tasks.interruptMaxRetries", "1").getValues().get(0).getLongValue()); jobDataMap.put(TaskJob.DRY_RUN_JOBDETAIL_KEY, false); jobDataMap.put(GroupMemberProvisionTaskJobDelegate.GROUP_KEY_JOBDETAIL_KEY, key); jobDataMap.put(GroupMemberProvisionTaskJobDelegate.ACTION_TYPE_JOBDETAIL_KEY, actionType); 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 GroupPatch) { key = ((GroupPatch) 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(); } }