/* * 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.logic; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.collections4.Transformer; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutableTriple; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.ConnObjectTO; import org.apache.syncope.common.lib.to.ResourceTO; import org.apache.syncope.common.lib.types.AnyTypeKind; import org.apache.syncope.common.lib.types.ClientExceptionType; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.syncope.core.persistence.api.dao.DuplicateException; 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.GroupDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource; import org.apache.syncope.core.persistence.api.entity.resource.MappingItem; import org.apache.syncope.core.provisioning.api.Connector; import org.apache.syncope.core.provisioning.api.ConnectorFactory; import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder; import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils; import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; import org.apache.syncope.core.persistence.api.dao.ConnInstanceDAO; import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO; import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; import org.apache.syncope.core.persistence.api.entity.Any; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.ConnInstance; import org.apache.syncope.core.persistence.api.entity.VirSchema; import org.apache.syncope.core.persistence.api.entity.resource.Provision; import org.apache.syncope.core.provisioning.api.MappingManager; import org.apache.syncope.core.provisioning.java.utils.MappingUtils; 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.Uid; import org.identityconnectors.framework.spi.SearchResultsHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @Component public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> { @Autowired private ExternalResourceDAO resourceDAO; @Autowired private AnyTypeDAO anyTypeDAO; @Autowired private AnyObjectDAO anyObjectDAO; @Autowired private ConnInstanceDAO connInstanceDAO; @Autowired private UserDAO userDAO; @Autowired private GroupDAO groupDAO; @Autowired private VirSchemaDAO virSchemaDAO; @Autowired private ResourceDataBinder binder; @Autowired private ConnObjectUtils connObjectUtils; @Autowired private MappingManager mappingManager; @Autowired private ConnectorFactory connFactory; @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_CREATE + "')") public ResourceTO create(final ResourceTO resourceTO) { if (StringUtils.isBlank(resourceTO.getKey())) { SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing); sce.getElements().add("Resource key"); throw sce; } if (resourceDAO.find(resourceTO.getKey()) != null) { throw new DuplicateException(resourceTO.getKey()); } ExternalResource resource = null; try { resource = resourceDAO.save(binder.create(resourceTO)); } catch (SyncopeClientException e) { throw e; } catch (Exception e) { SyncopeClientException ex = SyncopeClientException.build(ClientExceptionType.InvalidExternalResource); ex.getElements().add(e.getMessage()); throw ex; } return binder.getResourceTO(resource); } @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_UPDATE + "')") public ResourceTO update(final ResourceTO resourceTO) { ExternalResource resource = resourceDAO.find(resourceTO.getKey()); if (resource == null) { throw new NotFoundException("Resource '" + resourceTO.getKey() + "'"); } resource = binder.update(resource, resourceTO); try { resource = resourceDAO.save(resource); } catch (SyncopeClientException e) { throw e; } catch (Exception e) { SyncopeClientException ex = SyncopeClientException.build(ClientExceptionType.InvalidExternalResource); ex.getElements().add(e.getMessage()); throw ex; } return binder.getResourceTO(resource); } @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_UPDATE + "')") public void setLatestSyncToken(final String key, final String anyTypeKey) { ExternalResource resource = resourceDAO.find(key); if (resource == null) { throw new NotFoundException("Resource '" + key + "'"); } AnyType anyType = anyTypeDAO.find(anyTypeKey); if (anyType == null) { throw new NotFoundException("AnyType '" + anyTypeKey + "'"); } Provision provision = resource.getProvision(anyType); if (provision == null) { throw new NotFoundException("Provision for AnyType '" + anyTypeKey + "' in Resource '" + key + "'"); } Connector connector; try { connector = connFactory.getConnector(resource); } catch (Exception e) { SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidConnInstance); sce.getElements().add(e.getMessage()); throw sce; } provision.setSyncToken(connector.getLatestSyncToken(provision.getObjectClass())); resourceDAO.save(resource); } @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_UPDATE + "')") public void removeSyncToken(final String key, final String anyTypeKey) { ExternalResource resource = resourceDAO.find(key); if (resource == null) { throw new NotFoundException("Resource '" + key + "'"); } AnyType anyType = anyTypeDAO.find(anyTypeKey); if (anyType == null) { throw new NotFoundException("AnyType '" + anyTypeKey + "'"); } Provision provision = resource.getProvision(anyType); if (provision == null) { throw new NotFoundException("Provision for AnyType '" + anyTypeKey + "' in Resource '" + key + "'"); } provision.setSyncToken(null); resourceDAO.save(resource); } @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_DELETE + "')") public ResourceTO delete(final String resourceName) { ExternalResource resource = resourceDAO.find(resourceName); if (resource == null) { throw new NotFoundException("Resource '" + resourceName + "'"); } ResourceTO resourceToDelete = binder.getResourceTO(resource); resourceDAO.delete(resourceName); return resourceToDelete; } @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_READ + "')") @Transactional(readOnly = true) public ResourceTO read(final String resourceName) { ExternalResource resource = resourceDAO.find(resourceName); if (resource == null) { throw new NotFoundException("Resource '" + resourceName + "'"); } return binder.getResourceTO(resource); } @PreAuthorize("isAuthenticated()") @Transactional(readOnly = true) public List<ResourceTO> list() { return CollectionUtils.collect(resourceDAO.findAll(), new Transformer<ExternalResource, ResourceTO>() { @Override public ResourceTO transform(final ExternalResource input) { return binder.getResourceTO(input); } }, new ArrayList<ResourceTO>()); } private Triple<ExternalResource, AnyType, Provision> connObjectInit( final String resourceKey, final String anyTypeKey) { ExternalResource resource = resourceDAO.find(resourceKey); if (resource == null) { throw new NotFoundException("Resource '" + resourceKey + "'"); } AnyType anyType = anyTypeDAO.find(anyTypeKey); if (anyType == null) { throw new NotFoundException("AnyType '" + anyTypeKey + "'"); } Provision provision = resource.getProvision(anyType); if (provision == null) { throw new NotFoundException("Provision on resource '" + resourceKey + "' for type '" + anyTypeKey + "'"); } return ImmutableTriple.of(resource, anyType, provision); } @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')") @Transactional(readOnly = true) public ConnObjectTO readConnObject(final String key, final String anyTypeKey, final String anyKey) { Triple<ExternalResource, AnyType, Provision> init = connObjectInit(key, anyTypeKey); // 1. find any Any<?> any = init.getMiddle().getKind() == AnyTypeKind.USER ? userDAO.find(anyKey) : init.getMiddle().getKind() == AnyTypeKind.ANY_OBJECT ? anyObjectDAO.find(anyKey) : groupDAO.find(anyKey); if (any == null) { throw new NotFoundException(init.getMiddle() + " " + anyKey); } // 2. build connObjectKeyItem MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(init.getRight()); if (connObjectKeyItem == null) { throw new NotFoundException( "ConnObjectKey mapping for " + init.getMiddle() + " " + anyKey + " on resource '" + key + "'"); } String connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, init.getRight()); // 3. determine attributes to query Set<MappingItem> linkinMappingItems = new HashSet<>(); for (VirSchema virSchema : virSchemaDAO.findByProvision(init.getRight())) { linkinMappingItems.add(virSchema.asLinkingMappingItem()); } Iterator<MappingItem> mapItems = IteratorUtils.chainedIterator( init.getRight().getMapping().getItems().iterator(), linkinMappingItems.iterator()); // 4. read from the underlying connector Connector connector = connFactory.getConnector(init.getLeft()); ConnectorObject connectorObject = connector.getObject(init.getRight().getObjectClass(), new Uid(connObjectKeyValue), MappingUtils.buildOperationOptions(mapItems)); if (connectorObject == null) { throw new NotFoundException( "Object " + connObjectKeyValue + " with class " + init.getRight().getObjectClass() + " not found on resource " + key); } // 5. build result Set<Attribute> attributes = connectorObject.getAttributes(); if (AttributeUtil.find(Uid.NAME, attributes) == null) { attributes.add(connectorObject.getUid()); } if (AttributeUtil.find(Name.NAME, attributes) == null) { attributes.add(connectorObject.getName()); } return connObjectUtils.getConnObjectTO(connectorObject); } @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_LIST_CONNOBJECT + "')") @Transactional(readOnly = true) public Pair<SearchResult, List<ConnObjectTO>> listConnObjects(final String key, final String anyTypeKey, final int size, final String pagedResultsCookie, final List<OrderByClause> orderBy) { Triple<ExternalResource, AnyType, Provision> init = connObjectInit(key, anyTypeKey); Connector connector = connFactory.getConnector(init.getLeft()); Set<MappingItem> linkinMappingItems = new HashSet<>(); for (VirSchema virSchema : virSchemaDAO.findByProvision(init.getRight())) { linkinMappingItems.add(virSchema.asLinkingMappingItem()); } Iterator<MappingItem> mapItems = IteratorUtils.chainedIterator( init.getRight().getMapping().getItems().iterator(), linkinMappingItems.iterator()); final SearchResult[] searchResult = new SearchResult[1]; final List<ConnObjectTO> connObjects = new ArrayList<>(); connector.search(init.getRight().getObjectClass(), null, new SearchResultsHandler() { private int count; @Override public void handleResult(final SearchResult result) { searchResult[0] = result; } @Override public boolean handle(final ConnectorObject connectorObject) { connObjects.add(connObjectUtils.getConnObjectTO(connectorObject)); // safety protection against uncontrolled result size count++; return count < size; } }, size, pagedResultsCookie, orderBy, mapItems); return ImmutablePair.of(searchResult[0], connObjects); } @PreAuthorize("hasRole('" + StandardEntitlement.CONNECTOR_READ + "')") @Transactional(readOnly = true) public void check(final ResourceTO resourceTO) { ConnInstance connInstance = connInstanceDAO.find(resourceTO.getConnector()); if (connInstance == null) { throw new NotFoundException("Connector '" + resourceTO.getConnector() + "'"); } connFactory.createConnector( connFactory.buildConnInstanceOverride( connInstance, resourceTO.getConfOverride(), resourceTO.isOverrideCapabilities() ? resourceTO.getCapabilitiesOverride() : null)). test(); } @Override protected ResourceTO resolveReference(final Method method, final Object... args) throws UnresolvedReferenceException { String key = null; if (ArrayUtils.isNotEmpty(args)) { for (int i = 0; key == null && i < args.length; i++) { if (args[i] instanceof String) { key = (String) args[i]; } else if (args[i] instanceof ResourceTO) { key = ((ResourceTO) args[i]).getKey(); } } } if (key != null) { try { return binder.getResourceTO(resourceDAO.find(key)); } catch (Throwable ignore) { LOG.debug("Unresolved reference", ignore); throw new UnresolvedReferenceException(ignore); } } throw new UnresolvedReferenceException(); } }