blob: 33d2d6493ca1e8dba499a8b21bf16ee0b4aa0ff1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.syncope.core.provisioning.java.pushpull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.LinkingMappingItem;
import org.apache.syncope.core.persistence.api.entity.VirSchema;
import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
import org.apache.syncope.core.provisioning.api.Connector;
import org.apache.syncope.core.provisioning.api.MappingManager;
import org.apache.syncope.core.provisioning.api.TimeoutException;
import org.apache.syncope.core.provisioning.api.VirAttrHandler;
import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
import org.apache.syncope.core.spring.ImplementationManager;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.SearchResult;
import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.spi.SearchResultsHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
public class OutboundMatcher {
protected static final Logger LOG = LoggerFactory.getLogger(OutboundMatcher.class);
protected final MappingManager mappingManager;
protected final UserDAO userDAO;
protected final AnyUtilsFactory anyUtilsFactory;
protected final VirSchemaDAO virSchemaDAO;
protected final VirAttrHandler virAttrHandler;
public OutboundMatcher(
final MappingManager mappingManager,
final UserDAO userDAO,
final AnyUtilsFactory anyUtilsFactory,
final VirSchemaDAO virSchemaDAO,
final VirAttrHandler virAttrHandler) {
this.mappingManager = mappingManager;
this.userDAO = userDAO;
this.anyUtilsFactory = anyUtilsFactory;
this.virSchemaDAO = virSchemaDAO;
this.virAttrHandler = virAttrHandler;
}
protected Optional<PushCorrelationRule> rule(final Provision provision) {
Optional<? extends PushCorrelationRuleEntity> correlationRule = provision.getResource().getPushPolicy() == null
? Optional.empty()
: provision.getResource().getPushPolicy().getCorrelationRule(provision.getAnyType());
Optional<PushCorrelationRule> rule = Optional.empty();
if (correlationRule.isPresent()) {
try {
rule = ImplementationManager.buildPushCorrelationRule(correlationRule.get().getImplementation());
} catch (Exception e) {
LOG.error("While building {}", correlationRule.get().getImplementation(), e);
}
}
return rule;
}
public String getFIQL(final ConnectorObject connectorObject, final Provision provision) {
return rule(provision).
map(rule -> rule.getFiql(connectorObject, provision)).
orElseGet(() -> PushCorrelationRule.DEFAULT_FIQL_BUILDER.apply(connectorObject, provision));
}
public List<ConnectorObject> match(
final PropagationTask task,
final Connector connector,
final Provision provision,
final List<PropagationActions> actions,
final String connObjectKeyValue) {
Optional<PushCorrelationRule> rule = rule(provision);
boolean isLinkedAccount = task.getAnyTypeKind() == AnyTypeKind.USER
&& userDAO.linkedAccountExists(task.getEntityKey(), connObjectKeyValue);
Any<?> any = null;
if (!isLinkedAccount) {
any = anyUtilsFactory.getInstance(task.getAnyTypeKind()).dao().find(task.getEntityKey());
}
Set<String> moreAttrsToGet = new HashSet<>();
actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(Optional.of(task), provision)));
List<ConnectorObject> result = new ArrayList<>();
try {
if (any != null && rule.isPresent()) {
result.addAll(matchByCorrelationRule(
connector,
rule.get().getFilter(any, provision),
provision,
Optional.of(moreAttrsToGet.toArray(new String[0])),
Optional.empty()));
} else {
MappingUtils.getConnObjectKeyItem(provision).flatMap(connObjectKeyItem -> matchByConnObjectKeyValue(
connector,
connObjectKeyItem,
connObjectKeyValue,
provision,
Optional.of(moreAttrsToGet.toArray(new String[0])),
Optional.empty())).ifPresent(result::add);
}
} catch (RuntimeException e) {
LOG.error("Could not match {} with any existing {}", any, provision.getObjectClass(), e);
}
if (any != null && result.size() == 1) {
virAttrHandler.setValues(any, result.get(0));
}
return result;
}
@Transactional(readOnly = true)
public List<ConnectorObject> match(
final Connector connector,
final Any<?> any,
final Provision provision,
final Optional<String[]> moreAttrsToGet,
final LinkingMappingItem... linkingItems) {
Set<String> matgFromPropagationActions = new HashSet<>();
provision.getResource().getPropagationActions().forEach(impl -> {
try {
matgFromPropagationActions.addAll(
ImplementationManager.<PropagationActions>build(impl).
moreAttrsToGet(Optional.empty(), provision));
} catch (Exception e) {
LOG.error("While building {}", impl, e);
}
});
Optional<String[]> effectiveMATG = Optional.of(Stream.concat(
moreAttrsToGet.stream().flatMap(Stream::of),
matgFromPropagationActions.stream()).toArray(String[]::new));
Optional<PushCorrelationRule> rule = rule(provision);
List<ConnectorObject> result = new ArrayList<>();
try {
if (rule.isPresent()) {
result.addAll(matchByCorrelationRule(
connector,
rule.get().getFilter(any, provision),
provision,
effectiveMATG,
ArrayUtils.isEmpty(linkingItems)
? Optional.empty() : Optional.of(List.of(linkingItems))));
} else {
Optional<? extends MappingItem> connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
Optional<String> connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, provision);
if (connObjectKeyItem.isPresent() && connObjectKeyValue.isPresent()) {
matchByConnObjectKeyValue(
connector,
connObjectKeyItem.get(),
connObjectKeyValue.get(),
provision,
effectiveMATG,
ArrayUtils.isEmpty(linkingItems)
? Optional.empty() : Optional.of(List.of(linkingItems))).
ifPresent(result::add);
}
}
} catch (RuntimeException e) {
LOG.error("Could not match {} with any existing {}", any, provision.getObjectClass(), e);
}
if (any != null && result.size() == 1) {
virAttrHandler.setValues(any, result.get(0));
}
return result;
}
protected List<ConnectorObject> matchByCorrelationRule(
final Connector connector,
final Filter filter,
final Provision provision,
final Optional<String[]> moreAttrsToGet,
final Optional<Collection<LinkingMappingItem>> linkingItems) {
Stream<MappingItem> items = Stream.concat(
provision.getMapping().getItems().stream(),
linkingItems.isPresent()
? linkingItems.get().stream()
: virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
List<ConnectorObject> objs = new ArrayList<>();
try {
connector.search(provision.getObjectClass(), filter, new SearchResultsHandler() {
@Override
public void handleResult(final SearchResult result) {
// nothing to do
}
@Override
public boolean handle(final ConnectorObject connectorObject) {
objs.add(connectorObject);
return true;
}
}, MappingUtils.buildOperationOptions(items, moreAttrsToGet.orElse(null)));
} catch (TimeoutException toe) {
LOG.debug("Request timeout", toe);
throw toe;
} catch (RuntimeException ignore) {
LOG.debug("Unexpected exception", ignore);
}
return objs;
}
@Transactional(readOnly = true)
public Optional<ConnectorObject> matchByConnObjectKeyValue(
final Connector connector,
final MappingItem connObjectKeyItem,
final String connObjectKeyValue,
final Provision provision,
final Optional<String[]> moreAttrsToGet,
final Optional<Collection<LinkingMappingItem>> linkingItems) {
Stream<MappingItem> items = Stream.concat(
provision.getMapping().getItems().stream(),
linkingItems.isPresent()
? linkingItems.get().stream()
: virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
ConnectorObject obj = null;
try {
obj = connector.getObject(
provision.getObjectClass(),
AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKeyValue),
provision.isIgnoreCaseMatch(),
MappingUtils.buildOperationOptions(items, moreAttrsToGet.orElse(null)));
} catch (TimeoutException toe) {
LOG.debug("Request timeout", toe);
throw toe;
} catch (RuntimeException ignore) {
LOG.debug("While resolving {}", connObjectKeyValue, ignore);
}
return Optional.ofNullable(obj);
}
}