- January 6, 2016
- Posted by: Scaleable Solutions
- Category: Dynamics CRM, Mobile
In our previous post, we covered how to connect iOS apps with Dynamics CRM using SOAP endpoint. In this blog post, we will look at how to connect android apps with Dynamics CRM using SOAP endpoint.
Application
The app takes Dynamics CRM online URL, username and password and returns the full name of the logged in user. The final application looks like below
Note: The complete code of the application is hosted on github.
Working
CRMAuth
This class creates and returns CRM Authentication SOAP Envelope and SOAP Header. CRM Authentication SOAP Envelope is used to authenticate user.
public class CRMAuth { public String GetAuthEnvelopeOnline(String url, String username, String password){ String urnAddress = GetUrnOnline(url); Date now = new Date(); StringBuilder xml = new StringBuilder(); xml.append("<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://www.w3.org/2005/08/addressing\" xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">"); xml.append("<s:Header>"); xml.append("<a:Action s:mustUnderstand=\"1\">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>"); xml.append("<a:MessageID>urn:uuid:" + java.util.UUID.randomUUID() + "</a:MessageID>"); xml.append("<a:ReplyTo>"); xml.append("<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>"); xml.append("</a:ReplyTo>"); xml.append("<a:To s:mustUnderstand=\"1\">https://login.microsoftonline.com/RST2.srf</a:To>"); xml.append("<o:Security s:mustUnderstand=\"1\" xmlns:o=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">"); xml.append("<u:Timestamp u:Id=\"_0\">"); xml.append("<u:Created>" + String.format("%tFT%<tT.%<tLZ", now) + "</u:Created>"); xml.append("<u:Expires>" + String.format("%tFT%<tT.%<tLZ", AddMinutes(60, now)) + "</u:Expires>"); xml.append("</u:Timestamp>"); xml.append("<o:UsernameToken u:Id=\"uuid-" + java.util.UUID.randomUUID() + "-1\">"); xml.append("<o:Username>" + username + "</o:Username>"); xml.append("<o:Password>" + password + "</o:Password>"); xml.append("</o:UsernameToken>"); xml.append("</o:Security>"); xml.append("</s:Header>"); xml.append("<s:Body>"); xml.append("<trust:RequestSecurityToken xmlns:trust=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">"); xml.append("<wsp:AppliesTo xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\">"); xml.append("<a:EndpointReference>"); xml.append("<a:Address>urn:" + urnAddress + "</a:Address>"); xml.append("</a:EndpointReference>"); xml.append("</wsp:AppliesTo>"); xml.append("<trust:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</trust:RequestType>"); xml.append("</trust:RequestSecurityToken>"); xml.append("</s:Body>"); xml.append("</s:Envelope>"); return xml.toString(); } public String createSOAPHeader(String url,String keyIdentifier, String token1, String token2){ StringBuilder xml = new StringBuilder(); xml.append("<s:Header>"); xml.append("<a:Action s:mustUnderstand=\"1\">http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute</a:Action>"); xml.append("<Security xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">"); xml.append("<EncryptedData Id=\"Assertion0\" Type=\"http://www.w3.org/2001/04/xmlenc#Element\" xmlns=\"http://www.w3.org/2001/04/xmlenc#\">"); xml.append("<EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#tripledes-cbc\"/>"); xml.append("<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">"); xml.append("<EncryptedKey>"); xml.append("<EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"/>"); xml.append("<ds:KeyInfo Id=\"keyinfo\">"); xml.append("<wsse:SecurityTokenReference xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">"); xml.append("<wsse:KeyIdentifier EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\" ValueType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier\">" + keyIdentifier + "</wsse:KeyIdentifier>"); xml.append("</wsse:SecurityTokenReference>"); xml.append("</ds:KeyInfo>"); xml.append("<CipherData>"); xml.append("<CipherValue>" + token1 + "</CipherValue>"); xml.append("</CipherData>"); xml.append("</EncryptedKey>"); xml.append("</ds:KeyInfo>"); xml.append("<CipherData>"); xml.append("<CipherValue>" + token2 + "</CipherValue>"); xml.append("</CipherData>"); xml.append("</EncryptedData>"); xml.append("</Security>"); xml.append("<a:MessageID>urn:uuid:" + java.util.UUID.randomUUID() + "</a:MessageID>"); xml.append("<a:ReplyTo>"); xml.append("<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>"); xml.append("</a:ReplyTo>"); xml.append("<a:To s:mustUnderstand=\"1\">" + url + "/XRMServices/2011/Organization.svc</a:To>"); xml.append("</s:Header>"); return xml.toString(); } private String GetUrnOnline(String url) { if (url.toUpperCase().contains("CRM2.DYNAMICS.COM")) return "crmsam:dynamics.com"; if (url.toUpperCase().contains("CRM4.DYNAMICS.COM")) return "crmemea:dynamics.com"; if (url.toUpperCase().contains("CRM5.DYNAMICS.COM")) return "crmapac:dynamics.com"; if (url.toUpperCase().contains("CRM6.DYNAMICS.COM")) return "crmoce:dynamics.com"; if (url.toUpperCase().contains("CRM7.DYNAMICS.COM")) return "crmjpn:dynamics.com"; if (url.toUpperCase().contains("CRM8.DYNAMICS.COM")) return "crmgcc:dynamics.com"; return "crmna:dynamics.com"; } private Date AddMinutes(int minutes, Date time) { long ONE_MINUTE_IN_MILLIS = 60000; long currentTime = time.getTime(); Date newDate = new Date(currentTime + (minutes * ONE_MINUTE_IN_MILLIS)); return newDate; } }
CRMAuthHeader
This is a container class for CRMAuth returned values.
public class CRMAuthHeader { private String header; private Date Expire; public CRMAuthHeader() { } public CRMAuthHeader(String header, Date expire) { this.header = header; Expire = expire; } public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } public Date getExpire() { return Expire; } public void setExpire(Date expire) { Expire = expire; } }
CRMAuthenticationTask
This class executes SOAP Envelope from CRMAuth, takes organization URL, username and password and returns CRMAuthHeader object with following values:
- SOAP Header
- Expire Time
First is used to authenticate user as it is sent with every SOAP Request and Expire time is used to determine when the authentication token will expire.
public class CRMAuthenticationTask extends AsyncTask<String, Void, CRMAuthHeader> { public interface CRMAuthResponse { void processFinish(CRMAuthHeader output); void credentialError(String error); } Context context; CRMAuthResponse delegate = null; public CRMAuthenticationTask(CRMAuthResponse delegate,Context context) { this.delegate = delegate; this.context = context; } @Override protected CRMAuthHeader doInBackground(String... params) { if (params.length != 0 && !params[0].contentEquals("") && !params[1].contentEquals("") && !params[2].contentEquals("")) { ServiceHandler serviceHandler = new ServiceHandler(); return serviceHandler.getAuthHeader(params[0], params[1], params[2]); } else { return null; } } @Override protected void onPostExecute(CRMAuthHeader crmAuthHeader) { if (crmAuthHeader != null) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.edit().putString("soap_header", crmAuthHeader.getHeader()).apply(); prefs.edit().putString("expire", crmAuthHeader.getExpire().toString()).apply(); delegate.processFinish(crmAuthHeader); } else { delegate.credentialError("Please provide Credentials"); } } }
WhoAmITask
This class contains and executes WhoAmIRequest and returns logged in UserId.
public class WhoAmITask extends AsyncTask<Void,Void,String> { public interface WhoAmIResponse{ void whoAmIProcessFinish(String id); } WhoAmIResponse delegate = null; LocalStorage localStorage; public WhoAmITask(WhoAmIResponse delegate, Context context) { this.delegate = delegate; this.localStorage = new LocalStorage(context); } @Override protected String doInBackground(Void... params) { ServiceHandler handler = new ServiceHandler(); return handler.getUserId(localStorage.getOrganizationURL(), localStorage.getCRMAuthHeader(),WhoAmIBody()); } @Override protected void onPostExecute(String s) { delegate.whoAmIProcessFinish(s); } private String WhoAmIBody() { StringBuilder xml = new StringBuilder(); xml.append("<s:Body>"); xml.append("<Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\">"); xml.append("<request i:type=\"c:WhoAmIRequest\" xmlns:b=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:c=\"http://schemas.microsoft.com/crm/2011/Contracts\">"); xml.append("<b:Parameters xmlns:d=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\"/>"); xml.append("<b:RequestId i:nil=\"true\"/>"); xml.append("<b:RequestName>WhoAmI</b:RequestName>"); xml.append("</request>"); xml.append("</Execute>"); xml.append("</s:Body>"); return xml.toString(); } }
UserInfoTask
This class takes logged in UserId and returns user’s full name.
public class UserInfoTask extends AsyncTask<String, Void, String> { public interface UserInfoResponse { void userInfoProcessFinish(String fullName); } UserInfoResponse delegate = null; LocalStorage localStorage; public UserInfoTask(UserInfoResponse delegate, Context context) { this.delegate = delegate; this.localStorage = new LocalStorage(context); } @Override protected String doInBackground(String... params) { ServiceHandler handler = new ServiceHandler(); return handler.getUserInfo(localStorage.getOrganizationURL(), localStorage.getCRMAuthHeader(), UserInfoBody(params[0])); } @Override protected void onPostExecute(String s) { delegate.userInfoProcessFinish(s); } StringBuilder xml = new StringBuilder(); xml.append("<s:Body>"); xml.append("<Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">"); xml.append("<request i:type=\"a:RetrieveRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">"); xml.append("<a:Parameters xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">"); xml.append("<a:KeyValuePairOfstringanyType>"); xml.append("<b:key>Target</b:key>"); xml.append("<b:value i:type=\"a:EntityReference\">"); xml.append("<a:Id>" + id + "</a:Id>"); xml.append("<a:LogicalName>systemuser</a:LogicalName>"); xml.append("<a:Name i:nil=\"true\" />"); xml.append("</b:value>"); xml.append("</a:KeyValuePairOfstringanyType>"); xml.append("<a:KeyValuePairOfstringanyType>"); xml.append("<b:key>ColumnSet</b:key>"); xml.append("<b:value i:type=\"a:ColumnSet\">"); xml.append("<a:AllColumns>false</a:AllColumns>"); xml.append("<a:Columns xmlns:c=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\">"); xml.append("<c:string>firstname</c:string>"); xml.append("<c:string>lastname</c:string>"); xml.append("</a:Columns>"); xml.append("</b:value>"); xml.append("</a:KeyValuePairOfstringanyType>"); xml.append("</a:Parameters>"); xml.append("<a:RequestId i:nil=\"true\" />"); xml.append("<a:RequestName>Retrieve</a:RequestName>"); xml.append("</request>"); xml.append("</Execute>"); xml.append("</s:Body>"); return xml.toString(); }
Conclusion
The given sample is great starter app to perform Dynamics CRM authentication in android apps. It gives a good overview of how you can make connection with Dynamics CRM from external apps not developed in .NET. You can freely use this sample in your apps for authentication purposes.