http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/AuditManager.java ---------------------------------------------------------------------- diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/AuditManager.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/AuditManager.java new file mode 100644 index 0000000..474b96d --- /dev/null +++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/AuditManager.java @@ -0,0 +1,109 @@ +/* + * 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.misc; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditElements.Result; +import org.apache.syncope.common.lib.types.AuditLoggerName; +import org.apache.syncope.common.lib.types.LoggerLevel; +import org.apache.syncope.core.persistence.api.dao.LoggerDAO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Component +public class AuditManager { + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(AuditManager.class); + + @Autowired + private LoggerDAO loggerDAO; + + public void audit( + final AuditElements.EventCategoryType type, + final String category, + final String subcategory, + final String event, + final Result result, + final Object before, + final Object output, + final Object... input) { + + final Throwable throwable; + final StringBuilder message = new StringBuilder(); + + message.append("BEFORE:\n"); + message.append("\t").append(before == null ? "unknown" : before).append("\n"); + + message.append("INPUT:\n"); + + if (ArrayUtils.isNotEmpty(input)) { + for (Object obj : input) { + message.append("\t").append(obj == null ? null : obj.toString()).append("\n"); + } + } else { + message.append("\t").append("none").append("\n"); + } + + message.append("OUTPUT:\n"); + + if (output instanceof Throwable) { + throwable = (Throwable) output; + message.append("\t").append(throwable.getMessage()); + } else { + throwable = null; + message.append("\t").append(output == null ? "none" : output.toString()); + } + + AuditLoggerName auditLoggerName = null; + try { + auditLoggerName = new AuditLoggerName(type, category, subcategory, event, result); + } catch (IllegalArgumentException e) { + LOG.error("Invalid audit parameters, aborting...", e); + } + + if (auditLoggerName != null) { + org.apache.syncope.core.persistence.api.entity.Logger syncopeLogger = + loggerDAO.find(auditLoggerName.toLoggerName()); + if (syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG) { + StringBuilder auditMessage = new StringBuilder(); + + final SecurityContext ctx = SecurityContextHolder.getContext(); + if (ctx != null && ctx.getAuthentication() != null) { + auditMessage.append('[').append(ctx.getAuthentication().getName()).append(']').append(' '); + } + auditMessage.append(message); + + final Logger logger = LoggerFactory.getLogger(auditLoggerName.toLoggerName()); + if (throwable == null) { + logger.debug(auditMessage.toString()); + } else { + logger.debug(auditMessage.toString(), throwable); + } + } + } + } +}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/ConnObjectUtil.java ---------------------------------------------------------------------- diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/ConnObjectUtil.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/ConnObjectUtil.java new file mode 100644 index 0000000..adb1637 --- /dev/null +++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/ConnObjectUtil.java @@ -0,0 +1,767 @@ +/* + * 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.misc; + +import org.apache.syncope.core.misc.policy.InvalidPasswordPolicySpecException; +import org.apache.syncope.core.misc.security.PasswordGenerator; +import org.apache.syncope.core.misc.security.SecureRandomUtil; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.AttributableOperations; +import org.apache.syncope.common.lib.mod.AbstractAttributableMod; +import org.apache.syncope.common.lib.to.AbstractAttributableTO; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.ConnObjectTO; +import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.RoleTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.AttrSchemaType; +import org.apache.syncope.common.lib.types.AttributableType; +import org.apache.syncope.common.lib.types.IntMappingType; +import org.apache.syncope.common.lib.types.MappingPurpose; +import org.apache.syncope.common.lib.types.PasswordPolicySpec; +import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException; +import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; +import org.apache.syncope.core.persistence.api.dao.NotFoundException; +import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.PolicyDAO; +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.Attributable; +import org.apache.syncope.core.persistence.api.entity.AttributableUtil; +import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.MappingItem; +import org.apache.syncope.core.persistence.api.entity.PasswordPolicy; +import org.apache.syncope.core.persistence.api.entity.PlainAttrValue; +import org.apache.syncope.core.persistence.api.entity.PlainSchema; +import org.apache.syncope.core.persistence.api.entity.Subject; +import org.apache.syncope.core.persistence.api.entity.VirAttr; +import org.apache.syncope.core.persistence.api.entity.membership.Membership; +import org.apache.syncope.core.persistence.api.entity.role.Role; +import org.apache.syncope.core.persistence.api.entity.task.SyncTask; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.Connector; +import org.apache.syncope.core.provisioning.api.ConnectorFactory; +import org.apache.syncope.core.provisioning.api.cache.VirAttrCache; +import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheValue; +import org.apache.syncope.core.misc.security.Encryptor; +import org.apache.syncope.core.misc.security.UnauthorizedRoleException; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.misc.jexl.JexlUtil; +import org.identityconnectors.common.Base64; +import org.identityconnectors.common.security.GuardedByteArray; +import org.identityconnectors.common.security.GuardedString; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationOptions; +import org.identityconnectors.framework.common.objects.Uid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class ConnObjectUtil { + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(ConnObjectUtil.class); + + @Autowired + private PolicyDAO policyDAO; + + @Autowired + private UserDAO userDAO; + + @Autowired + private RoleDAO roleDAO; + + @Autowired + private ExternalResourceDAO resourceDAO; + + @Autowired + private PlainSchemaDAO plainSchemaDAO; + + @Autowired + private PasswordGenerator pwdGen; + + private final Encryptor encryptor = Encryptor.getInstance(); + + /** + * Virtual attribute cache. + */ + @Autowired + private VirAttrCache virAttrCache; + + public ObjectClass fromSubject(final Subject<?, ?, ?> subject) { + if (subject == null) { + throw new IllegalArgumentException("No ObjectClass could be provided for " + subject); + } + + ObjectClass result = null; + if (subject instanceof User) { + result = ObjectClass.ACCOUNT; + } + if (subject instanceof Role) { + result = ObjectClass.GROUP; + } + + return result; + } + + /** + * Build a UserTO / RoleTO out of connector object attributes and schema mapping. + * + * @param obj connector object + * @param syncTask synchronization task + * @param attrUtil AttributableUtil + * @param <T> user/role + * @return UserTO for the user to be created + */ + @Transactional(readOnly = true) + public <T extends AbstractSubjectTO> T getSubjectTO(final ConnectorObject obj, final SyncTask syncTask, + final AttributableUtil attrUtil) { + + T subjectTO = getSubjectTOFromConnObject(obj, syncTask, attrUtil); + + // (for users) if password was not set above, generate + if (subjectTO instanceof UserTO && StringUtils.isBlank(((UserTO) subjectTO).getPassword())) { + final UserTO userTO = (UserTO) subjectTO; + + List<PasswordPolicySpec> ppSpecs = new ArrayList<>(); + + PasswordPolicy globalPP = policyDAO.getGlobalPasswordPolicy(); + if (globalPP != null && globalPP.getSpecification(PasswordPolicySpec.class) != null) { + ppSpecs.add(globalPP.getSpecification(PasswordPolicySpec.class)); + } + + for (MembershipTO memb : userTO.getMemberships()) { + Role role = roleDAO.find(memb.getRoleId()); + if (role != null && role.getPasswordPolicy() != null + && role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) { + + ppSpecs.add(role.getPasswordPolicy().getSpecification(PasswordPolicySpec.class)); + } + } + + for (String resName : userTO.getResources()) { + ExternalResource resource = resourceDAO.find(resName); + if (resource != null && resource.getPasswordPolicy() != null + && resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class) != null) { + + ppSpecs.add(resource.getPasswordPolicy().getSpecification(PasswordPolicySpec.class)); + } + } + + String password; + try { + password = pwdGen.generate(ppSpecs); + } catch (InvalidPasswordPolicySpecException e) { + LOG.error("Could not generate policy-compliant random password for {}", userTO, e); + + password = SecureRandomUtil.generateRandomPassword(16); + } + userTO.setPassword(password); + } + + return subjectTO; + } + + /** + * Build an UserMod out of connector object attributes and schema mapping. + * + * @param key user to be updated + * @param obj connector object + * @param original subject to get diff from + * @param syncTask synchronization task + * @param attrUtil AttributableUtil + * @param <T> user/role + * @return modifications for the user/role to be updated + * @throws NotFoundException if given id does not correspond to a T instance + * @throws UnauthorizedRoleException if there are no enough entitlements to access the T instance + */ + @SuppressWarnings("unchecked") + @Transactional(readOnly = true) + public <T extends AbstractAttributableMod> T getAttributableMod(final Long key, final ConnectorObject obj, + final AbstractAttributableTO original, final SyncTask syncTask, final AttributableUtil attrUtil) + throws NotFoundException, UnauthorizedRoleException { + + final AbstractAttributableTO updated = getSubjectTOFromConnObject(obj, syncTask, attrUtil); + updated.setKey(key); + + if (AttributableType.USER == attrUtil.getType()) { + // update password if and only if password is really changed + final User user = userDAO.authFetch(key); + if (StringUtils.isBlank(((UserTO) updated).getPassword()) + || encryptor.verify(((UserTO) updated).getPassword(), + user.getCipherAlgorithm(), user.getPassword())) { + + ((UserTO) updated).setPassword(null); + } + + for (MembershipTO membTO : ((UserTO) updated).getMemberships()) { + Membership memb = user.getMembership(membTO.getRoleId()); + if (memb != null) { + membTO.setKey(memb.getKey()); + } + } + + return (T) AttributableOperations.diff(((UserTO) updated), ((UserTO) original), true); + } + if (AttributableType.ROLE == attrUtil.getType()) { + // reading from connector object cannot change entitlements + ((RoleTO) updated).getEntitlements().addAll(((RoleTO) original).getEntitlements()); + return (T) AttributableOperations.diff(((RoleTO) updated), ((RoleTO) original), true); + } + + return null; + } + + private <T extends AbstractSubjectTO> T getSubjectTOFromConnObject(final ConnectorObject obj, + final SyncTask syncTask, final AttributableUtil attrUtil) { + + final T subjectTO = attrUtil.newSubjectTO(); + + // 1. fill with data from connector object + for (MappingItem item : attrUtil.getUidToMappingItems( + syncTask.getResource(), MappingPurpose.SYNCHRONIZATION)) { + + Attribute attribute = obj.getAttributeByName(item.getExtAttrName()); + + AttrTO attributeTO; + switch (item.getIntMappingType()) { + case UserId: + case RoleId: + break; + + case Password: + if (subjectTO instanceof UserTO && attribute != null && attribute.getValue() != null + && !attribute.getValue().isEmpty()) { + + ((UserTO) subjectTO).setPassword(getPassword(attribute.getValue().get(0))); + } + break; + + case Username: + if (subjectTO instanceof UserTO) { + ((UserTO) subjectTO).setUsername(attribute == null || attribute.getValue().isEmpty() + || attribute.getValue().get(0) == null + ? null + : attribute.getValue().get(0).toString()); + } + break; + + case RoleName: + if (subjectTO instanceof RoleTO) { + ((RoleTO) subjectTO).setName(attribute == null || attribute.getValue().isEmpty() + || attribute.getValue().get(0) == null + ? null + : attribute.getValue().get(0).toString()); + } + break; + + case RoleOwnerSchema: + if (subjectTO instanceof RoleTO && attribute != null) { + // using a special attribute (with schema "", that will be ignored) for carrying the + // RoleOwnerSchema value + attributeTO = new AttrTO(); + attributeTO.setSchema(StringUtils.EMPTY); + if (attribute.getValue().isEmpty() || attribute.getValue().get(0) == null) { + attributeTO.getValues().add(StringUtils.EMPTY); + } else { + attributeTO.getValues().add(attribute.getValue().get(0).toString()); + } + + ((RoleTO) subjectTO).getPlainAttrs().add(attributeTO); + } + break; + + case UserPlainSchema: + case RolePlainSchema: + attributeTO = new AttrTO(); + attributeTO.setSchema(item.getIntAttrName()); + + PlainSchema schema = plainSchemaDAO.find(item.getIntAttrName(), attrUtil.plainSchemaClass()); + + for (Object value : attribute == null || attribute.getValue() == null + ? Collections.emptyList() + : attribute.getValue()) { + + AttrSchemaType schemaType = schema == null ? AttrSchemaType.String : schema.getType(); + if (value != null) { + final PlainAttrValue attrValue = attrUtil.newPlainAttrValue(); + switch (schemaType) { + case String: + attrValue.setStringValue(value.toString()); + break; + + case Binary: + attrValue.setBinaryValue((byte[]) value); + break; + + default: + try { + attrValue.parseValue(schema, value.toString()); + } catch (ParsingValidationException e) { + LOG.error("While parsing provided value {}", value, e); + attrValue.setStringValue(value.toString()); + schemaType = AttrSchemaType.String; + } + } + attributeTO.getValues().add(attrValue.getValueAsString(schemaType)); + } + } + + subjectTO.getPlainAttrs().add(attributeTO); + break; + + case UserDerivedSchema: + case RoleDerivedSchema: + attributeTO = new AttrTO(); + attributeTO.setSchema(item.getIntAttrName()); + subjectTO.getDerAttrs().add(attributeTO); + break; + + case UserVirtualSchema: + case RoleVirtualSchema: + attributeTO = new AttrTO(); + attributeTO.setSchema(item.getIntAttrName()); + + for (Object value : attribute == null || attribute.getValue() == null + ? Collections.emptyList() + : attribute.getValue()) { + + if (value != null) { + attributeTO.getValues().add(value.toString()); + } + } + + subjectTO.getVirAttrs().add(attributeTO); + break; + + default: + } + } + + // 2. add data from defined template (if any) + AbstractSubjectTO template = AttributableType.USER == attrUtil.getType() + ? syncTask.getUserTemplate() : syncTask.getRoleTemplate(); + + if (template != null) { + if (template instanceof UserTO) { + if (StringUtils.isNotBlank(((UserTO) template).getUsername())) { + String evaluated = JexlUtil.evaluate(((UserTO) template).getUsername(), subjectTO); + if (StringUtils.isNotBlank(evaluated)) { + ((UserTO) subjectTO).setUsername(evaluated); + } + } + + if (StringUtils.isNotBlank(((UserTO) template).getPassword())) { + String evaluated = JexlUtil.evaluate(((UserTO) template).getPassword(), subjectTO); + if (StringUtils.isNotBlank(evaluated)) { + ((UserTO) subjectTO).setPassword(evaluated); + } + } + + Map<Long, MembershipTO> currentMembs = ((UserTO) subjectTO).getMembershipMap(); + for (MembershipTO membTO : ((UserTO) template).getMemberships()) { + MembershipTO membTBU; + if (currentMembs.containsKey(membTO.getRoleId())) { + membTBU = currentMembs.get(membTO.getRoleId()); + } else { + membTBU = new MembershipTO(); + membTBU.setRoleId(membTO.getRoleId()); + ((UserTO) subjectTO).getMemberships().add(membTBU); + } + fillFromTemplate(membTBU, membTO); + } + } + if (template instanceof RoleTO) { + if (StringUtils.isNotBlank(((RoleTO) template).getName())) { + String evaluated = JexlUtil.evaluate(((RoleTO) template).getName(), subjectTO); + if (StringUtils.isNotBlank(evaluated)) { + ((RoleTO) subjectTO).setName(evaluated); + } + } + + if (((RoleTO) template).getParent() != 0) { + final Role parentRole = roleDAO.find(((RoleTO) template).getParent()); + if (parentRole != null) { + ((RoleTO) subjectTO).setParent(parentRole.getKey()); + } + } + + if (((RoleTO) template).getUserOwner() != null) { + final User userOwner = userDAO.find(((RoleTO) template).getUserOwner()); + if (userOwner != null) { + ((RoleTO) subjectTO).setUserOwner(userOwner.getKey()); + } + } + if (((RoleTO) template).getRoleOwner() != null) { + final Role roleOwner = roleDAO.find(((RoleTO) template).getRoleOwner()); + if (roleOwner != null) { + ((RoleTO) subjectTO).setRoleOwner(roleOwner.getKey()); + } + } + + ((RoleTO) subjectTO).getEntitlements().addAll(((RoleTO) template).getEntitlements()); + + ((RoleTO) subjectTO).getRPlainAttrTemplates().addAll(((RoleTO) template).getRPlainAttrTemplates()); + ((RoleTO) subjectTO).getRDerAttrTemplates().addAll(((RoleTO) template).getRDerAttrTemplates()); + ((RoleTO) subjectTO).getRVirAttrTemplates().addAll(((RoleTO) template).getRVirAttrTemplates()); + ((RoleTO) subjectTO).getMPlainAttrTemplates().addAll(((RoleTO) template).getMPlainAttrTemplates()); + ((RoleTO) subjectTO).getMDerAttrTemplates().addAll(((RoleTO) template).getMDerAttrTemplates()); + ((RoleTO) subjectTO).getMVirAttrTemplates().addAll(((RoleTO) template).getMVirAttrTemplates()); + + ((RoleTO) subjectTO).setAccountPolicy(((RoleTO) template).getAccountPolicy()); + ((RoleTO) subjectTO).setPasswordPolicy(((RoleTO) template).getPasswordPolicy()); + + ((RoleTO) subjectTO).setInheritOwner(((RoleTO) template).isInheritOwner()); + ((RoleTO) subjectTO).setInheritTemplates(((RoleTO) template).isInheritTemplates()); + ((RoleTO) subjectTO).setInheritPlainAttrs(((RoleTO) template).isInheritPlainAttrs()); + ((RoleTO) subjectTO).setInheritDerAttrs(((RoleTO) template).isInheritDerAttrs()); + ((RoleTO) subjectTO).setInheritVirAttrs(((RoleTO) template).isInheritVirAttrs()); + ((RoleTO) subjectTO).setInheritPasswordPolicy(((RoleTO) template).isInheritPasswordPolicy()); + ((RoleTO) subjectTO).setInheritAccountPolicy(((RoleTO) template).isInheritAccountPolicy()); + } + + fillFromTemplate(subjectTO, template); + + for (String resource : template.getResources()) { + subjectTO.getResources().add(resource); + } + } + + return subjectTO; + } + + /** + * Extract password value from passed value (if instance of GuardedString or GuardedByteArray). + * + * @param pwd received from the underlying connector + * @return password value + */ + public String getPassword(final Object pwd) { + final StringBuilder result = new StringBuilder(); + + if (pwd instanceof GuardedString) { + ((GuardedString) pwd).access(new GuardedString.Accessor() { + + @Override + public void access(final char[] clearChars) { + result.append(clearChars); + } + }); + } else if (pwd instanceof GuardedByteArray) { + ((GuardedByteArray) pwd).access(new GuardedByteArray.Accessor() { + + @Override + public void access(final byte[] clearBytes) { + result.append(new String(clearBytes)); + } + }); + } else if (pwd instanceof String) { + result.append((String) pwd); + } else { + result.append(pwd.toString()); + } + + return result.toString(); + } + + /** + * Get connector object TO from a connector object. + * + * @param connObject connector object. + * @return connector object TO. + */ + public ConnObjectTO getConnObjectTO(final ConnectorObject connObject) { + final ConnObjectTO connObjectTO = new ConnObjectTO(); + + for (Attribute attr : connObject.getAttributes()) { + AttrTO attrTO = new AttrTO(); + attrTO.setSchema(attr.getName()); + + if (attr.getValue() != null) { + for (Object value : attr.getValue()) { + if (value != null) { + if (value instanceof GuardedString || value instanceof GuardedByteArray) { + attrTO.getValues().add(getPassword(value)); + } else if (value instanceof byte[]) { + attrTO.getValues().add(Base64.encode((byte[]) value)); + } else { + attrTO.getValues().add(value.toString()); + } + } + } + } + + connObjectTO.getPlainAttrs().add(attrTO); + } + + return connObjectTO; + } + + /** + * Query connected external resources for values to populated virtual attributes associated with the given owner. + * + * @param owner user or role + * @param attrUtil attributable util + */ + public void retrieveVirAttrValues(final Attributable<?, ?, ?> owner, final AttributableUtil attrUtil) { + final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext(); + final ConnectorFactory connFactory = context.getBean(ConnectorFactory.class); + + final IntMappingType type = attrUtil.getType() == AttributableType.USER + ? IntMappingType.UserVirtualSchema : attrUtil.getType() == AttributableType.ROLE + ? IntMappingType.RoleVirtualSchema : IntMappingType.MembershipVirtualSchema; + + final Map<String, ConnectorObject> externalResources = new HashMap<>(); + + // ----------------------- + // Retrieve virtual attribute values if and only if they have not been retrieved yet + // ----------------------- + for (VirAttr virAttr : owner.getVirAttrs()) { + // reset value set + if (virAttr.getValues().isEmpty()) { + retrieveVirAttrValue(owner, virAttr, attrUtil, type, externalResources, connFactory); + } + } + // ----------------------- + } + + private void retrieveVirAttrValue( + final Attributable<?, ?, ?> owner, + final VirAttr virAttr, + final AttributableUtil attrUtil, + final IntMappingType type, + final Map<String, ConnectorObject> externalResources, + final ConnectorFactory connFactory) { + + final String schemaName = virAttr.getSchema().getKey(); + final VirAttrCacheValue virAttrCacheValue = virAttrCache.get(attrUtil.getType(), owner.getKey(), schemaName); + + LOG.debug("Retrieve values for virtual attribute {} ({})", schemaName, type); + + if (virAttrCache.isValidEntry(virAttrCacheValue)) { + // cached ... + LOG.debug("Values found in cache {}", virAttrCacheValue); + virAttr.getValues().clear(); + virAttr.getValues().addAll(new ArrayList<>(virAttrCacheValue.getValues())); + } else { + // not cached ... + LOG.debug("Need one or more remote connections"); + + final VirAttrCacheValue toBeCached = new VirAttrCacheValue(); + + // SYNCOPE-458 if virattr owner is a Membership, owner must become user involved in membership because + // membership mapping is contained in user mapping + final Subject<?, ?, ?> realOwner = owner instanceof Membership + ? ((Membership) owner).getUser() + : (Subject) owner; + + final Set<ExternalResource> targetResources = owner instanceof Membership + ? getTargetResource(virAttr, type, attrUtil, realOwner.getResources()) + : getTargetResource(virAttr, type, attrUtil); + + for (ExternalResource resource : targetResources) { + LOG.debug("Search values into {}", resource.getKey()); + try { + final List<MappingItem> mappings = attrUtil.getMappingItems(resource, MappingPurpose.BOTH); + + final ConnectorObject connectorObject; + + if (externalResources.containsKey(resource.getKey())) { + connectorObject = externalResources.get(resource.getKey()); + } else { + LOG.debug("Perform connection to {}", resource.getKey()); + final String accountId = attrUtil.getAccountIdItem(resource) == null + ? null + : MappingUtil.getAccountIdValue( + realOwner, resource, attrUtil.getAccountIdItem(resource)); + + if (StringUtils.isBlank(accountId)) { + throw new IllegalArgumentException("No AccountId found for " + resource.getKey()); + } + + final Connector connector = connFactory.getConnector(resource); + + final OperationOptions oo = + connector.getOperationOptions(MappingUtil.getMatchingMappingItems(mappings, type)); + + connectorObject = connector.getObject(fromSubject(realOwner), new Uid(accountId), oo); + externalResources.put(resource.getKey(), connectorObject); + } + + if (connectorObject != null) { + // ask for searched virtual attribute value + final List<MappingItem> virAttrMappings = + MappingUtil.getMatchingMappingItems(mappings, schemaName, type); + + // the same virtual attribute could be mapped with one or more external attribute + for (MappingItem mapping : virAttrMappings) { + final Attribute attribute = connectorObject.getAttributeByName(mapping.getExtAttrName()); + + if (attribute != null && attribute.getValue() != null) { + for (Object obj : attribute.getValue()) { + if (obj != null) { + virAttr.getValues().add(obj.toString()); + } + } + } + } + + toBeCached.setResourceValues(resource.getKey(), new HashSet<String>(virAttr.getValues())); + + LOG.debug("Retrieved values {}", virAttr.getValues()); + } + } catch (Exception e) { + LOG.error("Error reading connector object from {}", resource.getKey(), e); + + if (virAttrCacheValue != null) { + toBeCached.forceExpiring(); + LOG.debug("Search for a cached value (even expired!) ..."); + final Set<String> cachedValues = virAttrCacheValue.getValues(resource.getKey()); + if (cachedValues != null) { + LOG.debug("Use cached value {}", cachedValues); + virAttr.getValues().addAll(cachedValues); + toBeCached.setResourceValues(resource.getKey(), new HashSet<>(cachedValues)); + } + } + } + } + + virAttrCache.put(attrUtil.getType(), owner.getKey(), schemaName, toBeCached); + } + } + + private Set<ExternalResource> getTargetResource( + final VirAttr attr, final IntMappingType type, final AttributableUtil attrUtil) { + + final Set<ExternalResource> resources = new HashSet<>(); + + if (attr.getOwner() instanceof Subject) { + for (ExternalResource res : ((Subject<?, ?, ?>) attr.getOwner()).getResources()) { + if (!MappingUtil.getMatchingMappingItems( + attrUtil.getMappingItems(res, MappingPurpose.BOTH), + attr.getSchema().getKey(), type).isEmpty()) { + + resources.add(res); + } + } + } + + return resources; + } + + private Set<ExternalResource> getTargetResource(final VirAttr attr, final IntMappingType type, + final AttributableUtil attrUtil, final Set<? extends ExternalResource> ownerResources) { + + final Set<ExternalResource> resources = new HashSet<>(); + + for (ExternalResource res : ownerResources) { + if (!MappingUtil.getMatchingMappingItems( + attrUtil.getMappingItems(res, MappingPurpose.BOTH), + attr.getSchema().getKey(), type).isEmpty()) { + + resources.add(res); + } + } + + return resources; + } + + private void fillFromTemplate(final AbstractAttributableTO attributableTO, final AbstractAttributableTO template) { + Map<String, AttrTO> currentAttrMap = attributableTO.getPlainAttrMap(); + for (AttrTO templateAttr : template.getPlainAttrs()) { + if (templateAttr.getValues() != null && !templateAttr.getValues().isEmpty() + && (!currentAttrMap.containsKey(templateAttr.getSchema()) + || currentAttrMap.get(templateAttr.getSchema()).getValues().isEmpty())) { + + attributableTO.getPlainAttrs().add(evaluateAttrTemplate(attributableTO, templateAttr)); + } + } + + currentAttrMap = attributableTO.getDerAttrMap(); + for (AttrTO templateDerAttr : template.getDerAttrs()) { + if (!currentAttrMap.containsKey(templateDerAttr.getSchema())) { + attributableTO.getDerAttrs().add(templateDerAttr); + } + } + + currentAttrMap = attributableTO.getVirAttrMap(); + for (AttrTO templateVirAttr : template.getVirAttrs()) { + if (templateVirAttr.getValues() != null && !templateVirAttr.getValues().isEmpty() + && (!currentAttrMap.containsKey(templateVirAttr.getSchema()) + || currentAttrMap.get(templateVirAttr.getSchema()).getValues().isEmpty())) { + + attributableTO.getVirAttrs().add(evaluateAttrTemplate(attributableTO, templateVirAttr)); + } + } + } + + private AttrTO evaluateAttrTemplate(final AbstractAttributableTO attributableTO, final AttrTO template) { + AttrTO result = new AttrTO(); + result.setSchema(template.getSchema()); + + if (template.getValues() != null && !template.getValues().isEmpty()) { + for (String value : template.getValues()) { + String evaluated = JexlUtil.evaluate(value, attributableTO); + if (StringUtils.isNotBlank(evaluated)) { + result.getValues().add(evaluated); + } + } + } + + return result; + } + + /** + * Transform a + * <code>Collection</code> of {@link Attribute} instances into a {@link Map}. The key to each element in the map is + * the <i>name</i> of an + * <code>Attribute</code>. The value of each element in the map is the + * <code>Attribute</code> instance with that name. <br/> Different from the original because: <ul> <li>map keys are + * transformed toUpperCase()</li> <li>returned map is mutable</li> </ul> + * + * @param attributes set of attribute to transform to a map. + * @return a map of string and attribute. + * + * @see org.identityconnectors.framework.common.objects.AttributeUtil#toMap(java.util.Collection) + */ + public Map<String, Attribute> toMap(final Collection<? extends Attribute> attributes) { + final Map<String, Attribute> map = new HashMap<>(); + for (Attribute attr : attributes) { + map.put(attr.getName().toUpperCase(), attr); + } + return map; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/DataFormat.java ---------------------------------------------------------------------- diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/DataFormat.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/DataFormat.java new file mode 100644 index 0000000..fded378 --- /dev/null +++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/DataFormat.java @@ -0,0 +1,117 @@ +/* + * 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.misc; + +import java.text.DecimalFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.syncope.common.lib.SyncopeConstants; + +/** + * Utility class for parsing / formatting date and numbers. + */ +public final class DataFormat { + + private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() { + + @Override + protected SimpleDateFormat initialValue() { + SimpleDateFormat sdf = new SimpleDateFormat(); + sdf.applyPattern(SyncopeConstants.DEFAULT_DATE_PATTERN); + return sdf; + } + }; + + private static final ThreadLocal<DecimalFormat> DECIMAL_FORMAT = new ThreadLocal<DecimalFormat>() { + + @Override + protected DecimalFormat initialValue() { + return new DecimalFormat(); + } + }; + + public static String format(final Date date) { + return format(date, true); + } + + public static String format(final Date date, final boolean lenient) { + return format(date, lenient, null); + } + + public static String format(final Date date, final boolean lenient, final String conversionPattern) { + SimpleDateFormat sdf = DATE_FORMAT.get(); + if (conversionPattern != null) { + sdf.applyPattern(conversionPattern); + } + sdf.setLenient(lenient); + return sdf.format(date); + } + + public static String format(final long number) { + return format(number, null); + } + + public static String format(final long number, final String conversionPattern) { + DecimalFormat df = DECIMAL_FORMAT.get(); + if (conversionPattern != null) { + df.applyPattern(conversionPattern); + } + return df.format(number); + } + + public static String format(final double number) { + return format(number, null); + } + + public static String format(final double number, final String conversionPattern) { + DecimalFormat df = DECIMAL_FORMAT.get(); + if (conversionPattern != null) { + df.applyPattern(conversionPattern); + } + return df.format(number); + } + + public static Date parseDate(final String source) throws ParseException { + return DateUtils.parseDate(source, SyncopeConstants.DATE_PATTERNS); + } + + public static Date parseDate(final String source, final String conversionPattern) throws ParseException { + SimpleDateFormat sdf = DATE_FORMAT.get(); + sdf.applyPattern(conversionPattern); + sdf.setLenient(false); + return sdf.parse(source); + } + + public static Number parseNumber(final String source, final String conversionPattern) throws ParseException { + DecimalFormat df = DECIMAL_FORMAT.get(); + df.applyPattern(conversionPattern); + return df.parse(source); + } + + public static void clear() { + DATE_FORMAT.remove(); + DECIMAL_FORMAT.remove(); + } + + private DataFormat() { + // private empty constructor + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/ExceptionUtil.java ---------------------------------------------------------------------- diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/ExceptionUtil.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/ExceptionUtil.java new file mode 100644 index 0000000..8dcebec --- /dev/null +++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/ExceptionUtil.java @@ -0,0 +1,47 @@ +/* + * 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.misc; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +public final class ExceptionUtil { + + /** + * Uses commons lang's ExceptionUtils to provide a representation of the full stack trace of the given throwable. + * + * @param t throwable to build stack trace from + * @return a string representation of full stack trace of the given throwable + */ + public static String getFullStackTrace(final Throwable t) { + StringBuilder result = new StringBuilder(); + + for (Throwable throwable : ExceptionUtils.getThrowableList(t)) { + result.append(ExceptionUtils.getMessage(throwable)).append('\n'). + append(ExceptionUtils.getStackTrace(throwable)).append("\n\n"); + } + + return result.toString(); + } + + /** + * Private default constructor, for static-only classes. + */ + private ExceptionUtil() { + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtil.java ---------------------------------------------------------------------- diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtil.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtil.java new file mode 100644 index 0000000..267cfa0 --- /dev/null +++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/MappingUtil.java @@ -0,0 +1,738 @@ +/* + * 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.misc; + +import org.apache.syncope.core.misc.policy.InvalidPasswordPolicySpecException; +import org.apache.syncope.core.misc.security.PasswordGenerator; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.jexl2.JexlContext; +import org.apache.commons.jexl2.MapContext; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.mod.AttrMod; +import org.apache.syncope.common.lib.types.AttrSchemaType; +import org.apache.syncope.common.lib.types.AttributableType; +import org.apache.syncope.common.lib.types.IntMappingType; +import org.apache.syncope.common.lib.types.MappingPurpose; +import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO; +import org.apache.syncope.core.persistence.api.entity.Attributable; +import org.apache.syncope.core.persistence.api.entity.AttributableUtil; +import org.apache.syncope.core.persistence.api.entity.AttributableUtilFactory; +import org.apache.syncope.core.persistence.api.entity.DerAttr; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.MappingItem; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.PlainAttrValue; +import org.apache.syncope.core.persistence.api.entity.PlainSchema; +import org.apache.syncope.core.persistence.api.entity.Subject; +import org.apache.syncope.core.persistence.api.entity.VirAttr; +import org.apache.syncope.core.persistence.api.entity.VirSchema; +import org.apache.syncope.core.persistence.api.entity.membership.MDerSchema; +import org.apache.syncope.core.persistence.api.entity.membership.MPlainSchema; +import org.apache.syncope.core.persistence.api.entity.membership.MVirSchema; +import org.apache.syncope.core.persistence.api.entity.membership.Membership; +import org.apache.syncope.core.persistence.api.entity.role.RDerSchema; +import org.apache.syncope.core.persistence.api.entity.role.RPlainAttrValue; +import org.apache.syncope.core.persistence.api.entity.role.RPlainSchema; +import org.apache.syncope.core.persistence.api.entity.role.RVirSchema; +import org.apache.syncope.core.persistence.api.entity.role.Role; +import org.apache.syncope.core.persistence.api.entity.user.UDerSchema; +import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrValue; +import org.apache.syncope.core.persistence.api.entity.user.UPlainSchema; +import org.apache.syncope.core.persistence.api.entity.user.UVirSchema; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.cache.VirAttrCache; +import org.apache.syncope.core.misc.security.Encryptor; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.misc.jexl.JexlUtil; +import org.identityconnectors.framework.common.FrameworkUtil; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeBuilder; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.Name; +import org.identityconnectors.framework.common.objects.OperationalAttributes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ConfigurableApplicationContext; + +public final class MappingUtil { + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(MappingUtil.class); + + private static final Encryptor ENCRYPTOR = Encryptor.getInstance(); + + public static <T extends MappingItem> List<T> getMatchingMappingItems( + final Collection<T> items, final IntMappingType type) { + + final List<T> result = new ArrayList<>(); + + for (T mapItem : items) { + if (mapItem.getIntMappingType() == type) { + result.add(mapItem); + } + } + + return result; + } + + public static <T extends MappingItem> List<T> getMatchingMappingItems(final Collection<T> items, + final String intAttrName, final IntMappingType type) { + + final List<T> result = new ArrayList<>(); + + for (T mapItem : items) { + if (mapItem.getIntMappingType() == type && intAttrName.equals(mapItem.getIntAttrName())) { + result.add(mapItem); + } + } + + return result; + } + + public static <T extends MappingItem> Set<T> getMatchingMappingItems(final Collection<T> mapItems, + final String intAttrName) { + + final Set<T> result = new HashSet<>(); + + for (T mapItem : mapItems) { + if (intAttrName.equals(mapItem.getIntAttrName())) { + result.add(mapItem); + } + } + + return result; + } + + /** + * Prepare attributes for sending to a connector instance. + * + * @param attrUtil user / role + * @param subject given user / role + * @param password clear-text password + * @param changePwd whether password should be included for propagation attributes or not + * @param vAttrsToBeRemoved virtual attributes to be removed + * @param vAttrsToBeUpdated virtual attributes to be added + * @param membVAttrsToBeRemoved membership virtual attributes to be removed + * @param membVAttrsToBeUpdated membership virtual attributes to be added + * @param enable whether user must be enabled or not + * @param resource target resource + * @return account link + prepared attributes + */ + public static Map.Entry<String, Set<Attribute>> prepareAttributes( + final AttributableUtil attrUtil, final Subject<?, ?, ?> subject, + final String password, + final boolean changePwd, + final Set<String> vAttrsToBeRemoved, + final Map<String, AttrMod> vAttrsToBeUpdated, + final Set<String> membVAttrsToBeRemoved, + final Map<String, AttrMod> membVAttrsToBeUpdated, + final Boolean enable, + final ExternalResource resource) { + + LOG.debug("Preparing resource attributes for {} on resource {} with attributes {}", + subject, resource, subject.getPlainAttrs()); + + final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext(); + final VirAttrCache virAttrCache = context.getBean(VirAttrCache.class); + final PasswordGenerator passwordGenerator = context.getBean(PasswordGenerator.class); + + Set<Attribute> attributes = new HashSet<>(); + String accountId = null; + + for (MappingItem mapping : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) { + LOG.debug("Processing schema {}", mapping.getIntAttrName()); + + try { + if ((attrUtil.getType() == AttributableType.USER + && mapping.getIntMappingType() == IntMappingType.UserVirtualSchema) + || (attrUtil.getType() == AttributableType.ROLE + && mapping.getIntMappingType() == IntMappingType.RoleVirtualSchema)) { + + LOG.debug("Expire entry cache {}-{}", subject.getKey(), mapping.getIntAttrName()); + virAttrCache.expire(attrUtil.getType(), subject.getKey(), mapping.getIntAttrName()); + } + + // SYNCOPE-458 expire cache also for membership virtual schemas + if (attrUtil.getType() == AttributableType.USER && mapping.getIntMappingType() + == IntMappingType.MembershipVirtualSchema && (subject instanceof User)) { + + final User user = (User) subject; + for (Membership membership : user.getMemberships()) { + LOG.debug("Expire entry cache {}-{} for membership {}", subject.getKey(), + mapping.getIntAttrName(), membership); + virAttrCache.expire(AttributableType.MEMBERSHIP, membership.getKey(), + mapping.getIntAttrName()); + } + } + + Map.Entry<String, Attribute> preparedAttr = prepareAttr( + resource, mapping, subject, password, passwordGenerator, vAttrsToBeRemoved, vAttrsToBeUpdated, + membVAttrsToBeRemoved, membVAttrsToBeUpdated); + + if (preparedAttr != null && preparedAttr.getKey() != null) { + accountId = preparedAttr.getKey(); + } + + if (preparedAttr != null && preparedAttr.getValue() != null) { + Attribute alreadyAdded = AttributeUtil.find(preparedAttr.getValue().getName(), attributes); + + if (alreadyAdded == null) { + attributes.add(preparedAttr.getValue()); + } else { + attributes.remove(alreadyAdded); + + Set<Object> values = new HashSet<>(alreadyAdded.getValue()); + values.addAll(preparedAttr.getValue().getValue()); + + attributes.add(AttributeBuilder.build(preparedAttr.getValue().getName(), values)); + } + } + } catch (Exception e) { + LOG.debug("Attribute '{}' processing failed", mapping.getIntAttrName(), e); + } + } + + final Attribute accountIdExtAttr = + AttributeUtil.find(attrUtil.getAccountIdItem(resource).getExtAttrName(), attributes); + if (accountIdExtAttr != null) { + attributes.remove(accountIdExtAttr); + attributes.add(AttributeBuilder.build(attrUtil.getAccountIdItem(resource).getExtAttrName(), accountId)); + } + attributes.add(MappingUtil.evaluateNAME(subject, resource, accountId)); + + if (enable != null) { + attributes.add(AttributeBuilder.buildEnabled(enable)); + } + if (!changePwd) { + Attribute pwdAttr = AttributeUtil.find(OperationalAttributes.PASSWORD_NAME, attributes); + if (pwdAttr != null) { + attributes.remove(pwdAttr); + } + } + + return new AbstractMap.SimpleEntry<>(accountId, attributes); + } + + /** + * Prepare an attribute to be sent to a connector instance. + * + * @param resource target resource + * @param mapItem mapping item for the given attribute + * @param subject given user + * @param password clear-text password + * @param passwordGenerator password generator + * @param vAttrsToBeRemoved virtual attributes to be removed + * @param vAttrsToBeUpdated virtual attributes to be added + * @return account link + prepared attribute + */ + @SuppressWarnings("unchecked") + private static Map.Entry<String, Attribute> prepareAttr( + final ExternalResource resource, final MappingItem mapItem, + final Subject<?, ?, ?> subject, final String password, final PasswordGenerator passwordGenerator, + final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated, + final Set<String> membVAttrsToBeRemoved, final Map<String, AttrMod> membVAttrsToBeUpdated) { + + final List<Attributable<?, ?, ?>> attributables = new ArrayList<>(); + + final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext(); + final AttributableUtilFactory attrUtilFactory = context.getBean(AttributableUtilFactory.class); + final ConnObjectUtil connObjectUtil = context.getBean(ConnObjectUtil.class); + + switch (mapItem.getIntMappingType().getAttributableType()) { + case USER: + if (subject instanceof User) { + attributables.add(subject); + } + break; + + case ROLE: + if (subject instanceof User) { + for (Role role : ((User) subject).getRoles()) { + connObjectUtil.retrieveVirAttrValues(role, attrUtilFactory.getInstance(role)); + attributables.add(role); + } + } + if (subject instanceof Role) { + attributables.add(subject); + } + break; + + case MEMBERSHIP: + if (subject instanceof User) { + attributables.addAll(((User) subject).getMemberships()); + } + break; + + default: + } + + List<PlainAttrValue> values = getIntValues( + resource, mapItem, attributables, vAttrsToBeRemoved, vAttrsToBeUpdated, membVAttrsToBeRemoved, + membVAttrsToBeUpdated); + + PlainSchema schema = null; + boolean readOnlyVirSchema = false; + AttrSchemaType schemaType; + final Map.Entry<String, Attribute> result; + + switch (mapItem.getIntMappingType()) { + case UserPlainSchema: + case RolePlainSchema: + case MembershipPlainSchema: + final PlainSchemaDAO plainSchemaDAO = context.getBean(PlainSchemaDAO.class); + schema = plainSchemaDAO.find(mapItem.getIntAttrName(), + MappingUtil.getIntMappingTypeClass(mapItem.getIntMappingType())); + schemaType = schema == null ? AttrSchemaType.String : schema.getType(); + break; + + case UserVirtualSchema: + case RoleVirtualSchema: + case MembershipVirtualSchema: + VirSchemaDAO virSchemaDAO = context.getBean(VirSchemaDAO.class); + VirSchema virSchema = virSchemaDAO.find(mapItem.getIntAttrName(), + MappingUtil.getIntMappingTypeClass(mapItem.getIntMappingType())); + readOnlyVirSchema = (virSchema != null && virSchema.isReadonly()); + schemaType = AttrSchemaType.String; + break; + + default: + schemaType = AttrSchemaType.String; + } + + final String extAttrName = mapItem.getExtAttrName(); + + LOG.debug("Define mapping for: " + + "\n* ExtAttrName " + extAttrName + + "\n* is accountId " + mapItem.isAccountid() + + "\n* is password " + (mapItem.isPassword() || mapItem.getIntMappingType() == IntMappingType.Password) + + "\n* mandatory condition " + mapItem.getMandatoryCondition() + + "\n* Schema " + mapItem.getIntAttrName() + + "\n* IntMappingType " + mapItem.getIntMappingType().toString() + + "\n* ClassType " + schemaType.getType().getName() + + "\n* Values " + values); + + if (readOnlyVirSchema) { + result = null; + } else { + final List<Object> objValues = new ArrayList<>(); + + for (PlainAttrValue value : values) { + if (FrameworkUtil.isSupportedAttributeType(schemaType.getType())) { + objValues.add(value.getValue()); + } else { + objValues.add(value.getValueAsString()); + } + } + + if (mapItem.isAccountid()) { + result = new AbstractMap.SimpleEntry<>(objValues.iterator().next().toString(), null); + } else if (mapItem.isPassword() && subject instanceof User) { + String passwordAttrValue = password; + if (StringUtils.isBlank(passwordAttrValue)) { + User user = (User) subject; + if (user.canDecodePassword()) { + try { + passwordAttrValue = ENCRYPTOR.decode(user.getPassword(), user.getCipherAlgorithm()); + } catch (Exception e) { + LOG.error("Could not decode password for {}", user, e); + } + } else if (resource.isRandomPwdIfNotProvided()) { + try { + passwordAttrValue = passwordGenerator.generate(user); + } catch (InvalidPasswordPolicySpecException e) { + LOG.error("Could not generate policy-compliant random password for {}", user, e); + } + } + } + + if (passwordAttrValue == null) { + result = null; + } else { + result = new AbstractMap.SimpleEntry<>( + null, + AttributeBuilder.buildPassword(passwordAttrValue.toCharArray())); + } + } else { + if ((schema != null && schema.isMultivalue()) || attrUtilFactory.getInstance(subject).getType() + != mapItem.getIntMappingType().getAttributableType()) { + + result = new AbstractMap.SimpleEntry<>( + null, + AttributeBuilder.build(extAttrName, objValues)); + } else { + result = new AbstractMap.SimpleEntry<>( + null, objValues.isEmpty() + ? AttributeBuilder.build(extAttrName) + : AttributeBuilder.build(extAttrName, objValues.iterator().next())); + } + } + } + + return result; + } + + /** + * Build __NAME__ for propagation. First look if there ia a defined accountLink for the given resource (and in this + * case evaluate as JEXL); otherwise, take given accountId. + * + * @param subject given user / role + * @param resource target resource + * @param accountId accountId + * @return the value to be propagated as __NAME__ + */ + public static Name evaluateNAME(final Subject<?, ?, ?> subject, + final ExternalResource resource, final String accountId) { + + final AttributableUtilFactory attrUtilFactory = + ApplicationContextProvider.getApplicationContext().getBean(AttributableUtilFactory.class); + final AttributableUtil attrUtil = attrUtilFactory.getInstance(subject); + + if (StringUtils.isBlank(accountId)) { + // LOG error but avoid to throw exception: leave it to the external resource + LOG.error("Missing accountId for '{}': ", resource.getKey()); + } + + // Evaluate AccountLink expression + String evalAccountLink = null; + if (StringUtils.isNotBlank(attrUtil.getAccountLink(resource))) { + final JexlContext jexlContext = new MapContext(); + JexlUtil.addFieldsToContext(subject, jexlContext); + JexlUtil.addAttrsToContext(subject.getPlainAttrs(), jexlContext); + JexlUtil.addDerAttrsToContext(subject.getDerAttrs(), subject.getPlainAttrs(), jexlContext); + evalAccountLink = JexlUtil.evaluate(attrUtil.getAccountLink(resource), jexlContext); + } + + // If AccountLink evaluates to an empty string, just use the provided AccountId as Name(), + // otherwise evaluated AccountLink expression is taken as Name(). + Name name; + if (StringUtils.isBlank(evalAccountLink)) { + // add AccountId as __NAME__ attribute ... + LOG.debug("Add AccountId [{}] as __NAME__", accountId); + name = new Name(accountId); + } else { + LOG.debug("Add AccountLink [{}] as __NAME__", evalAccountLink); + name = new Name(evalAccountLink); + + // AccountId not propagated: it will be used to set the value for __UID__ attribute + LOG.debug("AccountId will be used just as __UID__ attribute"); + } + + return name; + } + + private static String getRoleOwnerValue( + final ExternalResource resource, final Subject<?, ?, ?> subject) { + + AttributableUtilFactory attrUtilFactory = + ApplicationContextProvider.getApplicationContext().getBean(AttributableUtilFactory.class); + + Map.Entry<String, Attribute> preparedAttr = prepareAttr( + resource, attrUtilFactory.getInstance(subject).getAccountIdItem(resource), subject, null, null, + Collections.<String>emptySet(), Collections.<String, AttrMod>emptyMap(), + Collections.<String>emptySet(), Collections.<String, AttrMod>emptyMap()); + String accountId = preparedAttr.getKey(); + + final Name roleOwnerName = evaluateNAME(subject, resource, accountId); + return roleOwnerName.getNameValue(); + } + + /** + * Get attribute values. + * + * @param resource target resource + * @param mappingItem mapping item + * @param attributables list of attributables + * @param vAttrsToBeRemoved virtual attributes to be removed + * @param vAttrsToBeUpdated virtual attributes to be added + * @param membVAttrsToBeRemoved membership virtual attributes to be removed + * @param membVAttrsToBeUpdated membership virtual attributes to be added + * @return attribute values. + */ + public static List<PlainAttrValue> getIntValues(final ExternalResource resource, + final MappingItem mappingItem, final List<Attributable<?, ?, ?>> attributables, + final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated, + final Set<String> membVAttrsToBeRemoved, final Map<String, AttrMod> membVAttrsToBeUpdated) { + + LOG.debug("Get attributes for '{}' and mapping type '{}'", attributables, mappingItem.getIntMappingType()); + + final EntityFactory entityFactory = + ApplicationContextProvider.getApplicationContext().getBean(EntityFactory.class); + List<PlainAttrValue> values = new ArrayList<>(); + PlainAttrValue attrValue; + switch (mappingItem.getIntMappingType()) { + case UserPlainSchema: + case RolePlainSchema: + case MembershipPlainSchema: + for (Attributable<?, ?, ?> attributable : attributables) { + final PlainAttr attr = attributable.getPlainAttr(mappingItem.getIntAttrName()); + if (attr != null) { + if (attr.getUniqueValue() != null) { + values.add(attr.getUniqueValue()); + } else if (attr.getValues() != null) { + values.addAll(attr.getValues()); + } + } + + LOG.debug("Retrieved attribute {}" + + "\n* IntAttrName {}" + + "\n* IntMappingType {}" + + "\n* Attribute values {}", + attr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values); + } + + break; + + case UserVirtualSchema: + case RoleVirtualSchema: + for (Attributable<?, ?, ?> attributable : attributables) { + VirAttr virAttr = attributable.getVirAttr(mappingItem.getIntAttrName()); + if (virAttr != null) { + if (vAttrsToBeRemoved != null && vAttrsToBeUpdated != null) { + if (vAttrsToBeUpdated.containsKey(mappingItem.getIntAttrName())) { + virAttr.getValues().clear(); + virAttr.getValues().addAll( + vAttrsToBeUpdated.get(mappingItem.getIntAttrName()).getValuesToBeAdded()); + } else if (vAttrsToBeRemoved.contains(mappingItem.getIntAttrName())) { + virAttr.getValues().clear(); + } else { + throw new IllegalArgumentException("Don't need to update virtual attribute '" + + mappingItem.getIntAttrName() + "'"); + } + } + if (virAttr.getValues() != null) { + for (String value : virAttr.getValues()) { + attrValue = entityFactory.newEntity(UPlainAttrValue.class); + attrValue.setStringValue(value); + values.add(attrValue); + } + } + } + + LOG.debug("Retrieved {} virtual attribute {}" + + "\n* IntAttrName {}" + + "\n* IntMappingType {}" + + "\n* Attribute values {}", + attributable.getClass().getSimpleName(), + virAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values); + } + break; + + case MembershipVirtualSchema: + for (Attributable<?, ?, ?> attributable : attributables) { + VirAttr virAttr = attributable.getVirAttr(mappingItem.getIntAttrName()); + if (virAttr != null) { + if (membVAttrsToBeRemoved != null && membVAttrsToBeUpdated != null) { + if (membVAttrsToBeUpdated.containsKey(mappingItem.getIntAttrName())) { + virAttr.getValues().clear(); + virAttr.getValues().addAll( + membVAttrsToBeUpdated.get(mappingItem.getIntAttrName()).getValuesToBeAdded()); + } else if (membVAttrsToBeRemoved.contains(mappingItem.getIntAttrName())) { + virAttr.getValues().clear(); + } else { + throw new IllegalArgumentException("Don't need to update membership virtual attribute '" + + mappingItem.getIntAttrName() + "'"); + } + } + if (virAttr.getValues() != null) { + for (String value : virAttr.getValues()) { + attrValue = entityFactory.newEntity(UPlainAttrValue.class); + attrValue.setStringValue(value); + values.add(attrValue); + } + } + } + + LOG.debug("Retrieved {} virtual attribute {}" + + "\n* IntAttrName {}" + + "\n* IntMappingType {}" + + "\n* Attribute values {}", + attributable.getClass().getSimpleName(), + virAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values); + } + break; + + case UserDerivedSchema: + case RoleDerivedSchema: + case MembershipDerivedSchema: + for (Attributable<?, ?, ?> attributable : attributables) { + DerAttr derAttr = attributable.getDerAttr(mappingItem.getIntAttrName()); + if (derAttr != null) { + attrValue = attributable instanceof Role + ? entityFactory.newEntity(RPlainAttrValue.class) + : entityFactory.newEntity(UPlainAttrValue.class); + attrValue.setStringValue(derAttr.getValue(attributable.getPlainAttrs())); + values.add(attrValue); + } + + LOG.debug("Retrieved attribute {}" + + "\n* IntAttrName {}" + + "\n* IntMappingType {}" + + "\n* Attribute values {}", + derAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values); + } + break; + + case UserId: + case RoleId: + case MembershipId: + for (Attributable<?, ?, ?> attributable : attributables) { + attrValue = entityFactory.newEntity(UPlainAttrValue.class); + attrValue.setStringValue(attributable.getKey().toString()); + values.add(attrValue); + } + break; + + case Username: + for (Attributable<?, ?, ?> attributable : attributables) { + if (attributable instanceof User) { + attrValue = entityFactory.newEntity(UPlainAttrValue.class); + attrValue.setStringValue(((User) attributable).getUsername()); + values.add(attrValue); + } + } + break; + + case RoleName: + for (Attributable<?, ?, ?> attributable : attributables) { + if (attributable instanceof Role) { + attrValue = entityFactory.newEntity(RPlainAttrValue.class); + attrValue.setStringValue(((Role) attributable).getName()); + values.add(attrValue); + } + } + break; + + case RoleOwnerSchema: + for (Attributable<?, ?, ?> attributable : attributables) { + if (attributable instanceof Role) { + Role role = (Role) attributable; + String roleOwnerValue = null; + if (role.getUserOwner() != null && resource.getUmapping() != null) { + roleOwnerValue = getRoleOwnerValue(resource, role.getUserOwner()); + } + if (role.getRoleOwner() != null && resource.getRmapping() != null) { + roleOwnerValue = getRoleOwnerValue(resource, role.getRoleOwner()); + } + + if (StringUtils.isNotBlank(roleOwnerValue)) { + attrValue = entityFactory.newEntity(RPlainAttrValue.class); + attrValue.setStringValue(roleOwnerValue); + values.add(attrValue); + } + } + } + break; + + default: + } + + LOG.debug("Retrieved values '{}'", values); + + return values; + } + + /** + * Get accountId internal value. + * + * @param attributable attributable + * @param accountIdItem accountId mapping item + * @param resource external resource + * @return accountId internal value + */ + public static String getAccountIdValue(final Attributable<?, ?, ?> attributable, + final ExternalResource resource, final MappingItem accountIdItem) { + + List<PlainAttrValue> values = getIntValues(resource, accountIdItem, + Collections.<Attributable<?, ?, ?>>singletonList(attributable), null, null, null, null); + return values == null || values.isEmpty() + ? null + : values.get(0).getValueAsString(); + } + + /** + * For given source mapping type, return the corresponding Class object. + * + * @param intMappingType source mapping type + * @return corresponding Class object, if any (can be null) + */ + @SuppressWarnings("rawtypes") + public static Class getIntMappingTypeClass(final IntMappingType intMappingType) { + Class result; + + switch (intMappingType) { + case UserPlainSchema: + result = UPlainSchema.class; + break; + + case RolePlainSchema: + result = RPlainSchema.class; + break; + + case MembershipPlainSchema: + result = MPlainSchema.class; + break; + + case UserDerivedSchema: + result = UDerSchema.class; + break; + + case RoleDerivedSchema: + result = RDerSchema.class; + break; + + case MembershipDerivedSchema: + result = MDerSchema.class; + break; + + case UserVirtualSchema: + result = UVirSchema.class; + break; + + case RoleVirtualSchema: + result = RVirSchema.class; + break; + + case MembershipVirtualSchema: + result = MVirSchema.class; + break; + + default: + result = null; + } + + return result; + } + + /** + * Private default constructor, for static-only classes. + */ + private MappingUtil() { + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/ClassFreeUberspectImpl.java ---------------------------------------------------------------------- diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/ClassFreeUberspectImpl.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/ClassFreeUberspectImpl.java new file mode 100644 index 0000000..662cb15 --- /dev/null +++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/ClassFreeUberspectImpl.java @@ -0,0 +1,43 @@ +/* + * 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.misc.jexl; + +import org.apache.commons.jexl2.JexlInfo; +import org.apache.commons.jexl2.introspection.JexlMethod; +import org.apache.commons.jexl2.introspection.JexlPropertyGet; +import org.apache.commons.jexl2.introspection.UberspectImpl; +import org.apache.commons.logging.Log; + +class ClassFreeUberspectImpl extends UberspectImpl { + + public ClassFreeUberspectImpl(final Log runtimeLogger) { + super(runtimeLogger); + } + + @Override + public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier, final JexlInfo info) { + return "class".equals(identifier) ? null : super.getPropertyGet(obj, identifier, info); + } + + @Override + public JexlMethod getMethod(final Object obj, final String method, final Object[] args, final JexlInfo info) { + return "getClass".equals(method) ? null : super.getMethod(obj, method, args, info); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/EmptyClassLoader.java ---------------------------------------------------------------------- diff --git a/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/EmptyClassLoader.java b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/EmptyClassLoader.java new file mode 100644 index 0000000..6b21915 --- /dev/null +++ b/syncope620/core/misc/src/main/java/org/apache/syncope/core/misc/jexl/EmptyClassLoader.java @@ -0,0 +1,36 @@ +/* + * 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.misc.jexl; + +/** + * A class loader that will throw {@link ClassNotFoundException} for every class name. + */ +class EmptyClassLoader extends ClassLoader { + + @Override + public Class<?> loadClass(final String name) throws ClassNotFoundException { + throw new ClassNotFoundException("This classloader won't attemp to load " + name); + } + + @Override + protected Class<?> loadClass(final String name, boolean resolve) throws ClassNotFoundException { + throw new ClassNotFoundException("This classloader won't attemp to load " + name); + } + +}
