/*
* Copyright (c) 2010-2017 Evolveum
*
* Licensed 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 com.evolveum.midpoint.provisioning.ucf.impl.connid;
import static com.evolveum.midpoint.provisioning.ucf.impl.connid.ConnIdUtil.processIcfException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Key;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.annotation.PostConstruct;
import javax.net.ssl.TrustManager;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.prism.schema.PrismSchemaImpl;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.StringUtils;
import org.identityconnectors.common.Version;
import org.identityconnectors.common.security.Encryptor;
import org.identityconnectors.common.security.EncryptorFactory;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.api.APIConfiguration;
import org.identityconnectors.framework.api.ConfigurationProperties;
import org.identityconnectors.framework.api.ConfigurationProperty;
import org.identityconnectors.framework.api.ConnectorFacadeFactory;
import org.identityconnectors.framework.api.ConnectorInfo;
import org.identityconnectors.framework.api.ConnectorInfoManager;
import org.identityconnectors.framework.api.ConnectorInfoManagerFactory;
import org.identityconnectors.framework.api.ConnectorKey;
import org.identityconnectors.framework.api.RemoteFrameworkConnectionInfo;
import org.identityconnectors.framework.api.operations.APIOperation;
import org.identityconnectors.framework.api.operations.AuthenticationApiOp;
import org.identityconnectors.framework.api.operations.CreateApiOp;
import org.identityconnectors.framework.api.operations.DeleteApiOp;
import org.identityconnectors.framework.api.operations.GetApiOp;
import org.identityconnectors.framework.api.operations.SchemaApiOp;
import org.identityconnectors.framework.api.operations.ScriptOnConnectorApiOp;
import org.identityconnectors.framework.api.operations.ScriptOnResourceApiOp;
import org.identityconnectors.framework.api.operations.SearchApiOp;
import org.identityconnectors.framework.api.operations.SyncApiOp;
import org.identityconnectors.framework.api.operations.TestApiOp;
import org.identityconnectors.framework.api.operations.UpdateApiOp;
import org.identityconnectors.framework.api.operations.ValidateApiOp;
import org.identityconnectors.framework.common.FrameworkUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Element;
import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration;
import com.evolveum.midpoint.prism.ComplexTypeDefinition;
import com.evolveum.midpoint.prism.ComplexTypeDefinitionImpl;
import com.evolveum.midpoint.prism.PrismContainerDefinitionImpl;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismPropertyDefinitionImpl;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.schema.PrismSchema;
import com.evolveum.midpoint.provisioning.ucf.api.ConnectorFactory;
import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance;
import com.evolveum.midpoint.provisioning.ucf.api.UcfUtil;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorHostType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.XmlSchemaType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
/**
* Implementation of the UCF Connector Manager API interface for ConnId framework.
*
* @author Radovan Semancik
*/
@Component
public class ConnectorFactoryConnIdImpl implements ConnectorFactory {
public static final String ICFS_NAME_DISPLAY_NAME = "ConnId Name";
public static final int ICFS_NAME_DISPLAY_ORDER = 110;
public static final String ICFS_UID_DISPLAY_NAME = "ConnId UID";
public static final int ICFS_UID_DISPLAY_ORDER = 100;
public static final QName ICFS_ACCOUNT = new QName(SchemaConstants.NS_ICF_SCHEMA, "account");
public static final String CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_TYPE_LOCAL_NAME = "ConfigurationPropertiesType";
public static final QName CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_TYPE_QNAME = new QName(SchemaConstants.NS_ICF_CONFIGURATION,
CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_TYPE_LOCAL_NAME);
public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_XML_ELEMENT_NAME = "connectorPoolConfiguration";
public static final QName CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_ELEMENT = new QName(SchemaConstants.NS_ICF_CONFIGURATION,
"connectorPoolConfiguration");
public static final QName CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_TYPE = new QName(SchemaConstants.NS_ICF_CONFIGURATION,
"ConnectorPoolConfigurationType");
protected static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis";
public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MIN_IDLE = "minIdle";
public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MAX_IDLE = "maxIdle";
public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MAX_OBJECTS = "maxObjects";
public static final String CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MAX_WAIT = "maxWait";
public static final String CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_XML_ELEMENT_NAME = "producerBufferSize";
public static final QName CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_ELEMENT = new QName(SchemaConstants.NS_ICF_CONFIGURATION,
CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_XML_ELEMENT_NAME);
public static final QName CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_TYPE = DOMUtil.XSD_INT;
public static final String CONNECTOR_SCHEMA_LEGACY_SCHEMA_XML_ELEMENT_NAME = "legacySchema";
public static final QName CONNECTOR_SCHEMA_LEGACY_SCHEMA_ELEMENT = new QName(SchemaConstants.NS_ICF_CONFIGURATION,
CONNECTOR_SCHEMA_LEGACY_SCHEMA_XML_ELEMENT_NAME);
public static final QName CONNECTOR_SCHEMA_LEGACY_SCHEMA_TYPE = DOMUtil.XSD_BOOLEAN;
public static final String CONNECTOR_SCHEMA_TIMEOUTS_XML_ELEMENT_NAME = "timeouts";
public static final QName CONNECTOR_SCHEMA_TIMEOUTS_ELEMENT = new QName(SchemaConstants.NS_ICF_CONFIGURATION,
CONNECTOR_SCHEMA_TIMEOUTS_XML_ELEMENT_NAME);
public static final QName CONNECTOR_SCHEMA_TIMEOUTS_TYPE = new QName(SchemaConstants.NS_ICF_CONFIGURATION, "TimeoutsType");
public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT_LOCAL_NAME = "resultsHandlerConfiguration";
public static final QName CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT = new QName(SchemaConstants.NS_ICF_CONFIGURATION,
CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT_LOCAL_NAME);
public static final QName CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_TYPE = new QName(SchemaConstants.NS_ICF_CONFIGURATION,
"ResultsHandlerConfigurationType");
public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_NORMALIZING_RESULTS_HANDLER = "enableNormalizingResultsHandler";
public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_FILTERED_RESULTS_HANDLER = "enableFilteredResultsHandler";
public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_FILTERED_RESULTS_HANDLER_IN_VALIDATION_MODE = "filteredResultsHandlerInValidationMode";
public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_CASE_INSENSITIVE_HANDLER = "enableCaseInsensitiveFilter";
public static final String CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_ATTRIBUTES_TO_GET_SEARCH_RESULTS_HANDLER = "enableAttributesToGetSearchResultsHandler";
static final Map<String, Class<? extends APIOperation>> apiOpMap = new HashMap<String, Class<? extends APIOperation>>();
private static final String ICF_CONFIGURATION_NAMESPACE_PREFIX = SchemaConstants.ICF_FRAMEWORK_URI + "/bundle/";
private static final String CONNECTOR_IDENTIFIER_SEPARATOR = "/";
public static final int ATTR_DISPLAY_ORDER_START = 120;
public static final int ATTR_DISPLAY_ORDER_INCREMENT = 10;
private static final Trace LOGGER = TraceManager.getTrace(ConnectorFactoryConnIdImpl.class);
// This is not really used in the code. It is here just to make sure that the JUL logger is loaded
// by the parent classloader so we can correctly adjust the log levels from the main code
static final java.util.logging.Logger JUL_LOGGER = java.util.logging.Logger.getLogger(ConnectorFactoryConnIdImpl.class.getName());
private ConnectorInfoManagerFactory connectorInfoManagerFactory;
private ConnectorInfoManager localConnectorInfoManager;
private Set<URL> bundleURLs;
private Set<ConnectorType> localConnectorTypes = null;
@Autowired(required = true)
private MidpointConfiguration midpointConfiguration;
@Autowired(required = true)
private Protector protector;
@Autowired(required = true)
private PrismContext prismContext;
public ConnectorFactoryConnIdImpl() {
}
/**
* Initialize the ICF implementation. Look for all connector bundles, get
* basic information about them and keep that in memory.
*/
@PostConstruct
public void initialize() {
// OLD
// bundleURLs = listBundleJars();
bundleURLs = new HashSet<URL>();
Configuration config = midpointConfiguration.getConfiguration("midpoint.icf");
// Is classpath scan enabled
if (config.getBoolean("scanClasspath")) {
// Scan class path
bundleURLs.addAll(scanClassPathForBundles());
}
// Scan all provided directories
@SuppressWarnings("unchecked")
List<String> dirs = config.getList("scanDirectory");
for (String dir : dirs) {
bundleURLs.addAll(scanDirectory(dir));
}
for (URL u : bundleURLs) {
LOGGER.debug("ICF bundle URL : {}", u);
}
connectorInfoManagerFactory = ConnectorInfoManagerFactory.getInstance();
}
/**
* Creates new connector instance.
*
* It will initialize the connector by taking the XML Resource definition,
* transforming it to the ICF configuration and applying that to the new
* connector instance.
*
* @throws ObjectNotFoundException
* @throws SchemaException
*
*/
@Override
public ConnectorInstance createConnectorInstance(ConnectorType connectorType, String namespace, String desc)
throws ObjectNotFoundException, SchemaException {
ConnectorInfo cinfo = getConnectorInfo(connectorType);
if (cinfo == null) {
LOGGER.error("Failed to instantiate {}", ObjectTypeUtil.toShortString(connectorType));
LOGGER.debug("Connector key: {}, host: {}", getConnectorKey(connectorType),
ObjectTypeUtil.toShortString(connectorType));
LOGGER.trace("Connector object: {}", ObjectTypeUtil.dump(connectorType));
LOGGER.trace("Connector host object: {}", ObjectTypeUtil.dump(connectorType.getConnectorHost()));
throw new ObjectNotFoundException("The classes (JAR) of " + ObjectTypeUtil.toShortString(connectorType)
+ " were not found by the ICF framework; bundle="+connectorType.getConnectorBundle()+" connector type=" + connectorType.getConnectorType() + ", version="+connectorType.getConnectorVersion());
}
PrismSchema connectorSchema = UcfUtil.getConnectorSchema(connectorType, prismContext);
if (connectorSchema == null) {
connectorSchema = generateConnectorConfigurationSchema(cinfo, connectorType);
}
ConnectorInstanceConnIdImpl connectorImpl = new ConnectorInstanceConnIdImpl(cinfo, connectorType, namespace,
connectorSchema, protector, prismContext);
connectorImpl.setDescription(desc);
return connectorImpl;
}
/**
* Returns a list XML representation of the ICF connectors.
*
* @throws CommunicationException
*/
@Override
public Set<ConnectorType> listConnectors(ConnectorHostType host, OperationResult parentRestul)
throws CommunicationException {
OperationResult result = parentRestul.createSubresult(ConnectorFactory.OPERATION_LIST_CONNECTOR);
result.addContext(OperationResult.CONTEXT_IMPLEMENTATION_CLASS, ConnectorFactoryConnIdImpl.class);
result.addParam("host", host);
try {
if (host == null) {
Set<ConnectorType> connectors = listLocalConnectors();
result.recordSuccess();
return connectors;
} else {
// This is necessary as list of the remote connectors is cached locally.
// So if any remote connector is added then it will not be discovered unless we
// clear the cache. This may look like inefficiency but in fact the listConnectors() method is
// used only when discovering new connectors. Normal connector operation is using connector objects
// stored in repository.
connectorInfoManagerFactory.clearRemoteCache();
Set<ConnectorType> connectors = listRemoteConnectors(host);
result.recordSuccess();
return connectors;
}
} catch (Throwable icfException) {
Throwable ex = processIcfException(icfException, "list connectors", result);
result.recordFatalError(ex.getMessage(), ex);
if (ex instanceof CommunicationException) {
throw (CommunicationException) ex;
} else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
} else if (ex instanceof Error) {
throw (Error) ex;
} else {
throw new SystemException("Unexpected ICF exception: " + ex.getMessage(), ex);
}
}
}
private Set<ConnectorType> listLocalConnectors() {
if (localConnectorTypes == null) {
// Lazy initialize connector list
localConnectorTypes = new HashSet<ConnectorType>();
// Fetch list of local connectors from ICF
List<ConnectorInfo> connectorInfos = getLocalConnectorInfoManager().getConnectorInfos();
for (ConnectorInfo connectorInfo : connectorInfos) {
ConnectorType connectorType;
try {
connectorType = convertToConnectorType(connectorInfo, null);
localConnectorTypes.add(connectorType);
} catch (SchemaException e) {
LOGGER.error("Schema error while initializing ConnId connector {}: {}", getConnctorDesc(connectorInfo), e.getMessage(), e);
}
}
}
return localConnectorTypes;
}
private Set<ConnectorType> listRemoteConnectors(ConnectorHostType host) {
ConnectorInfoManager remoteConnectorInfoManager = getRemoteConnectorInfoManager(host);
Set<ConnectorType> connectorTypes = new HashSet<ConnectorType>();
List<ConnectorInfo> connectorInfos = remoteConnectorInfoManager.getConnectorInfos();
for (ConnectorInfo connectorInfo : connectorInfos) {
try {
ConnectorType connectorType = convertToConnectorType(connectorInfo, host);
connectorTypes.add(connectorType);
} catch (SchemaException e) {
LOGGER.error("Schema error while initializing ConnId connector {}: {}", getConnctorDesc(connectorInfo), e.getMessage(), e);
}
}
return connectorTypes;
}
private String getConnctorDesc(ConnectorInfo connectorInfo) {
return connectorInfo.getConnectorKey().getConnectorName();
}
/**
* Converts ICF ConnectorInfo into a midPoint XML connector representation.
*
* TODO: schema transformation
*
* @param hostType
* host that this connector runs on or null for local connectors
*/
private ConnectorType convertToConnectorType(ConnectorInfo cinfo, ConnectorHostType hostType) throws SchemaException {
ConnectorType connectorType = new ConnectorType();
ConnectorKey key = cinfo.getConnectorKey();
UcfUtil.addConnectorNames(connectorType, "ConnId", key.getBundleName(), key.getConnectorName(), key.getBundleVersion(), hostType);
String stringID = keyToNamespaceSuffix(key);
connectorType.setFramework(SchemaConstants.ICF_FRAMEWORK_URI);
connectorType.setConnectorType(key.getConnectorName());
connectorType.setNamespace(ICF_CONFIGURATION_NAMESPACE_PREFIX + stringID);
connectorType.setConnectorVersion(key.getBundleVersion());
connectorType.setConnectorBundle(key.getBundleName());
if (hostType != null) {
if (hostType.getOid() != null) {
// bind using connectorHostRef and OID
ObjectReferenceType ref = new ObjectReferenceType();
ref.setOid(hostType.getOid());
ref.setType(ObjectTypes.CONNECTOR_HOST.getTypeQName());
connectorType.setConnectorHostRef(ref);
} else {
// Embed the object
connectorType.setConnectorHost(hostType);
}
}
PrismSchema connectorSchema = generateConnectorConfigurationSchema(cinfo, connectorType);
LOGGER.trace("Generated connector schema for {}: {} definitions",
connectorType, connectorSchema.getDefinitions().size());
UcfUtil.setConnectorSchema(connectorType, connectorSchema);
return connectorType;
}
/**
* Converts ICF connector key to a simple string.
*
* The string may be used as an OID.
*/
private String keyToNamespaceSuffix(ConnectorKey key) {
StringBuilder sb = new StringBuilder();
sb.append(key.getBundleName());
sb.append(CONNECTOR_IDENTIFIER_SEPARATOR);
// Don't include version. It is lesser evil.
// sb.append(key.getBundleVersion());
// sb.append(CONNECTOR_IDENTIFIER_SEPARATOR);
sb.append(key.getConnectorName());
return sb.toString();
}
@Override
public PrismSchema generateConnectorConfigurationSchema(ConnectorType connectorType) throws ObjectNotFoundException {
ConnectorInfo cinfo = getConnectorInfo(connectorType);
return generateConnectorConfigurationSchema(cinfo, connectorType);
}
private PrismSchema generateConnectorConfigurationSchema(ConnectorInfo cinfo, ConnectorType connectorType) {
LOGGER.trace("Generating configuration schema for {}", this);
APIConfiguration defaultAPIConfiguration = cinfo.createDefaultAPIConfiguration();
ConfigurationProperties icfConfigurationProperties = defaultAPIConfiguration
.getConfigurationProperties();
if (icfConfigurationProperties == null || icfConfigurationProperties.getPropertyNames() == null
|| icfConfigurationProperties.getPropertyNames().isEmpty()) {
LOGGER.debug("No configuration schema for {}", this);
return null;
}
PrismSchema connectorSchema = new PrismSchemaImpl(connectorType.getNamespace(), prismContext);
// Create configuration type - the type used by the "configuration"
// element
PrismContainerDefinitionImpl<?> configurationContainerDef = ((PrismSchemaImpl) connectorSchema).createPropertyContainerDefinition(
ResourceType.F_CONNECTOR_CONFIGURATION.getLocalPart(),
SchemaConstants.CONNECTOR_SCHEMA_CONFIGURATION_TYPE_LOCAL_NAME);
// element with "ConfigurationPropertiesType" - the dynamic part of
// configuration schema
ComplexTypeDefinition configPropertiesTypeDef = ((PrismSchemaImpl) connectorSchema).createComplexTypeDefinition(new QName(
connectorType.getNamespace(),
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_TYPE_LOCAL_NAME));
// Create definition of "configurationProperties" type
// (CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_TYPE_LOCAL_NAME)
int displayOrder = 1;
for (String icfPropertyName : icfConfigurationProperties.getPropertyNames()) {
ConfigurationProperty icfProperty = icfConfigurationProperties.getProperty(icfPropertyName);
QName propXsdType = ConnIdUtil.icfTypeToXsdType(icfProperty.getType(), icfProperty.isConfidential());
LOGGER.trace("{}: Mapping ICF config schema property {} from {} to {}", new Object[] { this,
icfPropertyName, icfProperty.getType(), propXsdType });
PrismPropertyDefinitionImpl<?> propertyDefinifion = ((ComplexTypeDefinitionImpl) configPropertiesTypeDef).createPropertyDefinition(
icfPropertyName, propXsdType);
propertyDefinifion.setDisplayName(icfProperty.getDisplayName(null));
propertyDefinifion.setHelp(icfProperty.getHelpMessage(null));
if (ConnIdUtil.isMultivaluedType(icfProperty.getType())) {
propertyDefinifion.setMaxOccurs(-1);
} else {
propertyDefinifion.setMaxOccurs(1);
}
if (icfProperty.isRequired() && icfProperty.getValue() == null) {
// If ICF says that the property is required it may not be in fact really required if it also has a default value
propertyDefinifion.setMinOccurs(1);
} else {
propertyDefinifion.setMinOccurs(0);
}
propertyDefinifion.setDisplayOrder(displayOrder);
displayOrder++;
}
// Create common ICF configuration property containers as a references
// to a static schema
configurationContainerDef.createContainerDefinition(
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_ELEMENT,
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_TYPE, 0, 1);
configurationContainerDef.createPropertyDefinition(
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_ELEMENT,
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_TYPE, 0, 1);
configurationContainerDef.createContainerDefinition(
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_TIMEOUTS_ELEMENT,
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_TIMEOUTS_TYPE, 0, 1);
configurationContainerDef.createContainerDefinition(
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT,
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_TYPE, 0, 1);
configurationContainerDef.createPropertyDefinition(
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_LEGACY_SCHEMA_ELEMENT,
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_LEGACY_SCHEMA_TYPE, 0, 1);
// No need to create definition of "configuration" element.
// midPoint will look for this element, but it will be generated as part
// of the PropertyContainer serialization to schema
configurationContainerDef.createContainerDefinition(
SchemaConstants.CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_ELEMENT_QNAME,
configPropertiesTypeDef, 1, 1);
LOGGER.debug("Generated configuration schema for {}: {} definitions", this, connectorSchema.getDefinitions()
.size());
return connectorSchema;
}
/**
* Returns ICF connector info manager that manages local connectors. The
* manager will be created if it does not exist yet.
*
* @return ICF connector info manager that manages local connectors
*/
private ConnectorInfoManager getLocalConnectorInfoManager() {
if (null == localConnectorInfoManager) {
localConnectorInfoManager = connectorInfoManagerFactory.getLocalManager(bundleURLs.toArray(new URL[0]));
}
return localConnectorInfoManager;
}
/**
* Returns ICF connector info manager that manages local connectors. The
* manager will be created if it does not exist yet.
*
* @return ICF connector info manager that manages local connectors
*/
private ConnectorInfoManager getRemoteConnectorInfoManager(ConnectorHostType hostType) {
String hostname = hostType.getHostname();
int port = Integer.parseInt(hostType.getPort());
GuardedString key;
try {
key = new GuardedString(protector.decryptString(hostType.getSharedSecret()).toCharArray());
} catch (EncryptionException e) {
throw new SystemException("Shared secret decryption error: "+e.getMessage(),e);
}
Integer timeout = hostType.getTimeout();
if (timeout == null) {
timeout = 0;
}
boolean useSSL = false;
if (hostType.isProtectConnection() != null) {
useSSL = hostType.isProtectConnection();
}
List<TrustManager> trustManagers = protector.getTrustManagers();
LOGGER.trace("Creating RemoteFrameworkConnectionInfo: hostname={}, port={}, key={}, useSSL={}, trustManagers={}, timeout={}",
new Object[] {hostname, port, key, useSSL, trustManagers, timeout});
RemoteFrameworkConnectionInfo remoteFramewrorkInfo = new RemoteFrameworkConnectionInfo(hostname, port, key, useSSL, trustManagers, timeout);
return connectorInfoManagerFactory.getRemoteManager(remoteFramewrorkInfo);
}
/**
* Scan class path for connector bundles
*
* @return Set of all bundle URL
*/
private Set<URL> scanClassPathForBundles() {
Set<URL> bundle = new HashSet<URL>();
// scan class path for bundles
Enumeration<URL> en = null;
try {
// Search all jars in classpath
en = ConnectorFactoryConnIdImpl.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
} catch (IOException ex) {
LOGGER.debug("Error during reding content from class path");
}
// Find which one is ICF connector
while (en.hasMoreElements()) {
URL u = en.nextElement();
Properties prop = new Properties();
LOGGER.trace("Scan classloader resource: " + u.toString());
try {
// read content of META-INF/MANIFEST.MF
InputStream is = u.openStream();
// skip if unreadable
if (is == null) {
continue;
}
// Convert to properties
prop.load(is);
} catch (IOException ex) {
LOGGER.trace("Unable load: " + u + " [" + ex.getMessage() + "]");
}
if (null != prop.get("ConnectorBundle-Name")) {
LOGGER.info("Discovered ICF bundle on CLASSPATH: " + prop.get("ConnectorBundle-Name") + " version: "
+ prop.get("ConnectorBundle-Version"));
// hack to split MANIFEST from name
try {
String upath = u.getPath();
URL tmp = new URL(toUrl(upath.substring(0, upath.lastIndexOf("!"))));
if (isThisBundleCompatible(tmp)) {
bundle.add(tmp);
} else {
LOGGER.warn("Skip loading ICF bundle {} due error occured", tmp);
}
} catch (MalformedURLException e) {
LOGGER.error("This never happend we hope. URL:" + u.getPath(), e);
throw new SystemException(e);
}
}
}
return bundle;
}
private String toUrl(String string) {
if (string.contains(":")) {
// We are OK, there is a protocol section
return string;
}
// We assume file protocol as default
return "file:" + string;
}
/**
* Scan directory for bundles only on first lval we do the scan
*
* @param path
* @return
*/
private Set<URL> scanDirectory(String path) {
// Prepare return object
Set<URL> bundle = new HashSet<URL>();
// COnvert path to object File
File dir = new File(path);
// Test if this path is single jar or need to do deep examination
if (isThisJarFileBundle(dir)) {
try {
if (isThisBundleCompatible(dir.toURI().toURL())) {
bundle.add(dir.toURI().toURL());
} else {
LOGGER.warn("Skip loading budle {} due error occured", dir.toURI().toURL());
}
} catch (MalformedURLException e) {
LOGGER.error("This never happend we hope.", e);
throw new SystemException(e);
}
return bundle;
}
// Test if it is a directory
if (!dir.isDirectory()) {
LOGGER.error("Provided Icf connector path {} is not a directory.", dir.getAbsolutePath());
}
// List directory items
File[] dirEntries = dir.listFiles();
if (null == dirEntries) {
LOGGER.warn("No bundles found in directory {}", dir.getAbsolutePath());
return bundle;
}
// test all entires for bundle
for (int i = 0; i < dirEntries.length; i++) {
if (isThisJarFileBundle(dirEntries[i])) {
try {
if (isThisBundleCompatible(dirEntries[i].toURI().toURL())) {
bundle.add(dirEntries[i].toURI().toURL());
} else {
LOGGER.warn("Skip loading budle {} due error occured", dirEntries[i].toURI().toURL());
}
} catch (MalformedURLException e) {
LOGGER.error("This never happend we hope.", e);
throw new SystemException(e);
}
}
}
return bundle;
}
/**
* Test if bundle internal configuration and dependencies are OK
* @param bundleUrl
* tested bundle URL
* @return true if OK
*/
private Boolean isThisBundleCompatible(URL bundleUrl) {
if (null == bundleUrl)
return false;
try {
ConnectorInfoManager localManager = ConnectorInfoManagerFactory.getInstance().getLocalManager(bundleUrl);
List<ConnectorInfo> connectorInfos = localManager.getConnectorInfos();
if (connectorInfos == null || connectorInfos.isEmpty()) {
LOGGER.error("Strange error happened. ConnId is not accepting bundle {}. But no error is indicated.", bundleUrl);
return false;
} else {
LOGGER.trace("Found {} compatible connectors in bundle {}", connectorInfos.size(), bundleUrl);
return true;
}
} catch (Exception ex) {
if (LOGGER.isDebugEnabled()) {
LOGGER.error("Error instantiating ICF bundle using URL '{}': {}", new Object[] { bundleUrl, ex.getMessage(), ex});
} else {
LOGGER.error("Error instantiating ICF bundle using URL '{}': {}", new Object[] { bundleUrl, ex.getMessage()});
}
return false;
}
}
/**
* Test if provided file is connector bundle
*
* @param file
* tested file
* @return boolean
*/
private Boolean isThisJarFileBundle(File file) {
// Startup tests
if (null == file) {
throw new IllegalArgumentException("No file is providied for bundle test.");
}
// Skip all processing if it is not a file
if (!file.isFile()) {
LOGGER.debug("This {} is not a file", file.getAbsolutePath());
return false;
}
Properties prop = new Properties();
JarFile jar = null;
// Open jar file
try {
jar = new JarFile(file);
} catch (IOException ex) {
LOGGER.debug("Unable to read jar file: " + file.getAbsolutePath() + " [" + ex.getMessage() + "]");
return false;
}
// read jar file
InputStream is;
try {
JarEntry entry = new JarEntry("META-INF/MANIFEST.MF");
is = jar.getInputStream(entry);
} catch (IOException ex) {
LOGGER.debug("Unable to fine MANIFEST.MF in jar file: " + file.getAbsolutePath() + " [" + ex.getMessage()
+ "]");
return false;
}
// Parse jar file
try {
prop.load(is);
} catch (IOException ex) {
LOGGER.debug("Unable to parse jar file: " + file.getAbsolutePath() + " [" + ex.getMessage() + "]");
return false;
} catch (NullPointerException ex) {
LOGGER.debug("Unable to parse jar file: " + file.getAbsolutePath() + " [" + ex.getMessage() + "]");
return false;
}
// Test if it is a connector
if (null != prop.get("ConnectorBundle-Name")) {
LOGGER.info("Discovered ICF bundle in JAR: " + prop.get("ConnectorBundle-Name") + " version: "
+ prop.get("ConnectorBundle-Version"));
return true;
}
LOGGER.debug("Provided file {} is not iCF bundle jar", file.getAbsolutePath());
return false;
}
/**
* Get contect informations
*
* @param connectorType
* @return
* @throws ObjectNotFoundException
*/
private ConnectorInfo getConnectorInfo(ConnectorType connectorType) throws ObjectNotFoundException {
if (!SchemaConstants.ICF_FRAMEWORK_URI.equals(connectorType.getFramework())) {
throw new ObjectNotFoundException("Requested connector for framework " + connectorType.getFramework()
+ " cannot be found in framework " + SchemaConstants.ICF_FRAMEWORK_URI);
}
ConnectorKey key = getConnectorKey(connectorType);
if (connectorType.getConnectorHost() == null && connectorType.getConnectorHostRef() == null) {
// Local connector
return getLocalConnectorInfoManager().findConnectorInfo(key);
}
ConnectorHostType host = connectorType.getConnectorHost();
if (host == null) {
throw new ObjectNotFoundException(
"Attempt to use remote connector without ConnectorHostType resolved (there is only ConnectorHostRef");
}
return getRemoteConnectorInfoManager(host).findConnectorInfo(key);
}
private ConnectorKey getConnectorKey(ConnectorType connectorType) {
return new ConnectorKey(connectorType.getConnectorBundle(), connectorType.getConnectorVersion(),
connectorType.getConnectorType());
}
@Override
public void selfTest(OperationResult parentTestResult) {
selfTestGuardedString(parentTestResult);
}
@Override
public boolean supportsFramework(String frameworkIdentifier) {
return SchemaConstants.ICF_FRAMEWORK_URI.equals(frameworkIdentifier);
}
@Override
public String getFrameworkVersion() {
Version version = FrameworkUtil.getFrameworkVersion();
if (version == null) {
return null;
}
return version.getVersion();
}
private void selfTestGuardedString(OperationResult parentTestResult) {
OperationResult result = parentTestResult.createSubresult(ConnectorFactoryConnIdImpl.class + ".selfTestGuardedString");
OperationResult subresult = result.createSubresult(ConnectorFactoryConnIdImpl.class + ".selfTestGuardedString.encryptorReflection");
EncryptorFactory encryptorFactory = EncryptorFactory.getInstance();
subresult.addReturn("encryptorFactoryImpl", encryptorFactory.getClass());
LOGGER.debug("Encryptor factory implementation class: {}", encryptorFactory.getClass());
Encryptor encryptor = EncryptorFactory.getInstance().newRandomEncryptor();
subresult.addReturn("encryptorImpl", encryptor.getClass());
LOGGER.debug("Encryptor implementation class: {}", encryptor.getClass());
if (encryptor.getClass().getName().equals("org.identityconnectors.common.security.impl.EncryptorImpl")) {
// let's do some reflection magic to have a look inside
try {
LOGGER.trace("Encryptor fields: {}", Arrays.asList(encryptor.getClass().getDeclaredFields()));
Field keyField = encryptor.getClass().getDeclaredField("key");
keyField.setAccessible(true);
Key key = (Key) keyField.get(encryptor);
subresult.addReturn("keyAlgorithm", key.getAlgorithm());
subresult.addReturn("keyLength", key.getEncoded().length*8);
subresult.addReturn("keyFormat", key.getFormat());
subresult.recordSuccess();
} catch (IllegalArgumentException e) {
subresult.recordPartialError("Reflection introspection failed", e);
} catch (IllegalAccessException e) {
subresult.recordPartialError("Reflection introspection failed", e);
} catch (NoSuchFieldException e) {
subresult.recordPartialError("Reflection introspection failed", e);
} catch (SecurityException e) {
subresult.recordPartialError("Reflection introspection failed", e);
}
}
OperationResult encryptorSubresult = result.createSubresult(ConnectorFactoryConnIdImpl.class + ".selfTestGuardedString.encryptor");
try {
String plainString = "Scurvy seadog";
byte[] encryptedBytes = encryptor.encrypt(plainString.getBytes());
byte[] decryptedBytes = encryptor.decrypt(encryptedBytes);
String decryptedString = new String(decryptedBytes);
if (!plainString.equals(decryptedString)) {
encryptorSubresult.recordFatalError("Encryptor roundtrip failed; encrypted="+plainString+", decrypted="+decryptedString);
} else {
encryptorSubresult.recordSuccess();
}
} catch (Throwable e) {
LOGGER.error("Encryptor operation error: {}", e.getMessage(), e);
encryptorSubresult.recordFatalError("Encryptor opeation error: " + e.getMessage(), e);
}
final OperationResult guardedStringSubresult = result.createSubresult(ConnectorFactoryConnIdImpl.class + ".selfTestGuardedString.guardedString");
// try to encrypt and decrypt GuardedString
try {
final String origString = "Shiver me timbers";
// This should encrypt it
GuardedString guardedString = new GuardedString(origString.toCharArray());
// and this should decrypt it
guardedString.access(new GuardedString.Accessor() {
@Override
public void access(char[] decryptedChars) {
if (!(new String(decryptedChars)).equals(origString)) {
guardedStringSubresult.recordFatalError("GuardeString roundtrip failed; encrypted="+origString+", decrypted="+(new String(decryptedChars)));
}
}
});
guardedStringSubresult.recordSuccessIfUnknown();
} catch (Throwable e) {
LOGGER.error("GuardedString operation error: {}", e.getMessage(), e);
guardedStringSubresult.recordFatalError("GuardedString opeation error: " + e.getMessage(), e);
}
result.computeStatus();
}
static Class<? extends APIOperation> resolveApiOpClass(String opName) {
return apiOpMap.get(opName);
}
static {
apiOpMap.put("create", CreateApiOp.class);
apiOpMap.put("get", GetApiOp.class);
apiOpMap.put("update", UpdateApiOp.class);
apiOpMap.put("delete", DeleteApiOp.class);
apiOpMap.put("test", TestApiOp.class);
apiOpMap.put("scriptOnConnector", ScriptOnConnectorApiOp.class);
apiOpMap.put("scriptOnResource", ScriptOnResourceApiOp.class);
apiOpMap.put("authentication", AuthenticationApiOp.class);
apiOpMap.put("search", SearchApiOp.class);
apiOpMap.put("validate", ValidateApiOp.class);
apiOpMap.put("sync", SyncApiOp.class);
apiOpMap.put("schema", SchemaApiOp.class);
}
@Override
public void shutdown() {
LOGGER.info("Shutting down ConnId framework");
ConnectorFacadeFactory.getInstance().dispose();
}
}