Dynamics Light Seeing the (Silver)Light effect in Dynamics CRM

11Apr/106

MSCRM Development Tips & Tricks #3 – Connecting to MSCRM Online

Connecting to Microsoft CRM Online involves acquiring the Windows Live Id ticket which then used to authenticate against Microsoft CRM Online. As a result, Microsoft CRM will give you back a CrmTicket value which you can use throughout your session to query / modify CRM data.

There is a sample on how to achieve this using Passport authentication in MSCRM SDK. This approach uses the IdClrWrapper.dll to get the Windows Live Id ticket, which internally executes an unmanaged code. While this works great in our Full trust development machine, it failed miserably in shared hosting environment which runs Medium or High trust level.

What I wanted to share today is an alternative way to get the Windows Live Id ticket that works virtually on any environment. :) This method uses the RPS (Relying Party Suites) authentication option for Windows Live.

You are not just limited to use this LiveIdTicketAcquirer class for MSCRM Online, but you can also use it for other Windows Live Service offerings like Live Contacts, Live Mesh, etc.

using System;
using System.IO;
using System.Net;
using System.Xml;
 
namespace TTprotons.Crm.ExternalView.Service.Crm
{
    /// <summary>
    /// Handles the retrieval of Live Id ticket.
    /// </summary>
    public class LiveIdTicketAcquirer
    {
        #region SoapMarkUp
 
        private const string applicationId = "10"; // An arbitrary value that will be defined in the next non-alpha release
 
        private const string soapEnvelopeTemplate = @"<s:Envelope
            xmlns:s = ""http://www.w3.org/2003/05/soap-envelope""
            xmlns:wsse = ""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd""
            xmlns:saml = ""urn:oasis:names:tc:SAML:1.0:assertion""
            xmlns:wsp = ""http://schemas.xmlsoap.org/ws/2004/09/policy""
            xmlns:wsu = ""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd""
            xmlns:wsa = ""http://www.w3.org/2005/08/addressing""
            xmlns:wssc = ""http://schemas.xmlsoap.org/ws/2005/02/sc""
            xmlns:wst = ""http://schemas.xmlsoap.org/ws/2005/02/trust"">
            <s:Header>
                <wlid:ClientInfo xmlns:wlid = ""http://schemas.microsoft.com/wlid"">
                    <wlid:ApplicationID>" + applicationId + @"</wlid:ApplicationID>
                </wlid:ClientInfo>
                <wsa:Action s:mustUnderstand = ""1"">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>
                <wsa:To s:mustUnderstand = ""1"">{4:url}</wsa:To>
                <wsse:Security>
                    <wsse:UsernameToken wsu:Id = ""user"">
                        <wsse:Username>{0:userName}</wsse:Username>
                        <wsse:Password>{1:passWord}</wsse:Password>
                    </wsse:UsernameToken>
                </wsse:Security>
            </s:Header>
            <s:Body>
            <!--
                <wst:RequestSecurityToken Id = ""RST0"">
                    <wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>
                    <wsp:AppliesTo>
                        <wsa:EndpointReference>
                            <wsa:Address>http://live-int.com</wsa:Address>
                        </wsa:EndpointReference>
                    </wsp:AppliesTo>
                    <wsp:PolicyReference URI = ""MBI""></wsp:PolicyReference>
                </wst:RequestSecurityToken>
            -->
                <wst:RequestSecurityToken Id = ""RST1"">
                    <wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>
                    <wsp:AppliesTo>
                        <wsa:EndpointReference>
                            <wsa:Address>{2:partner}</wsa:Address>
                        </wsa:EndpointReference>
                    </wsp:AppliesTo>
                    <wsp:PolicyReference URI = ""{3:policy}""></wsp:PolicyReference>
                </wst:RequestSecurityToken>
            </s:Body>
        </s:Envelope>";
 
        #endregion
 
