blob: 19ff4951888a1b8f4c9bcac95010db7c7d5581fe [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.provisioning.java.data;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.SyncopeClientCompositeException;
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.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.TypeExtensionTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.ResourceOperation;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyType;
import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.DerSchema;
import org.apache.syncope.core.persistence.api.entity.DynGroupMembership;
import org.apache.syncope.core.persistence.api.entity.Entity;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.VirSchema;
import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.provisioning.api.DerAttrHandler;
import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
import org.apache.syncope.core.provisioning.api.MappingManager;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.VirAttrHandler;
import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
import org.springframework.transaction.annotation.Transactional;
@Transactional(rollbackFor = { Throwable.class })
public class GroupDataBinderImpl extends AbstractAnyDataBinder implements GroupDataBinder {
protected final SearchCondVisitor searchCondVisitor;
public GroupDataBinderImpl(
final AnyTypeDAO anyTypeDAO,
final RealmDAO realmDAO,
final AnyTypeClassDAO anyTypeClassDAO,
final AnyObjectDAO anyObjectDAO,
final UserDAO userDAO,
final GroupDAO groupDAO,
final PlainSchemaDAO plainSchemaDAO,
final PlainAttrDAO plainAttrDAO,
final PlainAttrValueDAO plainAttrValueDAO,
final ExternalResourceDAO resourceDAO,
final RelationshipTypeDAO relationshipTypeDAO,
final EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final DerAttrHandler derAttrHandler,
final VirAttrHandler virAttrHandler,
final MappingManager mappingManager,
final IntAttrNameParser intAttrNameParser,
final OutboundMatcher outboundMatcher,
final SearchCondVisitor searchCondVisitor) {
super(anyTypeDAO,
realmDAO,
anyTypeClassDAO,
anyObjectDAO,
userDAO,
groupDAO,
plainSchemaDAO,
plainAttrDAO,
plainAttrValueDAO,
resourceDAO,
relationshipTypeDAO,
entityFactory,
anyUtilsFactory,
derAttrHandler,
virAttrHandler,
mappingManager,
intAttrNameParser,
outboundMatcher);
this.searchCondVisitor = searchCondVisitor;
}
protected void setDynMembership(final Group group, final AnyType anyType, final String dynMembershipFIQL) {
SearchCond dynMembershipCond = SearchCondConverter.convert(searchCondVisitor, dynMembershipFIQL);
if (!dynMembershipCond.isValid()) {
SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchParameters);
sce.getElements().add(dynMembershipFIQL);
throw sce;
}
if (anyType.getKind() == AnyTypeKind.GROUP) {
SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidAnyType);
sce.getElements().add(anyType.getKind().name());
throw sce;
}
DynGroupMembership<?> dynMembership;
if (anyType.getKind() == AnyTypeKind.ANY_OBJECT && group.getADynMembership(anyType).isEmpty()) {
dynMembership = entityFactory.newEntity(ADynGroupMembership.class);
dynMembership.setGroup(group);
((ADynGroupMembership) dynMembership).setAnyType(anyType);
group.add((ADynGroupMembership) dynMembership);
} else if (anyType.getKind() == AnyTypeKind.USER && group.getUDynMembership() == null) {
dynMembership = entityFactory.newEntity(UDynGroupMembership.class);
dynMembership.setGroup(group);
group.setUDynMembership((UDynGroupMembership) dynMembership);
} else {
dynMembership = anyType.getKind() == AnyTypeKind.ANY_OBJECT
? group.getADynMembership(anyType).get()
: group.getUDynMembership();
}
dynMembership.setFIQLCond(dynMembershipFIQL);
}
@Override
public void create(final Group group, final GroupCR groupCR) {
SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
// name
SyncopeClientException invalidGroups = SyncopeClientException.build(ClientExceptionType.InvalidGroup);
if (groupCR.getName() == null) {
LOG.error("No name specified for this group");
invalidGroups.getElements().add("No name specified for this group");
} else {
group.setName(groupCR.getName());
}
// realm
Realm realm = realmDAO.findByFullPath(groupCR.getRealm());
if (realm == null) {
SyncopeClientException noRealm = SyncopeClientException.build(ClientExceptionType.InvalidRealm);
noRealm.getElements().add("Invalid or null realm specified: " + groupCR.getRealm());
scce.addException(noRealm);
}
group.setRealm(realm);
// attributes and resources
fill(group, groupCR, anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce);
// owner
if (groupCR.getUserOwner() != null) {
User owner = userDAO.find(groupCR.getUserOwner());
if (owner == null) {
LOG.warn("Ignoring invalid user specified as owner: {}", groupCR.getUserOwner());
} else {
group.setUserOwner(owner);
}
}
if (groupCR.getGroupOwner() != null) {
Group owner = groupDAO.find(groupCR.getGroupOwner());
if (owner == null) {
LOG.warn("Ignoring invalid group specified as owner: {}", groupCR.getGroupOwner());
} else {
group.setGroupOwner(owner);
}
}
// dynamic membership
if (groupCR.getUDynMembershipCond() != null) {
setDynMembership(group, anyTypeDAO.findUser(), groupCR.getUDynMembershipCond());
}
groupCR.getADynMembershipConds().forEach((type, fiql) -> {
AnyType anyType = anyTypeDAO.find(type);
if (anyType == null) {
LOG.warn("Ignoring invalid {}: {}", AnyType.class.getSimpleName(), type);
} else {
setDynMembership(group, anyType, fiql);
}
});
// type extensions
groupCR.getTypeExtensions().forEach(typeExtTO -> {
AnyType anyType = anyTypeDAO.find(typeExtTO.getAnyType());
if (anyType == null) {
LOG.warn("Ignoring invalid {}: {}", AnyType.class.getSimpleName(), typeExtTO.getAnyType());
} else {
TypeExtension typeExt = entityFactory.newEntity(TypeExtension.class);
typeExt.setAnyType(anyType);
typeExt.setGroup(group);
group.add(typeExt);
typeExtTO.getAuxClasses().forEach(name -> {
AnyTypeClass anyTypeClass = anyTypeClassDAO.find(name);
if (anyTypeClass == null) {
LOG.warn("Ignoring invalid {}: {}", AnyTypeClass.class.getSimpleName(), name);
} else {
typeExt.add(anyTypeClass);
}
});
if (typeExt.getAuxClasses().isEmpty()) {
group.getTypeExtensions().remove(typeExt);
typeExt.setGroup(null);
}
}
});
// Throw composite exception if there is at least one element set in the composing exceptions
if (scce.hasExceptions()) {
throw scce;
}
}
@Override
public PropagationByResource<String> update(final Group toBeUpdated, final GroupUR groupUR) {
// Re-merge any pending change from workflow tasks
Group group = groupDAO.save(toBeUpdated);
// Save projection on Resources (before update)
Map<String, ConnObjectTO> beforeOnResources =
onResources(group, groupDAO.findAllResourceKeys(group.getKey()), null, false);
SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
// realm
setRealm(group, groupUR);
// name
if (groupUR.getName() != null && StringUtils.isNotBlank(groupUR.getName().getValue())) {
group.setName(groupUR.getName().getValue());
}
// owner
PropagationByResource<String> ownerPropByRes = new PropagationByResource<>();
if (groupUR.getUserOwner() != null) {
if (groupUR.getUserOwner().getValue() == null) {
if (group.getUserOwner() != null) {
group.setUserOwner(null);
ownerPropByRes.addAll(ResourceOperation.UPDATE, groupDAO.findAllResourceKeys(group.getKey()));
}
} else {
User userOwner = userDAO.find(groupUR.getUserOwner().getValue());
if (userOwner == null) {
LOG.debug("Unable to find user owner for group {} by key {}",
group.getKey(), groupUR.getUserOwner().getValue());
group.setUserOwner(null);
} else {
group.setUserOwner(userOwner);
ownerPropByRes.addAll(ResourceOperation.UPDATE, groupDAO.findAllResourceKeys(group.getKey()));
}
}
}
if (groupUR.getGroupOwner() != null) {
if (groupUR.getGroupOwner().getValue() == null) {
if (group.getGroupOwner() != null) {
group.setGroupOwner(null);
ownerPropByRes.addAll(ResourceOperation.UPDATE, groupDAO.findAllResourceKeys(group.getKey()));
}
} else {
Group groupOwner = groupDAO.find(groupUR.getGroupOwner().getValue());
if (groupOwner == null) {
LOG.debug("Unable to find group owner for group {} by key {}",
group.getKey(), groupUR.getGroupOwner().getValue());
group.setGroupOwner(null);
} else {
group.setGroupOwner(groupOwner);
ownerPropByRes.addAll(ResourceOperation.UPDATE, groupDAO.findAllResourceKeys(group.getKey()));
}
}
}
// attributes and resources
fill(group, groupUR, anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce);
group = groupDAO.save(group);
// dynamic membership
if (groupUR.getUDynMembershipCond() == null) {
if (group.getUDynMembership() != null) {
group.getUDynMembership().setGroup(null);
group.setUDynMembership(null);
groupDAO.clearUDynMembers(group);
}
} else {
setDynMembership(group, anyTypeDAO.findUser(), groupUR.getUDynMembershipCond());
}
for (Iterator<? extends ADynGroupMembership> itor = group.getADynMemberships().iterator(); itor.hasNext();) {
ADynGroupMembership memb = itor.next();
memb.setGroup(null);
itor.remove();
}
groupDAO.clearADynMembers(group);
for (Map.Entry<String, String> entry : groupUR.getADynMembershipConds().entrySet()) {
AnyType anyType = anyTypeDAO.find(entry.getKey());
if (anyType == null) {
LOG.warn("Ignoring invalid {}: {}", AnyType.class.getSimpleName(), entry.getKey());
} else {
setDynMembership(group, anyType, entry.getValue());
}
}
group = groupDAO.saveAndRefreshDynMemberships(group);
// type extensions
for (TypeExtensionTO typeExtTO : groupUR.getTypeExtensions()) {
AnyType anyType = anyTypeDAO.find(typeExtTO.getAnyType());
if (anyType == null) {
LOG.warn("Ignoring invalid {}: {}", AnyType.class.getSimpleName(), typeExtTO.getAnyType());
} else {
TypeExtension typeExt = group.getTypeExtension(anyType).orElse(null);
if (typeExt == null) {
typeExt = entityFactory.newEntity(TypeExtension.class);
typeExt.setAnyType(anyType);
typeExt.setGroup(group);
group.add(typeExt);
}
// add all classes contained in the TO
for (String name : typeExtTO.getAuxClasses()) {
AnyTypeClass anyTypeClass = anyTypeClassDAO.find(name);
if (anyTypeClass == null) {
LOG.warn("Ignoring invalid {}: {}", AnyTypeClass.class.getSimpleName(), name);
} else {
typeExt.add(anyTypeClass);
}
}
// remove all classes not contained in the TO
typeExt.getAuxClasses().
removeIf(anyTypeClass -> !typeExtTO.getAuxClasses().contains(anyTypeClass.getKey()));
// only consider non-empty type extensions
if (typeExt.getAuxClasses().isEmpty()) {
group.getTypeExtensions().remove(typeExt);
typeExt.setGroup(null);
}
}
}
// remove all type extensions not contained in the TO
group.getTypeExtensions().
removeIf(typeExt -> groupUR.getTypeExtension(typeExt.getAnyType().getKey()).isEmpty());
// Throw composite exception if there is at least one element set in the composing exceptions
if (scce.hasExceptions()) {
throw scce;
}
// Re-merge any pending change from above
group = groupDAO.save(group);
// Build final information for next stage (propagation)
PropagationByResource<String> propByRes = propByRes(
beforeOnResources, onResources(group, groupDAO.findAllResourceKeys(group.getKey()), null, false));
propByRes.merge(ownerPropByRes);
return propByRes;
}
@Override
public TypeExtensionTO getTypeExtensionTO(final TypeExtension typeExt) {
TypeExtensionTO typeExtTO = new TypeExtensionTO();
typeExtTO.setAnyType(typeExt.getAnyType().getKey());
typeExtTO.getAuxClasses().addAll(
typeExt.getAuxClasses().stream().map(Entity::getKey).collect(Collectors.toList()));
return typeExtTO;
}
@Transactional(readOnly = true)
@Override
public GroupTO getGroupTO(final Group group, final boolean details) {
GroupTO groupTO = new GroupTO();
groupTO.setCreator(group.getCreator());
groupTO.setCreationDate(group.getCreationDate());
groupTO.setCreationContext(group.getCreationContext());
groupTO.setLastModifier(group.getLastModifier());
groupTO.setLastChangeDate(group.getLastChangeDate());
groupTO.setLastChangeContext(group.getLastChangeContext());
groupTO.setKey(group.getKey());
groupTO.setName(group.getName());
groupTO.setStatus(group.getStatus());
if (group.getUserOwner() != null) {
groupTO.setUserOwner(group.getUserOwner().getKey());
}
if (group.getGroupOwner() != null) {
groupTO.setGroupOwner(group.getGroupOwner().getKey());
}
Map<DerSchema, String> derAttrValues = derAttrHandler.getValues(group);
Map<VirSchema, List<String>> virAttrValues = details
? virAttrHandler.getValues(group)
: Collections.<VirSchema, List<String>>emptyMap();
fillTO(groupTO,
group.getRealm().getFullPath(),
group.getAuxClasses(),
group.getPlainAttrs(),
derAttrValues,
virAttrValues,
group.getResources());
// dynamic realms
groupTO.getDynRealms().addAll(groupDAO.findDynRealms(group.getKey()));
// Static user and AnyType membership counts
groupTO.setStaticUserMembershipCount(groupDAO.countUMembers(group));
groupTO.setStaticAnyObjectMembershipCount(groupDAO.countAMembers(group));
// Dynamic user and AnyType membership counts
groupTO.setDynamicUserMembershipCount(groupDAO.countUDynMembers(group));
groupTO.setDynamicAnyObjectMembershipCount(groupDAO.countADynMembers(group));
if (group.getUDynMembership() != null) {
groupTO.setUDynMembershipCond(group.getUDynMembership().getFIQLCond());
}
group.getADynMemberships().
forEach(memb -> groupTO.getADynMembershipConds().put(memb.getAnyType().getKey(), memb.getFIQLCond()));
group.getTypeExtensions().
forEach(typeExt -> groupTO.getTypeExtensions().add(getTypeExtensionTO(typeExt)));
return groupTO;
}
@Transactional(readOnly = true)
@Override
public GroupTO getGroupTO(final String key) {
return getGroupTO(groupDAO.authFind(key), true);
}
protected static void populateTransitiveResources(
final Group group, final Any<?> any, final Map<String, PropagationByResource<String>> result) {
PropagationByResource<String> propByRes = new PropagationByResource<>();
group.getResources().forEach(resource -> {
if (!any.getResources().contains(resource)) {
propByRes.add(ResourceOperation.DELETE, resource.getKey());
}
if (!propByRes.isEmpty()) {
result.put(any.getKey(), propByRes);
}
});
}
@Transactional(readOnly = true)
@Override
public Map<String, PropagationByResource<String>> findAnyObjectsWithTransitiveResources(final String groupKey) {
Group group = groupDAO.authFind(groupKey);
Map<String, PropagationByResource<String>> result = new HashMap<>();
groupDAO.findAMemberships(group).
forEach((membership) -> populateTransitiveResources(group, membership.getLeftEnd(), result));
return result;
}
@Transactional(readOnly = true)
@Override
public Map<String, PropagationByResource<String>> findUsersWithTransitiveResources(final String groupKey) {
Group group = groupDAO.authFind(groupKey);
Map<String, PropagationByResource<String>> result = new HashMap<>();
groupDAO.findUMemberships(group).
forEach((membership) -> populateTransitiveResources(group, membership.getLeftEnd(), result));
return result;
}
}