com.evolveum.polygon.scim.SalesforceHandlingStrategy.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.polygon.scim.SalesforceHandlingStrategy.java

Source

/*
 * Copyright (c) 2016 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.polygon.scim;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.NoRouteToHostException;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.http.Header;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.framework.common.exceptions.AlreadyExistsException;
import org.identityconnectors.framework.common.exceptions.ConnectionFailedException;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.exceptions.ConnectorIOException;
import org.identityconnectors.framework.common.exceptions.InvalidCredentialException;
import org.identityconnectors.framework.common.exceptions.OperationTimeoutException;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.AttributeInfoBuilder;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.filter.ContainsAllValuesFilter;
import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
import org.json.JSONArray;
import org.json.JSONObject;

import com.evolveum.polygon.scim.common.HttpPatch;

/**
 * 
 * @author Macik
 *
 *         Implementation of the "HandlingStrategy" methods for the Slack
 *         service provider.
 *
 */

public class SalesforceHandlingStrategy extends StandardScimHandlingStrategy implements HandlingStrategy {

    private static final Log LOGGER = Log.getLog(SalesforceHandlingStrategy.class);
    private static final String SCHEMATYPE = "urn:scim:schemas:extension:enterprise:1.0";
    private static final String JSON = "json";

    private static List<String> multivaluedAttributes = new ArrayList<String>();
    private static List<String> writableAttributes = new ArrayList<String>();

    static {

        multivaluedAttributes.add("members.User.value");
        multivaluedAttributes.add("members.Group.value");
        multivaluedAttributes.add("members.default.value");
        multivaluedAttributes.add("members.default.display");

        // writableAttributes.add("name.formatted");
        // writableAttributes.add("entitlements.default.value");
        // writableAttributes.add("emails.work.value");
        // writableAttributes.add("phoneNumbers.work.value");
        // writableAttributes.add("phoneNumbers.fax.value");
        // writableAttributes.add("phoneNumbers.mobile.value");
        // writableAttributes.add("photos.thumbnail.value");
        // writableAttributes.add("roles.value");
        // writableAttributes.add("photos.photo.value");
        // writableAttributes.add("addresses.work.value");
        // writableAttributes.add("groups.default.value");
        // writableAttributes.add("addresses.thumbnail.value");
        // writableAttributes.add("members.default.display");
    }

    @Override
    public Map<String, Object> translateReferenceValues(Map<String, Map<String, Object>> attributeMap,
            JSONArray referenceValues, Map<String, Object> subAttributeMap, int position, String attributeName) {

        JSONObject referenceValue = new JSONObject();
        Boolean isComplex = null;
        Map<String, Object> processedParameters = new HashMap<String, Object>();

        LOGGER.info(
                "Processing trough Salesforce scim schema inconsistencies workaround (canonicalValues,referenceTypes)");
        referenceValue = ((JSONArray) referenceValues).getJSONObject(position);
        for (String subAttributeKeyNames : subAttributeMap.keySet()) {
            if (!TYPE.equals(subAttributeKeyNames)) {
                StringBuilder complexAttrName = new StringBuilder(attributeName);
                attributeMap.put(
                        complexAttrName.append(DOT).append(referenceValue.get(VALUE)).append(DOT)
                                .append(subAttributeKeyNames).toString(),
                        (HashMap<String, Object>) subAttributeMap.get(subAttributeKeyNames));
                isComplex = true;

            }
        }
        if (isComplex != null) {
            processedParameters.put(ISCOMPLEX, isComplex);
        }
        processedParameters.put("attributeMap", attributeMap);

        return processedParameters;
    }