        /// <summary>
        /// Gets a WLID ticket for a specified user
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="partner">sitename, i.e. crmapp.www.local-titan.com</param>
        /// <param name="policy">auth policy, i.e. MBI_SSL</param>
        /// <param name="environment">Wlid environment, i.e. INT</param>
        /// <param name="requestTimeoutSeconds">time in seconds the request waits for response</param>
        /// <returns></returns>
        public string GetWlidTicket(string userName, string password, string partner, string policy, string environment, int requestTimeoutSeconds)
        {
            string url;
            switch (environment)
            {
                case "INT":
                    url = @"https://dev.login.live-int.com/wstlogin.srf";
                    break;
                case "PPE":
                    url = @"https://dev.login.live-ppe.com/wstlogin.srf";
                    break;
                case "":
                case null:
                case "PROD":
                    url = @"https://dev.login.live.com/wstlogin.srf";
                    break;
                default:
                    throw new ArgumentException("environment is not valid", "environment");
            }
 
            var request = WebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/soap+xml; charset=UTF-8";
            request.Timeout = requestTimeoutSeconds * 1000;
 
            string soapEnvelope = string.Format(soapEnvelopeTemplate, userName, password, partner, policy, url);
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(soapEnvelope);
            using (var str = request.GetRequestStream())
            {
                str.Write(bytes, 0, bytes.Length);
                str.Close();
            }
 
            string xml;
            using (var response = request.GetResponse())
            {
                using (var reader = new StreamReader(response.GetResponseStream()))
                    xml = reader.ReadToEnd();
 
                response.Close();
            }
 
            var document = new XmlDocument();
            document.LoadXml(xml);
 
            var nsManager = new XmlNamespaceManager(document.NameTable);
            nsManager.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
 
            var nodes = document.SelectNodes(@"//wsse:BinarySecurityToken/text()", nsManager);
            if (nodes != null && nodes.Count > 0 && nodes[0] != null)
                return nodes[0].Value;
 
            // The wsse:BinarySecurityToken element is missing. Examine the xml for error information
            throw new Exception("Failed to get Wlid ticket from WS-trust endpoint");
        }
 
        /// <summary>
        /// Gets a WLID ticket for a specified user
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="partner">sitename, i.e. crmapp.www.local-titan.com</param>
        /// <param name="policy">auth policy, i.e. MBI_SSL</param>
        /// <param name="environment">Wlid environment, i.e. INT</param>
        /// <returns></returns>
        public string GetWlidTicket(string userName, string password, string partner, string policy, string environment)
        {
            return GetWlidTicket(userName, password, partner, policy, environment, 10);
        }
    }
}

And here is the code that uses this LiveIdTicketAcquirer class to authenticate with MSCRM Online. You will need to add references to both Microsoft.Crm.Sdk and Microsoft.Crm.SdkTypeProxy dlls (specific for MSCRM Online) and add new Web Reference for CrmOnlineDiscovery.

Below is the cut-down version of CrmOnlineQueryManager that I use on CRM External View project. Feel free to download the full solution on codeplex and explore it yourself. :)

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Caching;
using System.Web.Services.Protocols;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using TTprotons.Crm.ExternalView.Service.CrmOnlineDiscovery;
 
namespace TTprotons.Crm.ExternalView.Service.Crm
{
    /// <summary>
    /// Query Helper class specific for CRM Online.
    /// </summary>
    public class CrmOnlineQueryManager
    {
        private readonly LiveIdTicketAcquirer ticketAcquirer;
 
        #region Constants
 
        private const string CRMONLINE_PARTNER = "crm.dynamics.com";
        private const string CRMONLINE_ENVIRONMENT = "PROD";
 
        #endregion
 
        public CrmOnlineQueryManager()
        {
            ticketAcquirer = new LiveIdTicketAcquirer();
        }
 
        public void Connect(string username, string password, string organization)
        {
            try
            {
                var discoveryService = new CrmDiscoveryService();
 
                // Retrieve the policy for CRM Online Web Service.
                var policyRequest = new RetrievePolicyRequest();
                var policyResponse = (RetrievePolicyResponse)discoveryService.Execute(policyRequest);
 
                // Retrieve the WLID Ticket
                var wlidTicket = ticketAcquirer.GetWlidTicket(username, password, CRMONLINE_PARTNER, policyResponse.Policy, CRMONLINE_ENVIRONMENT);
 
                // Retrieve the CRM Ticket based on the WLID Ticket
                var crmTicketRequest = new RetrieveCrmTicketRequest
                {
                    OrganizationName = organization,
                    PassportTicket = wlidTicket
                };
 
                var crmTicketResponse = (RetrieveCrmTicketResponse)discoveryService.Execute(crmTicketRequest);
 
                // Retrieve the Ticket
                var crmTicket = crmTicketResponse.CrmTicket;
                var orgDetail = crmTicketResponse.OrganizationDetail;
            }
            catch (SoapException ex)
            { }
            catch (Exception ex)
            { }
        }
    }
}
Comments (6) Trackbacks (0)
  1. Great post. I’ve been meaning to take a look at how to do this on partial trust environments and this is great!

    Thanks!

  2. Thank you. Glad this post is helpful for you. :)

  3. This is great code. When using this method, do you still need to have a certificate associated with the Windows Live ID (associated using the Windows Live ID Sign-in Assistant)?

    Hoping the answer is no …

  4. Nope. There is no dependency with the WLID certificate. :)

  5. :) indeed. Thanks!

  6. Great post. I’ve been meaning to take a look at how to do this on partial trust environments and this is great!

    Thanks!


Leave a comment


No trackbacks yet.

Get Adobe Flash playerPlugin by wpburn.com wordpress themes