/* * Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. 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.wso2.carbon.identity.provisioning.connector.salesforce; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethodBase; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.wso2.carbon.identity.application.common.model.Property; import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants; import org.wso2.carbon.identity.provisioning.AbstractOutboundProvisioningConnector; import org.wso2.carbon.identity.provisioning.IdentityProvisioningConstants; import org.wso2.carbon.identity.provisioning.IdentityProvisioningException; import org.wso2.carbon.identity.provisioning.ProvisionedIdentifier; import org.wso2.carbon.identity.provisioning.ProvisioningEntity; import org.wso2.carbon.identity.provisioning.ProvisioningEntityType; import org.wso2.carbon.identity.provisioning.ProvisioningOperation; import org.wso2.carbon.registry.core.utils.UUIDGenerator; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; public class SalesforceProvisioningConnector extends AbstractOutboundProvisioningConnector { private static final long serialVersionUID = 8465869197181038416L; private static final Log log = LogFactory.getLog(SalesforceProvisioningConnector.class); private SalesforceProvisioningConnectorConfig configHolder; @Override /** * */ public void init(Property[] provisioningProperties) throws IdentityProvisioningException { Properties configs = new Properties(); if (provisioningProperties != null && provisioningProperties.length > 0) { for (Property property : provisioningProperties) { configs.put(property.getName(), property.getValue()); if (IdentityProvisioningConstants.JIT_PROVISIONING_ENABLED.equals(property .getName()) && "1".equals(property.getValue())) { jitProvisioningEnabled = true; } } } configHolder = new SalesforceProvisioningConnectorConfig(configs); } @Override /** * */ public ProvisionedIdentifier provision(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { String provisionedId = null; if (provisioningEntity != null) { if (provisioningEntity.isJitProvisioning() && !isJitProvisioningEnabled()) { log.debug("JIT provisioning disabled for Salesforce connector"); return null; } if (provisioningEntity.getEntityType() == ProvisioningEntityType.USER) { if (provisioningEntity.getOperation() == ProvisioningOperation.DELETE) { deleteUser(provisioningEntity); } else if (provisioningEntity.getOperation() == ProvisioningOperation.POST) { provisionedId = createUser(provisioningEntity); } else if (provisioningEntity.getOperation() == ProvisioningOperation.PUT) { update(provisioningEntity.getIdentifier().getIdentifier(), buildJsonObject(provisioningEntity)); } else { log.warn("Unsupported provisioning opertaion."); } } else { log.warn("Unsupported provisioning opertaion."); } } // creates a provisioned identifier for the provisioned user. ProvisionedIdentifier identifier = new ProvisionedIdentifier(); identifier.setIdentifier(provisionedId); return identifier; } /** * @param provisioningEntity * @return * @throws IdentityProvisioningException */ private JSONObject buildJsonObject(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); String provisioningPattern = this.configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.PROVISIONING_PATTERN_KEY); if (StringUtils.isBlank(provisioningPattern)) { log.info("Provisioning pattern is not defined, hence using default provisioning pattern"); provisioningPattern = SalesforceConnectorConstants.PropertyConfig.DEFAULT_PROVISIONING_PATTERN; } String provisioningSeparator = this.configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.PROVISIONING_SEPERATOR_KEY); if (StringUtils.isBlank(provisioningSeparator)) { log.info("Provisioning separator is not defined, hence using default provisioning separator"); provisioningSeparator = SalesforceConnectorConstants.PropertyConfig.DEFAULT_PROVISIONING_SEPERATOR; } String idpName = this.configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.IDP_NAME_KEY); JSONObject user = new JSONObject(); try { /** * Mandatory properties : 12 and this will vary according to API Version * * Alias, Email, EmailEncodingKey, LanguageLocaleKey, LastName, LocaleSidKey, ProfileId, * TimeZoneSidKey, User-name, UserPermissionsCallCenterAutoLogin, * UserPermissionsMarketingUser, UserPermissionsOfflineUser **/ Map<String, String> requiredAttributes = getSingleValuedClaims(provisioningEntity .getAttributes()); String userIdClaimURL = this.configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.USER_ID_CLAIM_URI_KEY); String provisioningDomain = this.configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.PROVISIONING_DOMAIN_KEY); String userId = provisioningEntity.getEntityName(); if (StringUtils.isNotBlank(requiredAttributes.get(userIdClaimURL))) { userId = requiredAttributes.get(userIdClaimURL); } String userIdFromPattern = null; if (provisioningPattern != null) { userIdFromPattern = buildUserId(provisioningEntity, provisioningPattern, provisioningSeparator, idpName); } if (StringUtils.isNotBlank(userIdFromPattern)) { userId = userIdFromPattern; } if (StringUtils.isBlank(userId)) { throw new IdentityProvisioningException("Cannot Find Username Attribute for Provisioning"); } if (StringUtils.isNotBlank(provisioningDomain) && !userId.endsWith(provisioningDomain)) { userId = userId.replaceAll("@", ".").concat("@").concat(provisioningDomain); } requiredAttributes.put(SalesforceConnectorConstants.USERNAME_ATTRIBUTE, userId); Iterator<Entry<String, String>> iterator = requiredAttributes.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, String> mapEntry = iterator.next(); if ("true".equals(mapEntry.getValue())) { user.put(mapEntry.getKey(), true); } else if ("false".equals(mapEntry.getValue())) { user.put(mapEntry.getKey(), false); } else { user.put(mapEntry.getKey(), mapEntry.getValue()); } if (isDebugEnabled) { log.debug("The key is: " + mapEntry.getKey() + ",value is :" + mapEntry.getValue()); } } if (isDebugEnabled) { log.debug("JSON object of User\n" + user.toString(2)); } } catch (JSONException e) { log.error("Error while creating JSON body"); throw new IdentityProvisioningException(e); } return user; } /** * @param provisioningEntity * @return * @throws IdentityProvisioningException */ private String createUser(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); HttpClient httpclient = new HttpClient(); JSONObject user = buildJsonObject(provisioningEntity); PostMethod post = new PostMethod(this.getUserObjectEndpoint()); setAuthorizationHeader(post); try { post.setRequestEntity(new StringRequestEntity(user.toString(), SalesforceConnectorConstants.CONTENT_TYPE_APPLICATION_JSON, null)); } catch (UnsupportedEncodingException e) { log.error("Error in encoding provisioning request"); throw new IdentityProvisioningException(e); } String provisionedId = null; try { httpclient.executeMethod(post); if (isDebugEnabled) { log.debug("HTTP status " + post.getStatusCode() + " creating user"); } if (post.getStatusCode() == HttpStatus.SC_CREATED) { JSONObject response = new JSONObject(new JSONTokener(new InputStreamReader( post.getResponseBodyAsStream()))); if (isDebugEnabled) { log.debug("Create response: " + response.toString(2)); } if (response.getBoolean("success")) { provisionedId = response.getString("id"); if (isDebugEnabled) { log.debug("New record id " + provisionedId); } } } else { log.error("recieved response status code :" + post.getStatusCode() + " text : " + post.getStatusText()); if (isDebugEnabled) { log.debug("Error response : " + readResponse(post)); } } } catch (IOException | JSONException e) { throw new IdentityProvisioningException("Error in invoking provisioning operation for the user", e); } finally { post.releaseConnection(); } if (isDebugEnabled) { log.debug("Returning created user's ID : " + provisionedId); } return provisionedId; } private String readResponse(PostMethod post) throws IOException { InputStream is = post.getResponseBodyAsStream(); BufferedReader rd = new BufferedReader(new InputStreamReader(is)); String line; StringBuilder response = new StringBuilder(); while ((line = rd.readLine()) != null) { response.append(line); response.append('\r'); } rd.close(); return response.toString(); } /** * @param provisioningEntity * @throws IdentityProvisioningException */ private void deleteUser(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { JSONObject entity = new JSONObject(); try { entity.put(SalesforceConnectorConstants.IS_ACTIVE, false); entity.put(SalesforceConnectorConstants.USERNAME_ATTRIBUTE, alterUsername(provisioningEntity)); update(provisioningEntity.getIdentifier().getIdentifier(), entity); } catch (JSONException e) { log.error("Error while creating JSON body"); throw new IdentityProvisioningException(e); } } /** * @param provsionedId * @param entity * @return * @throws IdentityProvisioningException */ private void update(String provsionedId, JSONObject entity) throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); try { PostMethod patch = new PostMethod(this.getUserObjectEndpoint() + provsionedId) { @Override public String getName() { return "PATCH"; } }; setAuthorizationHeader(patch); patch.setRequestEntity(new StringRequestEntity(entity.toString(), "application/json", null)); try { HttpClient httpclient = new HttpClient(); httpclient.executeMethod(patch); if (patch.getStatusCode() == HttpStatus.SC_OK || patch.getStatusCode() == HttpStatus.SC_NO_CONTENT) { if (isDebugEnabled) { log.debug("HTTP status " + patch.getStatusCode() + " updating user " + provsionedId + "\n\n"); } } else { log.error("recieved response status code :" + patch.getStatusCode() + " text : " + patch.getStatusText()); if (isDebugEnabled) { log.debug("Error response : " + readResponse(patch)); } } } catch (IOException e) { log.error("Error in invoking provisioning request"); throw new IdentityProvisioningException(e); } finally { patch.releaseConnection(); } } catch (UnsupportedEncodingException e) { log.error("Error in encoding provisioning request"); throw new IdentityProvisioningException(e); } } /** * adding OAuth authorization headers to a httpMethod * * @param httpMethod method which wants to add Authorization header */ private void setAuthorizationHeader(HttpMethodBase httpMethod) throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); String accessToken = authenticate(); if (StringUtils.isNotBlank(accessToken)) { httpMethod.setRequestHeader(SalesforceConnectorConstants.AUTHORIZATION_HEADER_NAME, SalesforceConnectorConstants.AUTHORIZATION_HEADER_OAUTH + " " + accessToken); if (isDebugEnabled) { log.debug("Setting authorization header for method : " + httpMethod.getName() + " as follows,"); Header authorizationHeader = httpMethod .getRequestHeader(SalesforceConnectorConstants.AUTHORIZATION_HEADER_NAME); log.debug(authorizationHeader.getName() + ": " + authorizationHeader.getValue()); } } else { throw new IdentityProvisioningException("Authentication failed"); } } /** * authenticate to salesforce API. */ private String authenticate() throws IdentityProvisioningException { boolean isDebugEnabled = log.isDebugEnabled(); HttpClient httpclient = new HttpClient(); String url = configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.OAUTH2_TOKEN_ENDPOINT); PostMethod post = new PostMethod(StringUtils.isNotBlank(url) ? url : IdentityApplicationConstants.SF_OAUTH2_TOKEN_ENDPOINT); post.addParameter(SalesforceConnectorConstants.CLIENT_ID, configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.CLIENT_ID)); post.addParameter(SalesforceConnectorConstants.CLIENT_SECRET, configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.CLIENT_SECRET)); post.addParameter(SalesforceConnectorConstants.PASSWORD, configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.PASSWORD)); post.addParameter(SalesforceConnectorConstants.GRANT_TYPE, SalesforceConnectorConstants.GRANT_TYPE_PASSWORD); post.addParameter(SalesforceConnectorConstants.USERNAME, configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.USERNAME)); StringBuilder sb = new StringBuilder(); try { // send the request int responseStatus = httpclient.executeMethod(post); if (isDebugEnabled) { log.debug("Authentication to salesforce returned with response code: " + responseStatus); } sb.append("HTTP status " + post.getStatusCode() + " creating user\n\n"); if (post.getStatusCode() == HttpStatus.SC_OK) { JSONObject response = new JSONObject(new JSONTokener(new InputStreamReader( post.getResponseBodyAsStream()))); if (isDebugEnabled) { log.debug("Authenticate response: " + response.toString(2)); } Object attributeValObj = response.opt("access_token"); if (attributeValObj instanceof String) { if (isDebugEnabled) { log.debug("Access token is : " + (String) attributeValObj); } return (String) attributeValObj; } else { log.error("Authentication response type : " + attributeValObj.toString() + " is invalide"); } } else { log.error("recieved response status code :" + post.getStatusCode() + " text : " + post.getStatusText()); } } catch (JSONException | IOException e) { throw new IdentityProvisioningException("Error in decoding response to JSON", e); } finally { post.releaseConnection(); } return ""; } /** * builds salesforce user end point using configurations * * @return */ private String getUserObjectEndpoint() { boolean isDebugEnabled = log.isDebugEnabled(); String url = configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.DOMAIN_NAME) + SalesforceConnectorConstants.CONTEXT_SERVICES_DATA + configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.API_VERSION) + SalesforceConnectorConstants.CONTEXT_SOOBJECTS_USER; if (isDebugEnabled) { log.debug("Built user endpoint url : " + url); } return url; } /** * Builds Salesforce query point using configurations * * @return */ private String getDataQueryEndpoint() { if (log.isTraceEnabled()) { log.trace("Starting getDataQueryEndpoint() of " + SalesforceProvisioningConnector.class); } boolean isDebugEnabled = log.isDebugEnabled(); String url = configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.DOMAIN_NAME) + SalesforceConnectorConstants.CONTEXT_SERVICES_DATA + configHolder.getValue(SalesforceConnectorConstants.PropertyConfig.API_VERSION) + SalesforceConnectorConstants.CONTEXT_QUERY; if (isDebugEnabled) { log.debug("Built query endpoint url : " + url); } return url; } /** * @return * @throws IdentityProvisioningException */ public String listUsers(String query) throws IdentityProvisioningException { if (log.isTraceEnabled()) { log.trace("Starting listUsers() of " + SalesforceProvisioningConnector.class); } boolean isDebugEnabled = log.isDebugEnabled(); if (StringUtils.isBlank(query)) { query = SalesforceConnectorDBQueries.SALESFORCE_LIST_USER_SIMPLE_QUERY; } HttpClient httpclient = new HttpClient(); GetMethod get = new GetMethod(this.getDataQueryEndpoint()); setAuthorizationHeader(get); // set the SOQL as a query param NameValuePair[] params = new NameValuePair[1]; params[0] = new NameValuePair("q", query); get.setQueryString(params); StringBuilder sb = new StringBuilder(); try { httpclient.executeMethod(get); if (get.getStatusCode() == HttpStatus.SC_OK) { JSONObject response = new JSONObject(new JSONTokener(new InputStreamReader( get.getResponseBodyAsStream()))); if (isDebugEnabled) { log.debug("Query response: " + response.toString(2)); } // Build the returning string sb.append(response.getString("totalSize") + " record(s) returned\n\n"); JSONArray results = response.getJSONArray("records"); for (int i = 0; i < results.length(); i++) { sb.append(results.getJSONObject(i).getString("Id") + ", " + results.getJSONObject(i).getString("Alias") + ", " + results.getJSONObject(i).getString("Email") + ", " + results.getJSONObject(i).getString("LastName") + ", " + results.getJSONObject(i).getString("Name") + ", " + results.getJSONObject(i).getString("ProfileId") + ", " + results.getJSONObject(i).getString("Username") + "\n"); } sb.append("\n"); } else { log.error("recieved response status code:" + get.getStatusCode() + " text : " + get.getStatusText()); } } catch (JSONException | IOException e) { log.error("Error in invoking provisioning operation for the user listing"); throw new IdentityProvisioningException(e); }finally { get.releaseConnection(); } if (isDebugEnabled) { log.debug("Returning string : " + sb.toString()); } if (log.isTraceEnabled()) { log.trace("Ending listUsers() of " + SalesforceProvisioningConnector.class); } return sb.toString(); } /** * Alter username while changing user to active state to inactive state. This is necessary when adding previously * deleted users. * * @param provisioningEntity * @return * @throws IdentityProvisioningException */ protected String alterUsername(ProvisioningEntity provisioningEntity) throws IdentityProvisioningException { if (StringUtils.isBlank(provisioningEntity.getEntityName())) { throw new IdentityProvisioningException("Could Not Find Entity Name from Provisioning Entity"); } String alteredUsername = SalesforceConnectorConstants.SALESFORCE_OLD_USERNAME_PREFIX + UUIDGenerator.generateUUID() + provisioningEntity.getEntityName(); if (log.isDebugEnabled()) { log.debug("Alter username: " + provisioningEntity.getEntityName() + " to: " + alteredUsername + "while deleting user"); } return alteredUsername; } }