    @Override
    public Uid groupUpdateProcedure(Integer statusCode, JSONObject jsonObject, String uri, Header authHeader,
            ScimConnectorConfiguration conf) {

        Uid id = null;
        HttpClient httpClient = initHttpClient(conf);
        LOGGER.info(
                "Status code from first update query: {0}. Processing trough Salesforce \"group/member update\" workaround. ",
                statusCode);
        HttpGet httpGet = buildHttpGet(uri, authHeader);
        try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet);) {
            statusCode = response.getStatusLine().getStatusCode();
            LOGGER.info("status code: {0}", statusCode);
            if (statusCode == 200) {

                String responseString = EntityUtils.toString(response.getEntity());
                if (!responseString.isEmpty()) {

                    JSONObject json = new JSONObject(responseString);
                    LOGGER.info("Json object returned from service provider: {0}", json);
                    for (String attributeName : jsonObject.keySet()) {

                        json.put(attributeName, jsonObject.get(attributeName));
                    }

                    StringEntity bodyContent = new StringEntity(json.toString(1));
                    bodyContent.setContentType(CONTENTTYPE);

                    HttpPatch httpPatch = new HttpPatch(uri);
                    httpPatch.addHeader(authHeader);
                    httpPatch.addHeader(PRETTYPRINTHEADER);
                    httpPatch.setEntity(bodyContent);

                    try (CloseableHttpResponse secondaryResponse = (CloseableHttpResponse) httpClient
                            .execute(httpPatch)) {
                        responseString = EntityUtils.toString(secondaryResponse.getEntity());
                        statusCode = secondaryResponse.getStatusLine().getStatusCode();
                        LOGGER.info("status code: {0}", statusCode);
                        if (statusCode == 200 || statusCode == 201) {
                            LOGGER.info("Update of resource was successful");

                            json = new JSONObject(responseString);
                            id = new Uid(json.getString(ID));
                            LOGGER.ok("Json response: {0}", json.toString(1));
                            return id;
                        } else {
                            ErrorHandler.onNoSuccess(responseString, statusCode, "updating object");
                        }
                    }
                }
            }

        } catch (ClientProtocolException e) {
            LOGGER.error(
                    "An protocol exception has occurred while in the process of updating a resource object. Possible mismatch in the interpretation of the HTTP specification: {0}",
                    e.getLocalizedMessage());
            LOGGER.info(
                    "An protocol exception has occurred while in the process of updating a resource object. Possible mismatch in the interpretation of the HTTP specification: {0}",
                    e);
            throw new ConnectionFailedException(
                    "An protocol exception has occurred while in the process of updating a resource object, Possible mismatch in the interpretation of the HTTP specification.",
                    e);
        } catch (IOException e) {

            StringBuilder errorBuilder = new StringBuilder(
                    "Occurrence in the process of creating a resource object");

            if ((e instanceof SocketTimeoutException || e instanceof NoRouteToHostException)) {
                errorBuilder.insert(0, "The connection timed out. ");
                throw new OperationTimeoutException(errorBuilder.toString(), e);
            } else {

                LOGGER.error(
                        "An error has occurred while processing the http response. Occurrence in the process of updating a resource object: {0}",
                        e.getLocalizedMessage());
                LOGGER.info(
                        "An error has occurred while processing the http response. Occurrence in the process of creating a resource object: {0}",
                        e);

                throw new ConnectorIOException(errorBuilder.toString(), e);
            }
        }
        return id;
    }

    @Override
    public List<String> excludeFromAssembly(List<String> excludedAttributes) {

        excludedAttributes.add("schemas");
        excludedAttributes.add(META);
        excludedAttributes.add("alias");

        return excludedAttributes;
    }

    @Override
    public Set<Attribute> attributeInjection(Set<Attribute> injectedAttributeSet, JSONObject loginObject) {

        String orgID = null;

        if (loginObject != null) {
            if (loginObject.has(ID)) {
                orgID = loginObject.getString(ID);
                String idParts[] = orgID.split("\\/");
                orgID = idParts[4];
            }
        } else {

            LOGGER.info("No json object returned after login");
        }
        // injection of organization ID into the set of attributes
        if (orgID != null) {
            LOGGER.info("The organization ID is: {0}", orgID);

            injectedAttributeSet.add(AttributeBuilder.build("schema.type", SCHEMATYPE));

            injectedAttributeSet.add(AttributeBuilder.build("schema.organization", orgID));
        } else {
            LOGGER.warn("No organization ID specified in instance URL");
        }
        return injectedAttributeSet;

    }

    @Override
    public StringBuilder processContainsAllValuesFilter(String p, ContainsAllValuesFilter filter,
            FilterHandler handler) {
        // members

        String attributeName = "";

        String[] keyParts = filter.getName().split("\\."); // eq.
        // members.User.value
        if (keyParts.length == 3) {

            String attributeNamePart = keyParts[0];

            if ("members".equals(attributeNamePart)) {

                attributeName = attributeNamePart;
            }

        }

        if (!attributeName.isEmpty()) {
            List<Object> valueList = filter.getAttribute().getValue();
            Collection<Filter> filterList = new ArrayList<Filter>();

            for (Object value : valueList) {
                Filter containsSingleAtribute = (EqualsFilter) FilterBuilder
                        .equalTo(AttributeBuilder.build(attributeName, value));
                filterList.add(containsSingleAtribute);
            }

            for (Filter f : filterList) {

                return f.accept(new FilterHandler(), p);
            }
        }
        return null;
    }

    @Override
    public AttributeInfoBuilder schemaObjectParametersInjection(AttributeInfoBuilder infoBuilder,
            String attributeName) {

        if (multivaluedAttributes.contains(attributeName)) {
            infoBuilder.setMultiValued(true);
        } /*
            * else if (writableAttributes.contains(attributeName)) {
            * infoBuilder.setUpdateable(true); infoBuilder.setCreateable(true);
            * infoBuilder.setReadable(true); }
            */
        return infoBuilder;
    }

    public void handleBadRequest(String error) {

        List<String> uniqueAttributes = new ArrayList<String>();
        uniqueAttributes.add("invalid_grant");

        String[] parts = error.split("\"");
        for (String part : parts) {
            if (uniqueAttributes.contains(part)) {
                StringBuilder errorBuilder = new StringBuilder("Conflict. ").append(error).append(
                        ". Propably the value you have chosen is already taken, please chose another and try again.");
                throw new InvalidCredentialException(errorBuilder.toString());
            }
        }
        throw new ConnectorException(error);

    }
}