blob: bbf2614eae5298b361bf0b07e4cd93ebf708519d [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.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.AnyOperations;
import org.apache.syncope.common.lib.EntityTOUtils;
import org.apache.syncope.common.lib.request.AnyCR;
import org.apache.syncope.common.lib.request.AnyUR;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.to.AnyObjectTO;
import org.apache.syncope.common.lib.to.AnyTO;
import org.apache.syncope.common.lib.Attr;
import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.GroupTO;
import org.apache.syncope.common.lib.to.RealmTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
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.Realm;
import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.apache.syncope.core.persistence.api.entity.task.PullTask;
import org.apache.syncope.core.provisioning.api.MappingManager;
import org.apache.syncope.core.spring.policy.InvalidPasswordRuleConf;
import org.apache.syncope.core.spring.security.Encryptor;
import org.apache.syncope.core.spring.security.PasswordGenerator;
import org.apache.syncope.core.spring.security.SecureRandomUtils;
import org.identityconnectors.common.security.GuardedByteArray;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.common.security.SecurityUtil;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
public class ConnObjectUtils {
protected static final Logger LOG = LoggerFactory.getLogger(ConnObjectUtils.class);
protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
/**
* Extract password value from passed value (if instance of GuardedString or GuardedByteArray).
*
* @param pwd received from the underlying connector
* @return password value
*/
public static String getPassword(final Object pwd) {
StringBuilder result = new StringBuilder();
if (pwd instanceof GuardedString) {
result.append(SecurityUtil.decrypt((GuardedString) pwd));
} else if (pwd instanceof GuardedByteArray) {
result.append(Arrays.toString(SecurityUtil.decrypt((GuardedByteArray) pwd)));
} else if (pwd instanceof String) {
result.append((String) pwd);
} else {
result.append(pwd.toString());
}
return result.toString();
}
/**
* Builds {@link ConnObjectTO} out of a collection of {@link Attribute} instances.
*
* @param fiql FIQL expression to uniquely identify the given Connector Object
* @param attrs attributes
* @return transfer object
*/
public static ConnObjectTO getConnObjectTO(final String fiql, final Set<Attribute> attrs) {
ConnObjectTO connObjectTO = new ConnObjectTO();
connObjectTO.setFiql(fiql);
if (!CollectionUtils.isEmpty(attrs)) {
connObjectTO.getAttrs().addAll(attrs.stream().map(attr -> {
Attr attrTO = new Attr();
attrTO.setSchema(attr.getName());
if (!CollectionUtils.isEmpty(attr.getValue())) {
attr.getValue().stream().filter(Objects::nonNull).forEach(value -> {
if (value instanceof GuardedString || value instanceof GuardedByteArray) {
attrTO.getValues().add(getPassword(value));
} else if (value instanceof byte[]) {
attrTO.getValues().add(Base64.getEncoder().encodeToString((byte[]) value));
} else {
attrTO.getValues().add(value.toString());
}
});
}
return attrTO;
}).collect(Collectors.toList()));
}
return connObjectTO;
}
protected final TemplateUtils templateUtils;
protected final RealmDAO realmDAO;
protected final UserDAO userDAO;
protected final ExternalResourceDAO resourceDAO;
protected final PasswordGenerator passwordGenerator;
protected final MappingManager mappingManager;
protected final AnyUtilsFactory anyUtilsFactory;
public ConnObjectUtils(
final TemplateUtils templateUtils,
final RealmDAO realmDAO,
final UserDAO userDAO,
final ExternalResourceDAO resourceDAO,
final PasswordGenerator passwordGenerator,
final MappingManager mappingManager,
final AnyUtilsFactory anyUtilsFactory) {
this.templateUtils = templateUtils;
this.realmDAO = realmDAO;
this.userDAO = userDAO;
this.resourceDAO = resourceDAO;
this.passwordGenerator = passwordGenerator;
this.mappingManager = mappingManager;
this.anyUtilsFactory = anyUtilsFactory;
}
/**
* Build a UserCR / GroupCR / AnyObjectCR out of connector object attributes and schema mapping.
*
* @param obj connector object
* @param pullTask pull task
* @param provision provision information
* @param generatePasswordIfPossible whether password value shall be generated, in case not found from
* connector object and allowed by resource configuration
* @param <C> create request type
* @return create request
*/
@Transactional(readOnly = true)
public <C extends AnyCR> C getAnyCR(
final ConnectorObject obj,
final PullTask pullTask,
final Provision provision,
final boolean generatePasswordIfPossible) {
AnyTO anyTO = getAnyTOFromConnObject(obj, pullTask, provision);
C anyCR = anyUtilsFactory.getInstance(provision.getAnyType().getKind()).newAnyCR();
EntityTOUtils.toAnyCR(anyTO, anyCR);
// (for users) if password was not set above, generate if resource is configured for that
if (anyCR instanceof UserCR
&& StringUtils.isBlank(((UserCR) anyCR).getPassword())
&& generatePasswordIfPossible && provision.getResource().isRandomPwdIfNotProvided()) {
UserCR userCR = (UserCR) anyCR;
List<PasswordPolicy> passwordPolicies = new ArrayList<>();
Realm realm = realmDAO.findByFullPath(userCR.getRealm());
if (realm != null) {
realmDAO.findAncestors(realm).stream().
filter(ancestor -> ancestor.getPasswordPolicy() != null).
forEach(ancestor -> passwordPolicies.add(ancestor.getPasswordPolicy()));
}
userCR.getResources().stream().
map(resource -> resourceDAO.find(resource)).
filter(resource -> resource != null && resource.getPasswordPolicy() != null).
forEach(resource -> passwordPolicies.add(resource.getPasswordPolicy()));
String password;
try {
password = passwordGenerator.generate(passwordPolicies);
} catch (InvalidPasswordRuleConf e) {
LOG.error("Could not generate policy-compliant random password for {}", userCR, e);
password = SecureRandomUtils.generateRandomPassword(16);
}
userCR.setPassword(password);
}
return anyCR;
}
public RealmTO getRealmTO(final ConnectorObject obj, final OrgUnit orgUnit) {
RealmTO realmTO = new RealmTO();
MappingUtils.getPullItems(orgUnit.getItems().stream()).forEach(item
-> mappingManager.setIntValues(item, obj.getAttributeByName(item.getExtAttrName()), realmTO));
return realmTO;
}
/**
* Build {@link AnyUR} out of connector object attributes and schema mapping.
*
* @param key any object to be updated
* @param obj connector object
* @param original any object to get diff from
* @param pullTask pull task
* @param provision provision information
* @param <U> any object
* @return modifications for the any object to be updated
*/
@SuppressWarnings("unchecked")
@Transactional(readOnly = true)
public <U extends AnyUR> U getAnyUR(
final String key,
final ConnectorObject obj,
final AnyTO original,
final PullTask pullTask,
final Provision provision) {
AnyTO updated = getAnyTOFromConnObject(obj, pullTask, provision);
updated.setKey(key);
U anyUR = null;
switch (provision.getAnyType().getKind()) {
case USER:
UserTO originalUser = (UserTO) original;
UserTO updatedUser = (UserTO) updated;
if (StringUtils.isBlank(updatedUser.getUsername())) {
updatedUser.setUsername(originalUser.getUsername());
}
// update password if and only if password is really changed
User user = userDAO.authFind(key);
if (StringUtils.isBlank(updatedUser.getPassword())
|| ENCRYPTOR.verify(updatedUser.getPassword(),
user.getCipherAlgorithm(), user.getPassword())) {
updatedUser.setPassword(null);
}
updatedUser.setSecurityQuestion(originalUser.getSecurityQuestion());
if (!mappingManager.hasMustChangePassword(provision)) {
updatedUser.setMustChangePassword(originalUser.isMustChangePassword());
}
anyUR = (U) AnyOperations.diff(updatedUser, originalUser, true);
break;
case GROUP:
GroupTO originalGroup = (GroupTO) original;
GroupTO updatedGroup = (GroupTO) updated;
if (StringUtils.isBlank(updatedGroup.getName())) {
updatedGroup.setName(originalGroup.getName());
}
updatedGroup.setUserOwner(originalGroup.getUserOwner());
updatedGroup.setGroupOwner(originalGroup.getGroupOwner());
updatedGroup.setUDynMembershipCond(originalGroup.getUDynMembershipCond());
updatedGroup.getADynMembershipConds().putAll(originalGroup.getADynMembershipConds());
updatedGroup.getTypeExtensions().addAll(originalGroup.getTypeExtensions());
anyUR = (U) AnyOperations.diff(updatedGroup, originalGroup, true);
break;
case ANY_OBJECT:
AnyObjectTO originalAnyObject = (AnyObjectTO) original;
AnyObjectTO updatedAnyObject = (AnyObjectTO) updated;
if (StringUtils.isBlank(updatedAnyObject.getName())) {
updatedAnyObject.setName(originalAnyObject.getName());
}
anyUR = (U) AnyOperations.diff(updatedAnyObject, originalAnyObject, true);
break;
default:
}
if (anyUR != null) {
// ensure not to include incidental realm changes in the patch
anyUR.setRealm(null);
// SYNCOPE-1343, remove null or empty values from the patch plain attributes
AnyOperations.cleanEmptyAttrs(updated, anyUR);
}
return anyUR;
}
protected <T extends AnyTO> T getAnyTOFromConnObject(
final ConnectorObject obj, final PullTask pullTask, final Provision provision) {
T anyTO = anyUtilsFactory.getInstance(provision.getAnyType().getKind()).newAnyTO();
anyTO.setType(provision.getAnyType().getKey());
anyTO.getAuxClasses().addAll(provision.getAuxClasses().stream().
map(AnyTypeClass::getKey).collect(Collectors.toList()));
// 1. fill with data from connector object
anyTO.setRealm(pullTask.getDestinationRealm().getFullPath());
MappingUtils.getPullItems(provision.getMapping().getItems().stream()).forEach(
item -> mappingManager.setIntValues(item, obj.getAttributeByName(item.getExtAttrName()), anyTO));
// 2. add data from defined template (if any)
templateUtils.apply(anyTO, pullTask.getTemplate(provision.getAnyType()));
return anyTO;
}
}