Commit c9ac0b3f authored by Francesco Chicchiricco's avatar Francesco Chicchiricco
Browse files

[CRV-305] Backporting SYNCOPE-1083

parent f77bbe38
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.core.provisioning.api;
import java.net.URI;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.core.persistence.api.entity.ConnInstance;
import org.identityconnectors.framework.api.ConfigurationProperties;
import org.identityconnectors.framework.api.ConnectorInfo;
import org.identityconnectors.framework.api.ConnectorInfoManager;
/**
* Manage information about ConnId connector bundles.
*/
public interface ConnIdBundleManager {
ConfigurationProperties getConfigurationProperties(ConnectorInfo info);
Map<URI, ConnectorInfoManager> getConnManagers();
Pair<URI, ConnectorInfo> getConnectorInfo(ConnInstance connInstance);
Map<URI, ConnectorInfoManager> getConnInfoManagers();
void resetConnManagers();
List<URI> getLocations();
void setStringLocations(String stringLocations);
}
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.core.provisioning.java;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.entity.ConnInstance;
import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
import org.apache.syncope.core.provisioning.api.utils.URIUtils;
import org.identityconnectors.common.IOUtil;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.api.APIConfiguration;
import org.identityconnectors.framework.api.ConfigurationProperties;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConnIdBundleManagerImpl implements ConnIdBundleManager {
private static final Logger LOG = LoggerFactory.getLogger(ConnIdBundleManager.class);
private String stringLocations;
/**
* ConnId Locations.
*/
private List<URI> locations;
/**
* ConnectorInfoManager instances.
*/
private final Map<URI, ConnectorInfoManager> connInfoManagers =
Collections.synchronizedMap(new LinkedHashMap<URI, ConnectorInfoManager>());
@Override
public List<URI> getLocations() {
init();
return locations;
}
@Override
public void setStringLocations(final String stringLocations) {
this.stringLocations = stringLocations;
}
private void init() {
if (locations == null) {
locations = new ArrayList<>();
for (String location : StringUtils.isBlank(stringLocations) ? new String[0] : stringLocations.split(",")) {
try {
locations.add(URIUtils.buildForConnId(location));
LOG.info("Valid ConnId location: {}", location.trim());
} catch (Exception e) {
LOG.error("Invalid ConnId location: {}", location.trim(), e);
}
}
locations = Collections.unmodifiableList(locations);
}
}
private void initLocal(final URI location) {
// 1. Find bundles inside local directory
File bundleDirectory = new File(location);
String[] bundleFiles = bundleDirectory.list();
if (bundleFiles == null) {
throw new NotFoundException("Local bundles directory " + location);
}
List<URL> bundleFileURLs = new ArrayList<>();
for (String file : bundleFiles) {
try {
bundleFileURLs.add(IOUtil.makeURL(bundleDirectory, file));
} catch (IOException ignore) {
// ignore exception and don't add bundle
LOG.debug("{}/{} is not a valid connector bundle", bundleDirectory.toString(), file, ignore);
}
}
if (bundleFileURLs.isEmpty()) {
LOG.warn("No connector bundles found in {}", location);
}
LOG.debug("Configuring local connector server:"
+ "\n\tFiles: {}", bundleFileURLs);
// 2. Get connector info manager
ConnectorInfoManager manager = ConnectorInfoManagerFactory.getInstance().getLocalManager(
bundleFileURLs.toArray(new URL[bundleFileURLs.size()]));
if (manager == null) {
throw new NotFoundException("Local ConnectorInfoManager");
}
connInfoManagers.put(location, manager);
}
private void initRemote(final URI location) {
// 1. Extract conf params for remote connection from given URI
String host = location.getHost();
int port = location.getPort();
GuardedString key = new GuardedString(location.getUserInfo().toCharArray());
boolean useSSL = location.getScheme().equals("connids");
List<TrustManager> trustManagers = new ArrayList<>();
String[] params = StringUtils.isBlank(location.getQuery()) ? null : location.getQuery().split("&");
if (params != null && params.length > 0) {
final String[] trustAllCerts = params[0].split("=");
if (trustAllCerts != null && trustAllCerts.length > 1
&& "trustAllCerts".equalsIgnoreCase(trustAllCerts[0])
&& "true".equalsIgnoreCase(trustAllCerts[1])) {
trustManagers.add(new X509TrustManager() {
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType)
throws CertificateException {
// no checks, trust all
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType)
throws CertificateException {
// no checks, trust all
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
});
}
}
LOG.debug("Configuring remote connector server:"
+ "\n\tHost: {}"
+ "\n\tPort: {}"
+ "\n\tKey: {}"
+ "\n\tUseSSL: {}"
+ "\n\tTrustAllCerts: {}",
host, port, key, useSSL, !trustManagers.isEmpty());
RemoteFrameworkConnectionInfo info =
new RemoteFrameworkConnectionInfo(host, port, key, useSSL, trustManagers, 60 * 1000);
LOG.debug("Remote connection info: {}", info);
// 2. Get connector info manager
ConnectorInfoManager manager = ConnectorInfoManagerFactory.getInstance().getRemoteManager(info);
if (manager == null) {
throw new NotFoundException("Remote ConnectorInfoManager");
}
connInfoManagers.put(location, manager);
}
@Override
public void resetConnManagers() {
connInfoManagers.clear();
}
@Override
public Map<URI, ConnectorInfoManager> getConnManagers() {
init();
if (connInfoManagers.isEmpty()) {
for (URI location : locations) {
try {
if ("file".equals(location.getScheme())) {
LOG.debug("Local initialization: {}", location);
initLocal(location);
} else if (location.getScheme().startsWith("connid")) {
LOG.debug("Remote initialization: {}", location);
initRemote(location);
} else {
LOG.warn("Unsupported scheme: {}", location);
}
} catch (Exception e) {
LOG.error("Could not process {}", location, e);
}
}
}
if (LOG.isDebugEnabled()) {
for (Map.Entry<URI, ConnectorInfoManager> entry : connInfoManagers.entrySet()) {
LOG.debug("Connector bundles found at {}", entry.getKey());
for (ConnectorInfo connInfo : entry.getValue().getConnectorInfos()) {
LOG.debug("\t{}", connInfo.getConnectorDisplayName());
}
}
}
return connInfoManagers;
}
@Override
public Pair<URI, ConnectorInfo> getConnectorInfo(final ConnInstance connInstance) {
// check ConnIdLocation
URI uriLocation = null;
try {
uriLocation = URIUtils.buildForConnId(connInstance.getLocation());
} catch (Exception e) {
throw new IllegalArgumentException("Invalid ConnId location " + connInstance.getLocation(), e);
}
// create key for search all properties
ConnectorKey key = new ConnectorKey(
connInstance.getBundleName(), connInstance.getVersion(), connInstance.getConnectorName());
if (LOG.isDebugEnabled()) {
LOG.debug("\nBundle name: " + key.getBundleName()
+ "\nBundle version: " + key.getBundleVersion()
+ "\nBundle class: " + key.getConnectorName());
}
// get the specified connector
ConnectorInfo info = null;
if (getConnManagers().containsKey(uriLocation)) {
info = getConnManagers().get(uriLocation).findConnectorInfo(key);
}
if (info == null) {
throw new NotFoundException("ConnectorInfo for location " + connInstance.getLocation() + " and key " + key);
}
return Pair.of(uriLocation, info);
}
@Override
public Map<URI, ConnectorInfoManager> getConnInfoManagers() {
return connInfoManagers;
}
@Override
public ConfigurationProperties getConfigurationProperties(final ConnectorInfo info) {
if (info == null) {
throw new NotFoundException("Invalid: connector info is null");
}
// create default configuration
APIConfiguration apiConfig = info.createDefaultAPIConfiguration();
// retrieve the ConfigurationProperties.
ConfigurationProperties properties = apiConfig.getConfigurationProperties();
if (properties == null) {
throw new NotFoundException("Configuration properties");
}
if (LOG.isDebugEnabled()) {
for (String propName : properties.getPropertyNames()) {
LOG.debug("Property Name: {}"
+ "\nProperty Type: {}",
properties.getProperty(propName).getName(),
properties.getProperty(propName).getType());
}
}
return properties;
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.core.provisioning.java.data;
import java.net.URI;
import org.apache.syncope.core.provisioning.api.data.ConnInstanceDataBinder;
import java.util.Arrays;
import java.util.Collection;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.to.ConnInstanceTO;
import org.apache.syncope.common.lib.to.ConnPoolConfTO;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.ConnConfPropSchema;
import org.apache.syncope.common.lib.types.ConnConfProperty;
import org.apache.syncope.core.persistence.api.dao.ConnInstanceDAO;
import org.apache.syncope.core.persistence.api.entity.ConnInstance;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
import org.apache.syncope.core.provisioning.api.utils.ConnPoolConfUtils;
import org.identityconnectors.framework.api.ConfigurationProperties;
import org.identityconnectors.framework.api.ConfigurationProperty;
import org.identityconnectors.framework.impl.api.ConfigurationPropertyImpl;
import org.apache.syncope.core.spring.BeanUtils;
import org.identityconnectors.framework.api.ConnectorInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ConnInstanceDataBinderImpl implements ConnInstanceDataBinder {
private static final String[] IGNORE_PROPERTIES = { "poolConf", "location" };
@Autowired
private ConnIdBundleManager connIdBundleManager;
@Autowired
private ConnInstanceDAO connInstanceDAO;
@Autowired
private EntityFactory entityFactory;
@Override
public ConnInstance getConnInstance(final ConnInstanceTO connInstanceTO) {
SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
if (connInstanceTO.getLocation() == null) {
sce.getElements().add("location");
}
if (connInstanceTO.getBundleName() == null) {
sce.getElements().add("bundlename");
}
if (connInstanceTO.getVersion() == null) {
sce.getElements().add("bundleversion");
}
if (connInstanceTO.getConnectorName() == null) {
sce.getElements().add("connectorname");
}
if (connInstanceTO.getConf().isEmpty()) {
sce.getElements().add("configuration");
}
ConnInstance connInstance = entityFactory.newEntity(ConnInstance.class);
BeanUtils.copyProperties(connInstanceTO, connInstance, IGNORE_PROPERTIES);
if (connInstanceTO.getLocation() != null) {
connInstance.setLocation(connInstanceTO.getLocation());
}
if (connInstanceTO.getPoolConf() != null) {
connInstance.setPoolConf(
ConnPoolConfUtils.getConnPoolConf(connInstanceTO.getPoolConf(), entityFactory.newConnPoolConf()));
}
// Throw exception if there is at least one element set
if (!sce.isEmpty()) {
throw sce;
}
return connInstance;
}
@Override
public ConnInstance update(final String key, final ConnInstanceTO connInstanceTO) {
SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
if (key == null) {
sce.getElements().add("connector key");
}
ConnInstance connInstance = connInstanceDAO.find(key);
connInstance.getCapabilities().clear();
connInstance.getCapabilities().addAll(connInstanceTO.getCapabilities());
if (connInstanceTO.getLocation() != null) {
connInstance.setLocation(connInstanceTO.getLocation());
}
if (connInstanceTO.getBundleName() != null) {
connInstance.setBundleName(connInstanceTO.getBundleName());
}
if (connInstanceTO.getVersion() != null) {
connInstance.setVersion(connInstanceTO.getVersion());
}
if (connInstanceTO.getConnectorName() != null) {
connInstance.setConnectorName(connInstanceTO.getConnectorName());
}
if (connInstanceTO.getConf() != null && !connInstanceTO.getConf().isEmpty()) {
connInstance.setConf(connInstanceTO.getConf());
}
if (connInstanceTO.getDisplayName() != null) {
connInstance.setDisplayName(connInstanceTO.getDisplayName());
}
if (connInstanceTO.getConnRequestTimeout() != null) {
connInstance.setConnRequestTimeout(connInstanceTO.getConnRequestTimeout());
}
if (connInstanceTO.getPoolConf() == null) {
connInstance.setPoolConf(null);
} else {
connInstance.setPoolConf(
ConnPoolConfUtils.getConnPoolConf(connInstanceTO.getPoolConf(), entityFactory.newConnPoolConf()));
}
if (!sce.isEmpty()) {
throw sce;
}
return connInstance;
}
@Override
public ConnConfPropSchema build(final ConfigurationProperty property) {
ConnConfPropSchema connConfPropSchema = new ConnConfPropSchema();
connConfPropSchema.setName(property.getName());
connConfPropSchema.setDisplayName(property.getDisplayName(property.getName()));
connConfPropSchema.setHelpMessage(property.getHelpMessage(property.getName()));
connConfPropSchema.setRequired(property.isRequired());
connConfPropSchema.setType(property.getType().getName());
connConfPropSchema.setOrder(((ConfigurationPropertyImpl) property).getOrder());
connConfPropSchema.setConfidential(property.isConfidential());
if (property.getValue() != null) {
if (property.getValue().getClass().isArray()) {
connConfPropSchema.getDefaultValues().addAll(Arrays.asList((Object[]) property.getValue()));
} else if (property.getValue() instanceof Collection<?>) {
connConfPropSchema.getDefaultValues().addAll((Collection<?>) property.getValue());
} else {
connConfPropSchema.getDefaultValues().add(property.getValue());
}
}
return connConfPropSchema;
}
@Override
public ConnInstanceTO getConnInstanceTO(final ConnInstance connInstance) {
ConnInstanceTO connInstanceTO = new ConnInstanceTO();
Pair<URI, ConnectorInfo> info = connIdBundleManager.getConnectorInfo(connInstance);
BeanUtils.copyProperties(connInstance, connInstanceTO, IGNORE_PROPERTIES);
connInstanceTO.setLocation(info.getLeft().toASCIIString());
// refresh stored properties in the given connInstance with direct information from underlying connector
ConfigurationProperties properties =
connIdBundleManager.getConfigurationProperties(info.getRight());
for (final String propName : properties.getPropertyNames()) {
ConnConfPropSchema schema = build(properties.getProperty(propName));
ConnConfProperty property = IterableUtils.find(connInstanceTO.getConf(),
new Predicate<ConnConfProperty>() {
@Override
public boolean evaluate(final ConnConfProperty candidate) {
return propName.equals(candidate.getSchema().getName());
}
});
if (property == null) {
property = new ConnConfProperty();
connInstanceTO.getConf().add(property);
}
property.setSchema(schema);
}
// pool configuration
if (connInstance.getPoolConf() != null) {
ConnPoolConfTO poolConf = new ConnPoolConfTO();
BeanUtils.copyProperties(connInstance.getPoolConf(), poolConf);
connInstanceTO.setPoolConf(poolConf);
}
return connInstanceTO;
}
}