blob: 76f5d8a749747995fb42bcb5767cc9620a553a9f [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.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.MatchType;
import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.entity.AnyType;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.apache.syncope.core.provisioning.api.Connector;
import org.apache.syncope.core.provisioning.api.IntAttrName;
import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
import org.apache.syncope.core.persistence.api.dao.PullMatch;
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.dao.search.AnyCond;
import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.DerSchema;
import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
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.VirSchema;
import org.apache.syncope.core.persistence.api.entity.resource.Item;
import org.apache.syncope.core.provisioning.api.VirAttrHandler;
import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
import org.apache.syncope.core.spring.ImplementationManager;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.Name;
import org.identityconnectors.framework.common.objects.SearchResult;
import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
import org.identityconnectors.framework.common.objects.SyncDelta;
import org.identityconnectors.framework.common.objects.SyncDeltaBuilder;
import org.identityconnectors.framework.common.objects.SyncDeltaType;
import org.identityconnectors.framework.common.objects.SyncToken;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.spi.SearchResultsHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@Transactional(readOnly = true)
@Component
public class InboundMatcher {
private static final Logger LOG = LoggerFactory.getLogger(InboundMatcher.class);
@Autowired
private UserDAO userDAO;
@Autowired
private AnyObjectDAO anyObjectDAO;
@Autowired
private GroupDAO groupDAO;
@Autowired
private AnySearchDAO searchDAO;
@Autowired
private RealmDAO realmDAO;
@Autowired
private VirSchemaDAO virSchemaDAO;
@Autowired
private VirAttrHandler virAttrHandler;
@Autowired
private IntAttrNameParser intAttrNameParser;
@Autowired
private AnyUtilsFactory anyUtilsFactory;
public Optional<PullMatch> match(
final AnyType anyType,
final String nameValue,
final ExternalResource resource,
final Connector connector) {
Optional<? extends Provision> provision = resource.getProvision(anyType);
if (provision.isEmpty()) {
return Optional.empty();
}
Stream<Item> mapItems = Stream.concat(
provision.get().getMapping().getItems().stream(),
virSchemaDAO.findByProvision(provision.get()).stream().map(VirSchema::asLinkingMappingItem));
List<ConnectorObject> found = new ArrayList<>();
Name nameAttr = new Name(nameValue);
connector.search(provision.get().getObjectClass(),
provision.get().isIgnoreCaseMatch()
? FilterBuilder.equalsIgnoreCase(nameAttr)
: FilterBuilder.equalTo(nameAttr),
new SearchResultsHandler() {
@Override
public void handleResult(final SearchResult result) {
// nothing to do
}
@Override
public boolean handle(final ConnectorObject connectorObject) {
return found.add(connectorObject);
}
}, MappingUtils.buildOperationOptions(mapItems));
Optional<PullMatch> result = Optional.empty();
if (found.isEmpty()) {
LOG.debug("No {} found on {} with {} {}", provision.get().getObjectClass(), resource, Name.NAME, nameValue);
} else {
if (found.size() > 1) {
LOG.warn("More than one {} found on {} with {} {} - taking first only",
provision.get().getObjectClass(), resource, Name.NAME, nameValue);
}
ConnectorObject connObj = found.iterator().next();
try {
List<PullMatch> matches = match(
new SyncDeltaBuilder().
setToken(new SyncToken("")).
setDeltaType(SyncDeltaType.CREATE_OR_UPDATE).
setObject(connObj).
build(),
provision.get());
if (matches.isEmpty()) {
LOG.debug("No matching {} found for {}, aborting", anyType.getKind(), connObj);
} else {
if (matches.size() > 1) {
LOG.warn("More than one {} found {} - taking first only", anyType.getKind(), matches);
}
result = matches.stream().filter(match -> match.getAny() != null).findFirst();
result.ifPresent(pullMatch -> virAttrHandler.setValues(pullMatch.getAny(), connObj));
}
} catch (IllegalArgumentException e) {
LOG.warn(e.getMessage());
}
}
return result;
}
public List<PullMatch> matchByConnObjectKeyValue(
final Item connObjectKeyItem,
final String connObjectKeyValue,
final Provision provision) {
return matchByConnObjectKeyValue(
connObjectKeyItem,
connObjectKeyValue,
provision.getAnyType().getKind(),
provision.isIgnoreCaseMatch(),
provision.getResource());
}
public List<PullMatch> matchByConnObjectKeyValue(
final Item connObjectKeyItem,
final String connObjectKeyValue,
final AnyTypeKind anyTypeKind,
final boolean ignoreCaseMatch,
final ExternalResource resource) {
String finalConnObjectKeyValue = connObjectKeyValue;
for (ItemTransformer transformer : MappingUtils.getItemTransformers(connObjectKeyItem)) {
List<Object> output = transformer.beforePull(
connObjectKeyItem,
null,
List.of(finalConnObjectKeyValue));
if (!CollectionUtils.isEmpty(output)) {
finalConnObjectKeyValue = output.get(0).toString();
}
}
List<PullMatch> noMatchResult = List.of(PullCorrelationRule.NO_MATCH);
IntAttrName intAttrName;
try {
intAttrName = intAttrNameParser.parse(connObjectKeyItem.getIntAttrName(), anyTypeKind);
} catch (ParseException e) {
LOG.error("Invalid intAttrName '{}' specified, ignoring", connObjectKeyItem.getIntAttrName(), e);
return noMatchResult;
}
AnyUtils anyUtils = anyUtilsFactory.getInstance(anyTypeKind);
List<Any<?>> anys = new ArrayList<>();
if (intAttrName.getField() != null) {
switch (intAttrName.getField()) {
case "key":
Optional.ofNullable(anyUtils.dao().find(finalConnObjectKeyValue)).ifPresent(anys::add);
break;
case "username":
if (anyTypeKind == AnyTypeKind.USER && ignoreCaseMatch) {
AnyCond cond = new AnyCond(AttrCond.Type.IEQ);
cond.setSchema("username");
cond.setExpression(finalConnObjectKeyValue);
anys.addAll(searchDAO.search(SearchCond.getLeaf(cond), AnyTypeKind.USER));
} else {
Optional.ofNullable(userDAO.findByUsername(finalConnObjectKeyValue)).ifPresent(anys::add);
}
break;
case "name":
if (anyTypeKind == AnyTypeKind.GROUP && ignoreCaseMatch) {
AnyCond cond = new AnyCond(AttrCond.Type.IEQ);
cond.setSchema("name");
cond.setExpression(finalConnObjectKeyValue);
anys.addAll(searchDAO.search(SearchCond.getLeaf(cond), AnyTypeKind.GROUP));
} else {
Optional.ofNullable(groupDAO.findByName(finalConnObjectKeyValue)).ifPresent(anys::add);
}
if (anyTypeKind == AnyTypeKind.ANY_OBJECT && ignoreCaseMatch) {
AnyCond cond = new AnyCond(AttrCond.Type.IEQ);
cond.setSchema("name");
cond.setExpression(finalConnObjectKeyValue);
anys.addAll(searchDAO.search(SearchCond.getLeaf(cond), AnyTypeKind.ANY_OBJECT));
} else {
Optional.ofNullable(anyObjectDAO.findByName(finalConnObjectKeyValue)).ifPresent(anys::add);
}
break;
default:
}
} else if (intAttrName.getSchemaType() != null) {
switch (intAttrName.getSchemaType()) {
case PLAIN:
PlainAttrValue value = intAttrName.getSchema().isUniqueConstraint()
? anyUtils.newPlainAttrUniqueValue()
: anyUtils.newPlainAttrValue();
try {
value.parseValue((PlainSchema) intAttrName.getSchema(), finalConnObjectKeyValue);
} catch (ParsingValidationException e) {
LOG.error("While parsing provided {} {}", Uid.NAME, value, e);
value.setStringValue(finalConnObjectKeyValue);
}
if (intAttrName.getSchema().isUniqueConstraint()) {
anyUtils.dao().findByPlainAttrUniqueValue((PlainSchema) intAttrName.getSchema(),
(PlainAttrUniqueValue) value, ignoreCaseMatch).
ifPresent(anys::add);
} else {
anys.addAll(anyUtils.dao().findByPlainAttrValue((PlainSchema) intAttrName.getSchema(),
value, ignoreCaseMatch));
}
break;
case DERIVED:
anys.addAll(anyUtils.dao().findByDerAttrValue((DerSchema) intAttrName.getSchema(),
finalConnObjectKeyValue, ignoreCaseMatch));
break;
default:
}
}
List<PullMatch> result = anys.stream().
map(any -> new PullMatch(MatchType.ANY, any)).
collect(Collectors.toList());
if (resource != null) {
userDAO.findLinkedAccount(resource, finalConnObjectKeyValue).
map(account -> new PullMatch(MatchType.LINKED_ACCOUNT, account)).
ifPresent(result::add);
}
return result.isEmpty() ? noMatchResult : result;
}
private List<PullMatch> matchByCorrelationRule(
final SyncDelta syncDelta,
final Provision provision,
final PullCorrelationRule rule,
final AnyTypeKind type) {
List<PullMatch> result = new ArrayList<>();
result.addAll(searchDAO.search(rule.getSearchCond(syncDelta, provision), type).stream().
map(any -> rule.matching(any, syncDelta, provision)).
collect(Collectors.toList()));
if (result.isEmpty()) {
rule.unmatching(syncDelta, provision).ifPresent(result::add);
}
return result;
}
/**
* Finds internal entities based on external attributes and mapping.
*
* @param syncDelta change operation, including external attributes
* @param provision mapping
* @return list of matching users' / groups' / any objects' keys
*/
public List<PullMatch> match(final SyncDelta syncDelta, final Provision provision) {
Optional<? extends PullCorrelationRuleEntity> correlationRule = provision.getResource().getPullPolicy() == null
? Optional.empty()
: provision.getResource().getPullPolicy().getCorrelationRule(provision.getAnyType());
Optional<PullCorrelationRule> rule = Optional.empty();
if (correlationRule.isPresent()) {
try {
rule = ImplementationManager.buildPullCorrelationRule(correlationRule.get().getImplementation());
} catch (Exception e) {
LOG.error("While building {}", correlationRule.get().getImplementation(), e);
}
}
List<PullMatch> result = List.of();
try {
if (rule.isPresent()) {
result = matchByCorrelationRule(syncDelta, provision, rule.get(), provision.getAnyType().getKind());
} else {
String connObjectKeyValue = null;
Optional<? extends Item> connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
if (connObjectKeyItem.isPresent()) {
Attribute connObjectKeyAttr = syncDelta.getObject().
getAttributeByName(connObjectKeyItem.get().getExtAttrName());
if (connObjectKeyAttr != null) {
connObjectKeyValue = AttributeUtil.getStringValue(connObjectKeyAttr);
}
// fallback to __UID__
if (connObjectKeyValue == null) {
connObjectKeyValue = syncDelta.getUid().getUidValue();
}
}
if (connObjectKeyValue == null) {
result = List.of(PullCorrelationRule.NO_MATCH);
} else {
result = matchByConnObjectKeyValue(connObjectKeyItem.get(), connObjectKeyValue, provision);
}
}
} catch (RuntimeException e) {
LOG.error("Could not match {} with any existing {}", syncDelta, provision.getAnyType(), e);
}
if (result.size() == 1 && result.get(0).getMatchTarget() == MatchType.ANY) {
virAttrHandler.setValues(result.get(0).getAny(), syncDelta.getObject());
}
return result;
}
/**
* Finds internal realms based on external attributes and mapping.
*
* @param syncDelta change operation, including external attributes
* @param orgUnit mapping
* @return list of matching realms' keys.
*/
@Transactional(readOnly = true)
public List<Realm> match(final SyncDelta syncDelta, final OrgUnit orgUnit) {
String connObjectKey = null;
Optional<? extends Item> connObjectKeyItem = orgUnit.getConnObjectKeyItem();
if (connObjectKeyItem.isPresent()) {
Attribute connObjectKeyAttr = syncDelta.getObject().
getAttributeByName(connObjectKeyItem.get().getExtAttrName());
if (connObjectKeyAttr != null) {
connObjectKey = AttributeUtil.getStringValue(connObjectKeyAttr);
}
}
if (connObjectKey == null) {
return List.of();
}
for (ItemTransformer transformer : MappingUtils.getItemTransformers(connObjectKeyItem.get())) {
List<Object> output = transformer.beforePull(
connObjectKeyItem.get(),
null,
List.of(connObjectKey));
if (!CollectionUtils.isEmpty(output)) {
connObjectKey = output.get(0).toString();
}
}
List<Realm> result = new ArrayList<>();
Realm realm;
switch (connObjectKeyItem.get().getIntAttrName()) {
case "key":
realm = realmDAO.find(connObjectKey);
if (realm != null) {
result.add(realm);
}
break;
case "name":
if (orgUnit.isIgnoreCaseMatch()) {
final String realmName = connObjectKey;
result.addAll(realmDAO.findAll().stream().
filter(r -> r.getName().equalsIgnoreCase(realmName)).collect(Collectors.toList()));
} else {
result.addAll(realmDAO.findByName(connObjectKey).stream().collect(Collectors.toList()));
}
break;
case "fullpath":
realm = realmDAO.findByFullPath(connObjectKey);
if (realm != null) {
result.add(realm);
}
break;
default:
}
return result;
}
}