You need to sign in to do that
Don't have an account?
How do you make single sign on to your composite app secure?
However, the point of single-sign-on is to allow a second application (in this case, us) to effectively "automatically log the user in" based on trusting a primary application (in this case, salesforce) to assert that a user is authenticated as a particular principal.
I don't see how the tokens passed to us allow us to assure that salesforce has authenticated the user indicated in a secure way. As stated above, we are going to log them in to our application as that user and give them full access to their data - so we have to know that it is really them, as asserted by salesforce, but in an environment where any machine or person can make arbitrary HTTP requests to our site (since we are not deployed behind a proxy at salesforce, etc.).
For a concrete example, imagine that a bank is signing up to become an AppExchange partner. They will offer account balances and wire transfers as a tab in salesforce. On their side, they would do some work to associate a salesforce login name (e.g. bob@companyname.com) with a bank account number or social security number. Obviously, security is critical - when the tab is clicked on and the accounts page is displayed, it better be the current salesforce user's data!
Now, when salesforce's web tab passes the username, session id, and server url to the bank's web site, the bank has to be able to validate these tokens and "trust" that salesforce has securely authenticated the user. Once it does that, it can then show the data from the external banking system, and allow operations on that data such as wire transfers. I know of three methods to do this - one is to use private/public keys to encode the tokens - another is to have the second application make a return call to the originator of the tokens (similar to a kerberos model) - and a third is to put the second server behind a proxy at the same site as the first server (which is not feasible in this case).
Given salesforce's approach, what steps should the bank's application take to validate the tokens? The username is what indicates the account to use. However, anybody can make an HTTP request and pass a username - so it's not secure to simply check that. The session id is interesting, but to prove that it is valid you would have to go back to a salesforce server (i.e. the token originator) to validate that is in active session. What server serves as the token originator - perhaps that is what the server URL parameter is for? That gave me the idea that you can use the server URL combined with the session id to verify that the session is valid on that salesforce server. (However, in this case - how do we make sure that the URL isn't pointing to some non-salesforce server designed to provide bogus validation?) Once we've determined that the session is indeed valid on a salesforce server, we know that there has been an authenticated session. However, how do we know it is for the user passed on the URL? Somebody could just as easily authenticate with the SForce API to salesforce.com, get a valid session token and server URL, and then pass that to our application along with a completely different username.
I looked into the getUserInfo call. It returns a GetUserInfoResult (I'm accessing this using the Java bindings). That object does not seem to contain the user name - at least not the name that is passed by the single-sign-on recommendation. I see the user full name, the user id, etc., but nothing to compare to the token passed on the URL (the login name).
I pretty much have this coded up but am running into an error. My approach was this - grab the server url, session id, and username from the request parameters. Create a SoapBindingStub using the url, set the session id into a _SessionHeader, and then call getUserInfo. Once getUserInfo returns, it'll give me the user's ID but not the user name. However, I can then make a query against the User object given the id and get the user name, which I can then compare to the username passed on the URL. If that all works fine, then I can trust the authentication.
//set the sessionId property on the header object using
sh.setSessionId(sessionId);
binding.setHeader("SforceService", "SessionHeader", sh);
Simon,
I mis-spoke - if I don't pass the URL to the SoapBindingStub, it still doesn't work, but I get a different exception on the getUserInfo call:
UNKNOWN_EXCEPTION: Destination URL not reset. The URL returned from login must be set in the SforceService
This is interesting - I don't make a login call, because I'm reusing an existing session token by setting it in the header. I'm not sure what this means I ought to do...
FWIW, the URL on the SoapBindingStub is this when I don't pass it a URL:
https://www.salesforce.com/services/Soap/c/2.5
The URL I'm passed from the web tab is this:
https://na1.salesforce.com/services/Soap/u/7.0
Does that make a difference? I'm not sure if the problem is incompatible client bindings (and if so, how to we guarantee forward compatibility once we do have the right version? We'll always need to connect to the URL passed by the web tab, correct?) or if it I'm just putting the sesion token in the header improperly...or if there is more to it.
Sorry if I'm beeing slow....I'm fairly new to the SForce API.
Dan
When you setup the webtab, you have to pick a version of the API for the merge field, that version # and the version of the WSDL you're using need to match. (I'd recommend downloading the current WSDL, and going forward with v7).
I know you recommend starting from the WSDL - but I downloaded the latest quickstart and now have the 7.0 Java bindings. Easier for me to get going (...but if somebody wants to show me how to go from WSDL-to-client-bindings in Java/Eclipse 3.1 as easy as you can with Visual Studio .NET, that would be appreciated.).
org.xml.sax.SAXException: Invalid element in com.sforce.soap.enterprise.sobject.SObject - type
String server = (String)request.getParameter("server");
String user = (String)request.getParameter("username");
String session = (String)request.getParameter("session");
if (server == null || user == null || session == null) {
return null;
}
// make sure the server is a salesforce.com server
if (!server.startsWith("https://") || server.indexOf("/", 8) < 0) {
return null; // not https or not valid path after server
}
if (!server.substring(0, server.indexOf("/", 8)).endsWith("salesforce.com")) {
return null; // not a salesforce.com server!
}
try {
SoapBindingStub binding = (SoapBindingStub)new SforceServiceLocator().getSoap(new URL(server));
//create a session head object
binding._setProperty(SoapBindingStub.ENDPOINT_ADDRESS_PROPERTY, server);
SessionHeader sh = new SessionHeader();
// set the sessionId property on the header object using
sh.setSessionId(session);
// add the header to the binding stub
String sforceURI = new SforceServiceLocator().getServiceName().getNamespaceURI();
binding.setHeader(sforceURI, "SessionHeader", sh);
// get the current user id
GetUserInfoResult userInfo = binding.getUserInfo();
if (userInfo != null) {
// get the profile id - then query and see if the username matches
QueryResult qrPerson = binding.query("select Username from User"); // where id = '" + userInfo.getUserId() + "'");
I thought there was a WSDL2Java eclipse plug-in now for axis.
http://my.server.com/saleforce?serverUrl={!API_Partner_Server_URL_70}&sid={!API_Session_ID}&username={!User_Username}
what you want is
http://my.server.com/saleforce?serverUrl={!API_Enterprise_Server_URL_70}&sid={!API_Session_ID}&username={!User_Username}
String server = (String)request.getParameter("server");
String user = (String)request.getParameter("username");
String session = (String)request.getParameter("session");
if (server == null || user == null || session == null) {
return null;
}
// make sure the server is a salesforce.com server
if (!server.startsWith("https://") || server.indexOf("/", 8) < 0) {
return null; // not https or not valid path after server
}
if (!server.substring(0, server.indexOf("/", 8)).endsWith("salesforce.com")) {
return null; // not a salesforce.com server!
}
try {
SoapBindingStub binding = (SoapBindingStub)new SforceServiceLocator().getSoap(new URL(server));
//create a session head object
binding._setProperty(SoapBindingStub.ENDPOINT_ADDRESS_PROPERTY, server);
SessionHeader sh = new SessionHeader();
// set the sessionId property on the header object using
sh.setSessionId(session);
// add the header to the binding stub
String sforceURI = new SforceServiceLocator().getServiceName().getNamespaceURI();
binding.setHeader(sforceURI, "SessionHeader", sh);
// get the current user id
GetUserInfoResult userInfo = binding.getUserInfo();
if (userInfo != null) {
QueryResult qrPerson = binding.query("select Username from User where id = '" + userInfo.getUserId() + "'");
SObject [] person = (SObject [])qrPerson.getRecords();
if (person != null && person.length == 1) {
if (((User)person[0]).getUsername().equals(user)) {
return user; // valid - session is for this user
}
}
}
} catch (Exception err) {
return null;
}
return null;
}
Message Edited by pathworks on 03-28-2006 02:22 PM