blob: f81e8ac464fd126b6cf8f696949257830c6a16f5 [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.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
import org.apache.syncope.common.lib.Attr;
import org.apache.syncope.common.lib.SyncopeClientCompositeException;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.request.StringPatchItem;
import org.apache.syncope.common.lib.request.UserCR;
import org.apache.syncope.common.lib.request.UserUR;
import org.apache.syncope.common.lib.request.AttrPatch;
import org.apache.syncope.common.lib.request.PasswordPatch;
import org.apache.syncope.common.lib.to.ConnObjectTO;
import org.apache.syncope.common.lib.to.LinkedAccountTO;
import org.apache.syncope.common.lib.to.MembershipTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.PatchOperation;
import org.apache.syncope.common.lib.types.ResourceOperation;
import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
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.SecurityQuestionDAO;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.resource.Item;
import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.provisioning.api.PropagationByResource;
import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
import org.apache.syncope.core.persistence.api.dao.ApplicationDAO;
import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
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.RoleDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.AccessToken;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.Delegation;
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.PlainSchema;
import org.apache.syncope.core.persistence.api.entity.Privilege;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.RelationshipType;
import org.apache.syncope.core.persistence.api.entity.Role;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttr;
import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
import org.apache.syncope.core.persistence.api.entity.user.UMembership;
import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr;
import org.apache.syncope.core.persistence.api.entity.user.URelationship;
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.VirAttrHandler;
import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
import org.apache.syncope.core.spring.security.SecurityProperties;
import org.springframework.transaction.annotation.Transactional;
@Transactional(rollbackFor = { Throwable.class })
public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDataBinder {
protected final RoleDAO roleDAO;
protected final SecurityQuestionDAO securityQuestionDAO;
protected final ApplicationDAO applicationDAO;
protected final AccessTokenDAO accessTokenDAO;
protected final DelegationDAO delegationDAO;
protected final ConfParamOps confParamOps;
protected final SecurityProperties securityProperties;
public UserDataBinderImpl(
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 RoleDAO roleDAO,
final SecurityQuestionDAO securityQuestionDAO,
final ApplicationDAO applicationDAO,
final AccessTokenDAO accessTokenDAO,
final DelegationDAO delegationDAO,
final ConfParamOps confParamOps,
final SecurityProperties securityProperties) {
super(anyTypeDAO,
realmDAO,
anyTypeClassDAO,
anyObjectDAO,
userDAO,
groupDAO,
plainSchemaDAO,
plainAttrDAO,
plainAttrValueDAO,
resourceDAO,
relationshipTypeDAO,
entityFactory,
anyUtilsFactory,
derAttrHandler,
virAttrHandler,
mappingManager,
intAttrNameParser,
outboundMatcher);
this.roleDAO = roleDAO;
this.securityQuestionDAO = securityQuestionDAO;
this.applicationDAO = applicationDAO;
this.accessTokenDAO = accessTokenDAO;
this.delegationDAO = delegationDAO;
this.confParamOps = confParamOps;
this.securityProperties = securityProperties;
}
@Transactional(readOnly = true)
@Override
public UserTO returnUserTO(final UserTO userTO) {
if (!confParamOps.get(AuthContextUtils.getDomain(), "return.password.value", false, Boolean.class)) {
userTO.setPassword(null);
userTO.getLinkedAccounts().forEach(account -> account.setPassword(null));
}
return userTO;
}
@Transactional(readOnly = true)
@Override
public UserTO getAuthenticatedUserTO() {
UserTO authUserTO;
String authUsername = AuthContextUtils.getUsername();
if (securityProperties.getAnonymousUser().equals(authUsername)) {
authUserTO = new UserTO();
authUserTO.setKey(null);
authUserTO.setUsername(securityProperties.getAnonymousUser());
} else if (securityProperties.getAdminUser().equals(authUsername)) {
authUserTO = new UserTO();
authUserTO.setKey(null);
authUserTO.setUsername(securityProperties.getAdminUser());
} else {
User authUser = userDAO.findByUsername(authUsername);
authUserTO = getUserTO(authUser, true);
}
return authUserTO;
}
private void setPassword(final User user, final String password, final SyncopeClientCompositeException scce) {
try {
String algorithm = confParamOps.get(AuthContextUtils.getDomain(),
"password.cipher.algorithm", CipherAlgorithm.AES.name(), String.class);
user.setPassword(password, CipherAlgorithm.valueOf(algorithm));
} catch (IllegalArgumentException e) {
SyncopeClientException invalidCiperAlgorithm = SyncopeClientException.build(ClientExceptionType.NotFound);
invalidCiperAlgorithm.getElements().add(e.getMessage());
scce.addException(invalidCiperAlgorithm);
throw scce;
}
}
private void linkedAccount(
final User user,
final LinkedAccountTO accountTO,
final AnyUtils anyUtils,
final SyncopeClientException invalidValues) {
ExternalResource resource = resourceDAO.find(accountTO.getResource());
if (resource == null) {
LOG.debug("Ignoring invalid resource {}", accountTO.getResource());
} else {
Optional<? extends LinkedAccount> found =
user.getLinkedAccount(resource.getKey(), accountTO.getConnObjectKeyValue());
LinkedAccount account = found.isPresent()
? found.get()
: new Supplier<LinkedAccount>() {
@Override
public LinkedAccount get() {
LinkedAccount acct = entityFactory.newEntity(LinkedAccount.class);
acct.setOwner(user);
user.add(acct);
acct.setConnObjectKeyValue(accountTO.getConnObjectKeyValue());
acct.setResource(resource);
return acct;
}
}.get();
account.setUsername(accountTO.getUsername());
if (StringUtils.isBlank(accountTO.getPassword())) {
account.setEncodedPassword(null, null);
} else if (!accountTO.getPassword().equals(account.getPassword())) {
account.setPassword(accountTO.getPassword(), CipherAlgorithm.AES);
}
account.setSuspended(accountTO.isSuspended());
accountTO.getPlainAttrs().stream().
filter(attrTO -> !attrTO.getValues().isEmpty()).
forEach(attrTO -> {
PlainSchema schema = getPlainSchema(attrTO.getSchema());
if (schema != null) {
LAPlainAttr attr = account.getPlainAttr(schema.getKey()).orElse(null);
if (attr == null) {
attr = entityFactory.newEntity(LAPlainAttr.class);
attr.setSchema(schema);
attr.setOwner(user);
attr.setAccount(account);
}
fillAttr(attrTO.getValues(), anyUtils, schema, attr, invalidValues);
if (attr.getValuesAsStrings().isEmpty()) {
attr.setOwner(null);
} else {
account.add(attr);
}
}
});
accountTO.getPrivileges().forEach(key -> {
Privilege privilege = applicationDAO.findPrivilege(key);
if (privilege == null) {
LOG.debug("Invalid privilege {}, ignoring", key);
} else {
account.add(privilege);
}
});
}
}
@Override
public void create(final User user, final UserCR userCR) {
SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
// set username
user.setUsername(userCR.getUsername());
// set password
if (StringUtils.isBlank(userCR.getPassword()) || !userCR.isStorePassword()) {
LOG.debug("Password was not provided or not required to be stored");
} else {
setPassword(user, userCR.getPassword(), scce);
user.setChangePwdDate(new Date());
}
user.setMustChangePassword(userCR.isMustChangePassword());
// security question / answer
if (userCR.getSecurityQuestion() != null) {
SecurityQuestion securityQuestion = securityQuestionDAO.find(userCR.getSecurityQuestion());
if (securityQuestion != null) {
user.setSecurityQuestion(securityQuestion);
}
}
user.setSecurityAnswer(userCR.getSecurityAnswer());
// roles
userCR.getRoles().forEach(roleKey -> {
Role role = roleDAO.find(roleKey);
if (role == null) {
LOG.warn("Ignoring unknown role with id {}", roleKey);
} else {
user.add(role);
}
});
// realm
Realm realm = realmDAO.findByFullPath(userCR.getRealm());
if (realm == null) {
SyncopeClientException noRealm = SyncopeClientException.build(ClientExceptionType.InvalidRealm);
noRealm.getElements().add("Invalid or null realm specified: " + userCR.getRealm());
scce.addException(noRealm);
}
user.setRealm(realm);
// relationships
Set<Pair<String, String>> relationships = new HashSet<>();
userCR.getRelationships().forEach(relationshipTO -> {
AnyObject otherEnd = anyObjectDAO.find(relationshipTO.getOtherEndKey());
if (otherEnd == null) {
LOG.debug("Ignoring invalid anyObject " + relationshipTO.getOtherEndKey());
} else if (relationships.contains(Pair.of(otherEnd.getKey(), relationshipTO.getType()))) {
LOG.error("{} was already in relationship {} with {}", otherEnd, relationshipTO.getType(), user);
SyncopeClientException assigned =
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
assigned.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName()
+ " in relationship " + relationshipTO.getType());
scce.addException(assigned);
} else if (user.getRealm().getFullPath().startsWith(otherEnd.getRealm().getFullPath())) {
relationships.add(Pair.of(otherEnd.getKey(), relationshipTO.getType()));
RelationshipType relationshipType = relationshipTypeDAO.find(relationshipTO.getType());
if (relationshipType == null) {
LOG.debug("Ignoring invalid relationship type {}", relationshipTO.getType());
} else {
URelationship relationship = entityFactory.newEntity(URelationship.class);
relationship.setType(relationshipType);
relationship.setRightEnd(otherEnd);
relationship.setLeftEnd(user);
user.add(relationship);
}
} else {
LOG.error("{} cannot be related to {}", otherEnd, user);
SyncopeClientException unrelatable =
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
unrelatable.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName()
+ " cannot be related");
scce.addException(unrelatable);
}
});
// memberships
Set<String> groups = new HashSet<>();
userCR.getMemberships().forEach(membershipTO -> {
Group group = membershipTO.getGroupKey() == null
? groupDAO.findByName(membershipTO.getGroupName())
: groupDAO.find(membershipTO.getGroupKey());
if (group == null) {
LOG.debug("Ignoring invalid group {}",
membershipTO.getGroupKey() + " / " + membershipTO.getGroupName());
} else if (groups.contains(group.getKey())) {
LOG.error("{} was already assigned to {}", group, user);
SyncopeClientException assigned =
SyncopeClientException.build(ClientExceptionType.InvalidMembership);
assigned.getElements().add("Group " + group.getName() + " was already assigned");
scce.addException(assigned);
} else if (user.getRealm().getFullPath().startsWith(group.getRealm().getFullPath())) {
groups.add(group.getKey());
UMembership membership = entityFactory.newEntity(UMembership.class);
membership.setRightEnd(group);
membership.setLeftEnd(user);
user.add(membership);
// membership attributes
fill(user, membership, membershipTO, anyUtilsFactory.getInstance(AnyTypeKind.USER), scce);
} else {
LOG.error("{} cannot be assigned to {}", group, user);
SyncopeClientException unassignable =
SyncopeClientException.build(ClientExceptionType.InvalidMembership);
unassignable.getElements().add("Group " + group.getName() + " cannot be assigned");
scce.addException(unassignable);
}
});
// linked accounts
SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
userCR.getLinkedAccounts().forEach(accountTO
-> linkedAccount(user, accountTO, anyUtilsFactory.getLinkedAccountInstance(), invalidValues));
if (!invalidValues.isEmpty()) {
scce.addException(invalidValues);
}
// attributes and resources
fill(user, userCR, anyUtilsFactory.getInstance(AnyTypeKind.USER), scce);
// Throw composite exception if there is at least one element set in the composing exceptions
if (scce.hasExceptions()) {
throw scce;
}
}
private boolean isPasswordMapped(final ExternalResource resource) {
return resource.getProvision(anyTypeDAO.findUser()).
filter(provision -> provision.getMapping() != null).
map(provision -> provision.getMapping().getItems().stream().anyMatch(Item::isPassword)).
orElse(false);
}
@Override
public Pair<PropagationByResource<String>, PropagationByResource<Pair<String, String>>> update(
final User toBeUpdated, final UserUR userUR) {
// Re-merge any pending change from workflow tasks
User user = userDAO.save(toBeUpdated);
PropagationByResource<String> propByRes = new PropagationByResource<>();
PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>();
SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
AnyUtils anyUtils = anyUtilsFactory.getInstance(AnyTypeKind.USER);
// password
String password = null;
boolean changePwd = false;
if (userUR.getPassword() != null) {
if (userUR.getPassword().getOperation() == PatchOperation.DELETE) {
user.setEncodedPassword(null, null);
changePwd = true;
} else if (StringUtils.isNotBlank(userUR.getPassword().getValue())) {
if (userUR.getPassword().isOnSyncope()) {
setPassword(user, userUR.getPassword().getValue(), scce);
user.setChangePwdDate(new Date());
}
password = userUR.getPassword().getValue();
changePwd = true;
}
if (changePwd) {
propByRes.addAll(ResourceOperation.UPDATE, userUR.getPassword().getResources());
}
}
// Save projection on Resources (before update)
Map<String, ConnObjectTO> beforeOnResources =
onResources(user, userDAO.findAllResourceKeys(user.getKey()), password, changePwd);
// realm
setRealm(user, userUR);
// username
if (userUR.getUsername() != null && StringUtils.isNotBlank(userUR.getUsername().getValue())) {
String oldUsername = user.getUsername();
user.setUsername(userUR.getUsername().getValue());
if (oldUsername.equals(AuthContextUtils.getUsername())) {
AuthContextUtils.updateUsername(userUR.getUsername().getValue());
}
AccessToken accessToken = accessTokenDAO.findByOwner(oldUsername);
if (accessToken != null) {
accessToken.setOwner(userUR.getUsername().getValue());
accessTokenDAO.save(accessToken);
}
}
// security question / answer:
if (userUR.getSecurityQuestion() != null) {
if (userUR.getSecurityQuestion().getValue() == null) {
user.setSecurityQuestion(null);
user.setSecurityAnswer(null);
} else {
SecurityQuestion securityQuestion =
securityQuestionDAO.find(userUR.getSecurityQuestion().getValue());
if (securityQuestion != null) {
user.setSecurityQuestion(securityQuestion);
user.setSecurityAnswer(userUR.getSecurityAnswer().getValue());
}
}
}
// roles
for (StringPatchItem patch : userUR.getRoles()) {
Role role = roleDAO.find(patch.getValue());
if (role == null) {
LOG.warn("Ignoring unknown role with key {}", patch.getValue());
} else {
switch (patch.getOperation()) {
case ADD_REPLACE:
user.add(role);
break;
case DELETE:
default:
user.getRoles().remove(role);
}
}
}
// attributes and resources
fill(user, userUR, anyUtils, scce);
// relationships
Set<Pair<String, String>> relationships = new HashSet<>();
userUR.getRelationships().stream().filter(patch -> patch.getRelationshipTO() != null).forEach(patch -> {
RelationshipType relationshipType = relationshipTypeDAO.find(patch.getRelationshipTO().getType());
if (relationshipType == null) {
LOG.debug("Ignoring invalid relationship type {}", patch.getRelationshipTO().getType());
} else {
user.getRelationship(relationshipType, patch.getRelationshipTO().getOtherEndKey()).
ifPresent(relationship -> {
user.getRelationships().remove(relationship);
relationship.setLeftEnd(null);
});
if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
AnyObject otherEnd = anyObjectDAO.find(patch.getRelationshipTO().getOtherEndKey());
if (otherEnd == null) {
LOG.debug("Ignoring invalid any object {}", patch.getRelationshipTO().getOtherEndKey());
} else if (relationships.contains(
Pair.of(otherEnd.getKey(), patch.getRelationshipTO().getType()))) {
LOG.error("{} was already in relationship {} with {}",
user, patch.getRelationshipTO().getType(), otherEnd);
SyncopeClientException assigned =
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
assigned.getElements().add("User was already in relationship "
+ patch.getRelationshipTO().getType() + " with "
+ otherEnd.getType().getKey() + " " + otherEnd.getName());
scce.addException(assigned);
} else if (user.getRealm().getFullPath().startsWith(otherEnd.getRealm().getFullPath())) {
relationships.add(Pair.of(otherEnd.getKey(), patch.getRelationshipTO().getType()));
URelationship newRelationship = entityFactory.newEntity(URelationship.class);
newRelationship.setType(relationshipType);
newRelationship.setRightEnd(otherEnd);
newRelationship.setLeftEnd(user);
user.add(newRelationship);
} else {
LOG.error("{} cannot be related to {}", otherEnd, user);
SyncopeClientException unrelatable =
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
unrelatable.getElements().add(otherEnd.getType().getKey() + " " + otherEnd.getName()
+ " cannot be related");
scce.addException(unrelatable);
}
}
}
});
SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
// memberships
Set<String> groups = new HashSet<>();
userUR.getMemberships().stream().filter(patch -> patch.getGroup() != null).forEach(patch -> {
user.getMembership(patch.getGroup()).ifPresent(membership -> {
user.remove(membership);
membership.setLeftEnd(null);
user.getPlainAttrs(membership).forEach(attr -> {
user.remove(attr);
attr.setOwner(null);
attr.setMembership(null);
plainAttrValueDAO.deleteAll(attr, anyUtils);
plainAttrDAO.delete(attr);
});
if (patch.getOperation() == PatchOperation.DELETE) {
propByRes.addAll(
ResourceOperation.UPDATE,
groupDAO.findAllResourceKeys((membership.getRightEnd().getKey())));
}
});
if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
Group group = groupDAO.find(patch.getGroup());
if (group == null) {
LOG.debug("Ignoring invalid group {}", patch.getGroup());
} else if (groups.contains(group.getKey())) {
LOG.error("Multiple patches for group {} of {} were found", group, user);
SyncopeClientException assigned =
SyncopeClientException.build(ClientExceptionType.InvalidMembership);
assigned.getElements().add("Multiple patches for group " + group.getName() + " were found");
scce.addException(assigned);
} else if (user.getRealm().getFullPath().startsWith(group.getRealm().getFullPath())) {
groups.add(group.getKey());
UMembership newMembership = entityFactory.newEntity(UMembership.class);
newMembership.setRightEnd(group);
newMembership.setLeftEnd(user);
user.add(newMembership);
patch.getPlainAttrs().forEach(attrTO -> {
PlainSchema schema = getPlainSchema(attrTO.getSchema());
if (schema == null) {
LOG.debug("Invalid " + PlainSchema.class.getSimpleName()
+ "{}, ignoring...", attrTO.getSchema());
} else {
UPlainAttr attr = user.getPlainAttr(schema.getKey(), newMembership).orElse(null);
if (attr == null) {
LOG.debug("No plain attribute found for {} and membership of {}",
schema, newMembership.getRightEnd());
attr = anyUtils.newPlainAttr();
attr.setOwner(user);
attr.setMembership(newMembership);
attr.setSchema(schema);
user.add(attr);
processAttrPatch(
user,
new AttrPatch.Builder(attrTO).build(),
schema,
attr,
anyUtils,
invalidValues);
}
}
});
if (!invalidValues.isEmpty()) {
scce.addException(invalidValues);
}
propByRes.addAll(ResourceOperation.UPDATE, groupDAO.findAllResourceKeys(group.getKey()));
// SYNCOPE-686: if password is invertible and we are adding resources with password mapping,
// ensure that they are counted for password propagation
if (toBeUpdated.canDecodePassword()) {
if (userUR.getPassword() == null) {
userUR.setPassword(new PasswordPatch());
}
group.getResources().stream().
filter(this::isPasswordMapped).
forEach(resource -> userUR.getPassword().getResources().add(resource.getKey()));
}
} else {
LOG.error("{} cannot be assigned to {}", group, user);
SyncopeClientException unassignable =
SyncopeClientException.build(ClientExceptionType.InvalidMembership);
unassignable.getElements().add("Group " + group.getName() + " cannot be assigned");
scce.addException(unassignable);
}
}
});
// linked accounts
userUR.getLinkedAccounts().stream().filter(patch -> patch.getLinkedAccountTO() != null).forEach(patch -> {
user.getLinkedAccount(
patch.getLinkedAccountTO().getResource(),
patch.getLinkedAccountTO().getConnObjectKeyValue()).ifPresent(account -> {
if (patch.getOperation() == PatchOperation.DELETE) {
user.getLinkedAccounts().remove(account);
account.setOwner(null);
propByLinkedAccount.add(
ResourceOperation.DELETE,
Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()));
}
account.getPlainAttrs().stream().collect(Collectors.toSet()).forEach(attr -> {
account.remove(attr);
attr.setOwner(null);
attr.setAccount(null);
plainAttrValueDAO.deleteAll(attr, anyUtilsFactory.getLinkedAccountInstance());
plainAttrDAO.delete(attr);
});
});
if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
linkedAccount(
user,
patch.getLinkedAccountTO(),
anyUtilsFactory.getLinkedAccountInstance(),
invalidValues);
}
});
user.getLinkedAccounts().forEach(account -> propByLinkedAccount.add(
ResourceOperation.CREATE,
Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())));
// 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
User saved = userDAO.save(user);
// Build final information for next stage (propagation)
Map<String, ConnObjectTO> afterOnResources =
onResources(user, userDAO.findAllResourceKeys(user.getKey()), password, changePwd);
propByRes.merge(propByRes(beforeOnResources, afterOnResources));
if (userUR.getMustChangePassword() != null) {
user.setMustChangePassword(userUR.getMustChangePassword().getValue());
propByRes.addAll(
ResourceOperation.UPDATE,
anyUtils.getAllResources(saved).stream().
map(resource -> resource.getProvision(saved.getType())).
filter(Optional::isPresent).map(Optional::get).
filter(mappingManager::hasMustChangePassword).
map(provision -> provision.getResource().getKey()).
collect(Collectors.toSet()));
}
return Pair.of(propByRes, propByLinkedAccount);
}
@Transactional(readOnly = true)
@Override
public LinkedAccountTO getLinkedAccountTO(final LinkedAccount account) {
LinkedAccountTO accountTO = new LinkedAccountTO.Builder(
account.getKey(), account.getResource().getKey(), account.getConnObjectKeyValue()).
username(account.getUsername()).
password(account.getPassword()).
suspended(BooleanUtils.isTrue(account.isSuspended())).
build();
account.getPlainAttrs().forEach(plainAttr -> {
accountTO.getPlainAttrs().add(
new Attr.Builder(plainAttr.getSchema().getKey()).
values(plainAttr.getValuesAsStrings()).build());
});
accountTO.getPrivileges().addAll(account.getPrivileges().stream().
map(Entity::getKey).collect(Collectors.toList()));
return accountTO;
}
@Transactional(readOnly = true)
@Override
public UserTO getUserTO(final User user, final boolean details) {
UserTO userTO = new UserTO();
userTO.setCreator(user.getCreator());
userTO.setCreationDate(user.getCreationDate());
userTO.setCreationContext(user.getCreationContext());
userTO.setLastModifier(user.getLastModifier());
userTO.setLastChangeDate(user.getLastChangeDate());
userTO.setLastChangeContext(user.getLastChangeContext());
userTO.setChangePwdDate(user.getChangePwdDate());
userTO.setFailedLogins(user.getFailedLogins());
userTO.setLastLoginDate(user.getLastLoginDate());
userTO.setToken(user.getToken());
userTO.setTokenExpireTime(user.getTokenExpireTime());
userTO.setKey(user.getKey());
userTO.setUsername(user.getUsername());
userTO.setPassword(user.getPassword());
userTO.setType(user.getType().getKey());
userTO.setStatus(user.getStatus());
userTO.setSuspended(BooleanUtils.isTrue(user.isSuspended()));
userTO.setMustChangePassword(user.isMustChangePassword());
if (user.getSecurityQuestion() != null) {
userTO.setSecurityQuestion(user.getSecurityQuestion().getKey());
}
fillTO(userTO, user.getRealm().getFullPath(),
user.getAuxClasses(),
user.getPlainAttrs(),
derAttrHandler.getValues(user),
details ? virAttrHandler.getValues(user) : Map.of(),
userDAO.findAllResources(user));
// dynamic realms
userTO.getDynRealms().addAll(userDAO.findDynRealms(user.getKey()));
if (details) {
// roles
userTO.getRoles().addAll(user.getRoles().stream().map(Entity::getKey).collect(Collectors.toList()));
// dynamic roles
userTO.getDynRoles().addAll(
userDAO.findDynRoles(user.getKey()).stream().map(Entity::getKey).collect(Collectors.toList()));
// privileges
userTO.getPrivileges().addAll(userDAO.findAllRoles(user).stream().
flatMap(role -> role.getPrivileges().stream()).map(Entity::getKey).collect(Collectors.toSet()));
// relationships
userTO.getRelationships().addAll(user.getRelationships().stream().
map(relationship -> getRelationshipTO(relationship.getType().getKey(), relationship.getRightEnd())).
collect(Collectors.toList()));
// memberships
userTO.getMemberships().addAll(
user.getMemberships().stream().map(membership -> getMembershipTO(
user.getPlainAttrs(membership),
derAttrHandler.getValues(user, membership),
virAttrHandler.getValues(user, membership),
membership)).collect(Collectors.toList()));
// dynamic memberships
userTO.getDynMemberships().addAll(
userDAO.findDynGroups(user.getKey()).stream().
map(group -> new MembershipTO.Builder(group.getKey()).groupName(group.getName()).build()).
collect(Collectors.toList()));
// linked accounts
userTO.getLinkedAccounts().addAll(
user.getLinkedAccounts().stream().map(this::getLinkedAccountTO).collect(Collectors.toList()));
// delegations
userTO.getDelegatingDelegations().addAll(
delegationDAO.findByDelegating(user).stream().map(Delegation::getKey).collect(Collectors.toList()));
userTO.getDelegatedDelegations().addAll(
delegationDAO.findByDelegated(user).stream().map(Delegation::getKey).collect(Collectors.toList()));
}
return userTO;
}
@Transactional(readOnly = true)
@Override
public UserTO getUserTO(final String key) {
return getUserTO(userDAO.authFind(key), true);
}
}