/* * Copyright (c) 2016, salesforce.com, inc. All rights reserved. Licensed under the BSD 3-Clause license. For full * license text, see LICENSE.TXT file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ package com.salesforce.emp.connector; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; import java.net.ConnectException; import java.net.URL; import java.nio.ByteBuffer; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.util.ByteBufferContentProvider; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; /** * A helper to obtain the Authentication bearer token via login * * @author hal.hildebrand * @since API v37.0 */ public class LoginHelper { private static class LoginResponseParser extends DefaultHandler { private String buffer; private String faultstring; private boolean reading = false; private String serverUrl; private String sessionId; @Override public void characters(char[] ch, int start, int length) { if (reading) buffer = new String(ch, start, length); } @Override public void endElement(String uri, String localName, String qName) { reading = false; switch (localName) { case "sessionId": sessionId = buffer; break; case "serverUrl": serverUrl = buffer; break; case "faultstring": faultstring = buffer; break; default: } buffer = null; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { switch (localName) { case "sessionId": reading = true; break; case "serverUrl": reading = true; break; case "faultstring": reading = true; break; default: } } } public static final String COMETD_REPLAY = "/cometd/"; public static final String COMETD_REPLAY_OLD = "/cometd/replay/"; static final String LOGIN_ENDPOINT = "https://login.salesforce.com"; private static final String ENV_END = "</soapenv:Body></soapenv:Envelope>"; private static final String ENV_START = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' " + "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " + "xmlns:urn='urn:partner.soap.sforce.com'><soapenv:Body>"; // The enterprise SOAP API endpoint used for the login call private static final String SERVICES_SOAP_PARTNER_ENDPOINT = "/services/Soap/u/44.0/"; public static BayeuxParameters login(String username, String password) throws Exception { return login(new URL(LOGIN_ENDPOINT), username, password); } public static BayeuxParameters login(String username, String password, BayeuxParameters params) throws Exception { return login(new URL(LOGIN_ENDPOINT), username, password, params); } public static BayeuxParameters login(URL loginEndpoint, String username, String password) throws Exception { return login(loginEndpoint, username, password, new BayeuxParameters() { @Override public String bearerToken() { throw new IllegalStateException("Have not authenticated"); } @Override public URL endpoint() { throw new IllegalStateException("Have not established replay endpoint"); } }); } public static BayeuxParameters login(URL loginEndpoint, String username, String password, BayeuxParameters parameters) throws Exception { HttpClient client = new HttpClient(parameters.sslContextFactory()); try { client.getProxyConfiguration().getProxies().addAll(parameters.proxies()); client.start(); URL endpoint = new URL(loginEndpoint, getSoapUri()); Request post = client.POST(endpoint.toURI()); post.content(new ByteBufferContentProvider("text/xml", ByteBuffer.wrap(soapXmlForLogin(username, password)))); post.header("SOAPAction", "''"); post.header("PrettyPrint", "Yes"); ContentResponse response = post.send(); SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setFeature("http://xml.org/sax/features/external-general-entities", false); spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); spf.setNamespaceAware(true); SAXParser saxParser = spf.newSAXParser(); LoginResponseParser parser = new LoginResponseParser(); saxParser.parse(new ByteArrayInputStream(response.getContent()), parser); String sessionId = parser.sessionId; if (sessionId == null || parser.serverUrl == null) { throw new ConnectException( String.format("Unable to login: %s", parser.faultstring)); } URL soapEndpoint = new URL(parser.serverUrl); String cometdEndpoint = Float.parseFloat(parameters.version()) < 37 ? COMETD_REPLAY_OLD : COMETD_REPLAY; URL replayEndpoint = new URL(soapEndpoint.getProtocol(), soapEndpoint.getHost(), soapEndpoint.getPort(), new StringBuilder().append(cometdEndpoint).append(parameters.version()).toString()); return new DelegatingBayeuxParameters(parameters) { @Override public String bearerToken() { return sessionId; } @Override public URL endpoint() { return replayEndpoint; } }; } finally { client.stop(); client.destroy(); } } private static String getSoapUri() { return SERVICES_SOAP_PARTNER_ENDPOINT; } private static byte[] soapXmlForLogin(String username, String password) throws UnsupportedEncodingException { return (ENV_START + " <urn:login>" + " <urn:username>" + username + "</urn:username>" + " <urn:password>" + password + "</urn:password>" + " </urn:login>" + ENV_END).getBytes("UTF-8"); } }