How to Configure Infinipoint for Salesforce

Overview

To Configure Infinipoint (OIDC) for Salesforce you will need to copy and paste data from the Infinipoint console to a new Salesforce Auth Provider page.

The instructions are for configuring OpenID Connect 1.0 for Salesforce in Brokering Mode.

Please make sure to configure “Brokering Mode” and set your identity provider

Infinipoint Configuration

  1. At the Infinipoint console, navigate to Settings > Applications.

  2. Click the ‘+', and select “Add from Catalog”.

  3. Select “Salesforce” from the catalog, and click Next to create an application.

  4. At the new window, enter the following:

    1. At the Application Name enter Salesforce (The Infinipoint console displays the name for the application of your choosing)

    2. At the Client ID enter salesforce (or one of your choosing)

    3. Keep this window open so that you can eventually enter the Redirect URI from the Salesforce console.

Salesforce Configuration

  1. To create a new “Auth Provider” in Salesforce, navigate the Salesforce console to Setup > Settings > Identity > Auth Providers.

  2. Click New to create a new Auth Provider and provide the following properties:

    1. Name: “Infinipoint” (This name to be presented to users when logging into Salesforce)

    2. URL Suffix: “infinipoint”

  3. Copy and paste the following properties from the Infinipoint console:

    1. Copy and paste the Client ID from the Infinipoint console at the Consumer Key section.

    2. Copy and paste the Client Secret from the Infinipoint console at the Consumer Secret section.

    3. Authorize Endpoint URL

    4. Token Endpoint URL

    5. User Info Endpoint URL

    6. For the Registration Handler, use the Apex Class below.

    7. For the Execute Registration As assign one of the admins or other appropriate role user.

  4. Copy the “Callback URL” from Salesforce to the “Redirect URI” field in the Infinipoint Salesforce application.

  5. Navigate to Setup > Settings > Company Settings > My Domain

    1. Click Edit by the Authentication Configuration

    2. Select your newly created Auth Provider as an Authentication Service

Salesforce Registration Handler

