4

The UCWA documentation states that UCWA is currently available only to customers who have Lync on-premises.

Nonetheless, if using Lync Connectivity Analyzer against an Office 365 account, it seems that in can connect to the UCWA service: Completed tests for Mobility (UCWA) service. Furthermore, if I inspect the web requests performed by this tool (with Fiddler, for example), I can take the authentication token it obtained (somehow) from Office 365 and use it to make requests against UCWA.

  • it seems that UCWA is exposed by Office 365, is this correct? It seems that Lync Connectivity Analyzer uses a certain WebTicket Service to get a authentication token.
  • is there any library that abstracts the usage of WebTicket Service in order to obtain the authentication token? After having the token, accessing the UCWA resources would be pretty simple - though, a library for that would be nice too :)
  • I could not find too much documentation about the WebTicket Service (WCF). If I add a service reference (Visual Studio) to https://lyncweb.domain.com/WebTicket/WebTicketService.svc, there aren't too many options - it seems that the request and response messages do not have a certain structure, so that it's quite tricky to call the IssueToken operation exposed by this WebTicket Service.

Links:

turdus-merula
  • 8,546
  • 8
  • 38
  • 50

2 Answers2

5

you did quite some investigation, and based on my information the WebTicket should be a possible authentication token for UCWA.

Unfortunately, the documentation is still correct in saying UCWA is only supported for on-prem installation at this moment. For Lync Online / Office 365 it isn't enabled yet.

There's a feature request on Office's User Voice that you can vote and follow. As you can read there are announcements to be expected soon in this

https://officespdev.uservoice.com/forums/224641-general/suggestions/5857717-ucwa-for-lync-365

Massimo Prota
  • 1,226
  • 7
  • 14
4

It does look like Office 365 exposes at least a decent subset of the UCWA functionality. The Lync Connectivity Analyzer is actually a .NET application so it's not terribly difficult to leverage it directly to do the heavy lifting. It's not quite as nice as using a 'friendly' library but it has worked well for me in my various prototyping. With the code below, I'm able to get the web ticket value and the UCWA base URL (via auto-discovery) and then go to town querying the API. It's not the prettiest code but it works.

Note: You'll need to add references to the managed DLLs in the Lync Connectivity Analyzer. For me, that meant:

  • Credentials.dll
  • LyncConnectivityAnalyzer.Logging.dll
  • LyncConnectivityAnalyzer.Resources.dll
  • LyncWebServices.dll
  • Utilities.dll

You'll also need the unmanaged DLLs (maybe just one but I did both) copied to the same folder as your app (e.g. the \bin\ folder).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Security;
using System.Threading.Tasks;
using LyncConnectivityAnalyzer.Logging;
using Microsoft.LyncServer.WebServices;
using Microsoft.Utilities.Credentials;

namespace SM.CoolStuff
{
    /// <summary>
    /// Helper class for performing auto-discovery and authentication to the Lync/Skype UCWA sevice on Office365
    /// </summary>
    public class SkypeHelper
    {
        private string _webTicket;
        private string _ucwaRootPath;
        private readonly ILyncLogging _logger = new Logger();

        /// <summary>
        /// Initializes the connection/authentication/etc.  Once this method completes, <see cref="_webTicket"/> and <see cref="_ucwaRootPath"/> will
        /// be populated.
        /// </summary>
        private async Task Initialize(string userName, SecureString password)
        {
            //Setup the credential
            var userCreds = new Credentials
            {
                UPN = userName,
                SecurePassword = password,
                credsType = CredInputType.CredDialog,
                URI = userName
            };

            //Perform auto-discovery to get the UCWA path
            //We'll just always allow redirects
            ValidateRedirectRequestDelegate alwaysAllowRedirect = (logger, url, destinationUrl) => true;
            var adm = new AutoDiscoverManager(_logger, "http://lyncdiscover." + userCreds.Domain, userCreds.URI, userCreds, alwaysAllowRedirect);
            await adm.StartDiscoveryJourney();

            //Save the path
            _ucwaRootPath = adm.GetAutoDiscoverAddress().ExternalUcwa;

            //Setup the 'validator' that does all the heavy lifting
            var webServicesValidation = new WebServicesValidation(_logger, _ucwaRootPath, userCreds)
            {
                HttpRequestBody = ApplicationPostBody,
                customHeaders = CustomHeaders,
                getPost = HttpMethod.Post
            };

            //Make a first request that should gracefully fail with 'authorization required'
            await webServicesValidation.CheckURL(_ucwaRootPath, false);

            //Now authorize the request
            await webServicesValidation.TryURLAuth(_ucwaRootPath);

            //Use some ugly reflection to get the ticket value.  There may be a better way but this works
            _webTicket = (string)WebTicketField.GetValue(webServicesValidation);
        }

        /// <summary>
        /// Example usage
        /// </summary>
        public async Task DoSomethingOnSkype()
        {
            //If you already have a SecureString, might as well use that.  Otherwise, convert an 'insecure' string to be 'Secure'
            var secureString = new SecureString();
            "TopSecret".ToList().ForEach(secureString.AppendChar);

            //Do the initialization
            await Initialize("user@somewhere.com", secureString);

            //TODO: Use _webTicket and _host to query something
        }

        private static readonly string ApplicationPostBody =
            string.Concat(
                "<input xmlns=\"http://schemas.microsoft.com/rtc/2012/03/ucwa\"><property name=\"culture\">en-US</property><property name=\"endpointId\">44:D8:84:3C:68:68</property><property name=\"type\">Phone</property><property name=\"userAgent\">",
                //TODO: Your app name here
                "LyncConnectivityAnalyzer", "/",
                //TODO: Your app version here
                "5.0.8308.582",
                " (Windows OS 6.0)</property></input>");

        private static readonly Dictionary<string, string> CustomHeaders = new Dictionary<string, string>
        {
            {"Accept", "application/vnd.microsoft.com.ucwa+xml"},
            {"Content-Type", "application/vnd.microsoft.com.ucwa+xml"},
            {"X-MS-Namespace", "internal"},
            {"Connection", "keep-alive"},
            {"Proxy-Connection", "keep-alive"}
        };

        private static readonly FieldInfo WebTicketField = FindWebTicketField();

        private static FieldInfo FindWebTicketField()
        {
            var fieldInfo = typeof(WebServicesValidation).GetField("_webticket", BindingFlags.Instance | BindingFlags.NonPublic);
            if (fieldInfo == null)
            {
                throw new ApplicationException("Could not find private _webticket field");
            }
            return fieldInfo;
        }
   }
}
Stephen McDaniel
  • 2,938
  • 2
  • 24
  • 53