global class InfinipointRegistrationHandlerV2 implements Auth.RegistrationHandler { class RegHandlerException extends Exception {} // Authenticate the user global User createUser(Id portalId, Auth.UserData data) { User u; List<User> userList = getMatchingUsers(data); // If the account exists just return the existing user // otherwise we create a new user or fail with a nice message if (userList.size() > 0){ String userId = userList.get(0).id; u = new User(id = userId); prepareUserData(data, u); return u; } else { throw new RegHandlerException('Can\'t create a new user or link an existing user to Infinipoint ID either because it is not allowed or the user was not found.'); } } // Update the user // This is called by salesforce if the user exists global void updateUser(Id userId, Id portalId, Auth.UserData data) { return; } private void prepareUserData(Auth.UserData data, User u) { String givenName, lastName, email, alias; // Get email if (data.attributeMap.containsKey('email')){ email = data.attributeMap.get('email'); u.email = email; } // Get first name if (data.attributeMap.containsKey('given_name')) { givenName = data.attributeMap.get('given_name'); u.firstName = givenName; } // Get last name if (data.attributeMap.containsKey('family_name')) { lastName = data.attributeMap.get('family_name'); u.lastName = lastName; } // Create alias - must be 8 characters or less // First names are more common than last names so, use lastname first // to avoid multiple aliases starting from the same chars. alias = lastName + givenName; if (alias.length() > 8) { u.alias = alias.toLowerCase().substring(0, 8); } // Get phoneNumber // If non existing then it will return null. String phoneNumber = data.attributeMap.get('phone_number'); // Set then phone number attribute based on phone type. // Because we don't know how null values are handled we first check for // the phone_number key existence and then set the correct attribute to the user. // If it does not exist, just skip it. if (data.attributeMap.containsKey('phone_number')) { if (data.attributeMap.get('phone_type') == 'mobile') { u.mobilePhone = phoneNumber; } else { u.phone = phoneNumber; } } // Get address //if (data.attributeMap.containsKey('address')) { // Map<String, String> addressFields = parseMalformedNestedJSON(data.attributeMap.get('address')); // u.street = addressFields.get('street_address'); // u.city = addressFields.get('locality'); // u.state = addressFields.get('state'); // u.country = addressFields.get('country'); // u.postalCode = addressFields.get('postal_code'); //} // Get locale //if (data.attributeMap.containsKey('locale')) { // String locale = data.attributeMap.get('locale').replace('-','_'); // u.localeSidKey = locale; // u.languageLocaleKey = locale; //} else { // u.localeSidKey = UserInfo.getLocale(); // u.languageLocaleKey = UserInfo.getLocale(); //} // Get zoneinfo //if (data.attributeMap.containsKey('zoneinfo')) { // u.timeZoneSidKey = data.attributeMap.get('zoneinfo'); //} // Set email encoding key to 'UTF-8' //u.emailEncodingKey = 'UTF-8'; } // Try to get at least one matching, existing and active user for account the linking case // or for the usage in a new computer / new browser. In the second case, this should trigger a verification email or sms to the user. private List<User> getMatchingUsers(Auth.UserData data) { // This is the Subject ID. In this case we have a public type and not a pairwise one. // Find more about public and pairwise Subject ids here. // http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes // We could also do data.identifier. String sub = data.attributeMap.get('sub'); List<ThirdPartyAccountLink> matchedAccounts = [SELECT userId, remoteIdentifier FROM ThirdPartyAccountLink where RemoteIdentifier =: sub]; // Normally we should get only one entry in the accountsList. List<User> userList; // If there is indeed one or more accounts found then we have a linked account already, so check for the first userid and the email, // and return the matching user of the first account. // Else we have an account linking case, where we need to find a matching user by the data provided by Infinipoint. // Check for the account by email (which in an organization probably is usually unique) and for being active (could we check for more?) if (matchedAccounts.size() > 0) { userList = [SELECT id FROM User WHERE id =: matchedAccounts.get(0).userId AND email =: data.email AND userType <>: 'Guest' AND isActive = true]; } else { userList = [SELECT id FROM User WHERE email =: data.email AND userType <>: 'Guest' AND isActive = true]; } return userList; } // Salesforce does not play well with nested jsons. // Instead of getting a JSON (e.g. for address) we get something like the following // {country=country, street_address=address, formatted=address\nlocality..., locality=locality, state=state, postalcode=postalcode}. // So, get the string inside the curly brackets and split by comma to get the array of values. // Then add these to a Map<String, String> by splitting each value at the first and the last '=' and trimming. // This is not perfectly safe as the user may pass commas and equal signs in the values. // It does not break things completely but also does not guarantee consistency on values that contain these characters. /*private Map<String, String> parseMalformedNestedJSON(String value) { String[] arrayOfValues = value.substring(value.indexOf('{') + 1, value.indexOf('}') - 1).split(','); //TODO: Q: wouldnt this fail if there are commas in the values? Map<String, String> fields = new Map<String, String>(); for (String item : arrayOfValues) { String k = item.substringBefore('=').trim(); String v = item.substringAfterLast('=').trim(); fields.put(k, v); } return fields; }*/ }

Salesforce Registration Handler Test

@isTest private class InfinipointRegistrationHandlerTestV2 { static testMethod void testCreateAndUpdateUser() { InfinipointRegistrationHandlerV2 handler = new InfinipointRegistrationHandlerV2(); Auth.UserData sampleData = new Auth.UserData(null, 'testFirst', 'Doe', 'testFirst Doe', 'email@your_org.com', null, 'testuserlong', 'en_US', 'facebook', null, new Map<String, String>{ 'language' => 'en_US', 'family_name' => 'Doe', 'email' => 'JohnDoe@your_org.com', 'email_verified' => 'true', 'given_name' => 'John', 'phone_number' => '123'}); User u = handler.createUser(null, sampleData); System.assertEquals('email@your_org.com', u.email); System.assertEquals('Doe', u.lastName); String uid = u.id; sampleData = new Auth.UserData('testNewId', 'testNewFirst', 'testNewLast', 'testNewFirst testNewLast', 'email@your_org.com', null, 'testnewuserlong', 'en_US', 'test', null, new Map<String, String>{'email_verified' => 'true'}); handler.updateUser(uid, null, sampleData); } }