Accessing SharePoint Online from Java, PHP, etc.

Hi,

I recently got a question from Java developers who wanted to consume the online SharePoint REST APIs from a background job (no possible interaction with end users). As I’m really working with the Microsoft stack, I thought about the different ways one would do that using a .NET console program :

  • Using the CSOM which is the most obvious choice for a .NET developer
  • Using Azure Active Directory authentication but that requires  some extra setup, but in theory it could be a good choice as ADAL was ported to Java (don’t know whether the .NET and Java versions are really aligned though!).
  • Using a pre-registered SharePoint App

Since they already had a Cloud only account (kind of service account) dedicated to them, I advised them to write a small .NET console program, use the CSOM for SPO and trace the HTTP traffic with a sniffer, but hey, they’re not .NET developers…Well, anyway, I decided to do it myself out of curiosity. So, to all Java developers (and other non-Microsoft ones), here is something you could reuse, providing you translate it into your own language, but that’s pure HTTP traffic so it shouldn’t be a big deal.

Note that it is an as is implementation that is rather optimistic as I don’t handle any exception here. I just wanted to POC it. It is also supposed to be used from within a backend component, without any end user interaction.

First thing to explain is that SharePoint’s REST APIs require a form digest for any writing operation while read operations only require to be authenticated.

So, first things first, let’s define the Digest class:

 public class Digest
    {
        public string digestValue
        {
            get;set;
        }
        public DateTime digestExpiration
        {
            get;set;
        }
    }

the value is what you must include in your writing operations and the expiration is to check whether the digest is still valid or not (times out after 1800 seconds out of the box).

Let’s now see how to play with this :

class Program
{
    static string _authenticationCookie = null;
    static readonly object _lockObject = new object();
    static string authenticationCookie
    {
        get
        {
            if(_authenticationCookie == null)
            {
                lock(_lockObject)
                {
                    if (_authenticationCookie == null)
                    {
                        _authenticationCookie = getCookie(getBinaryToken());
                    }
                }
            }
            return _authenticationCookie;
        }
    }

    static Digest _digestObject = null;
    static Digest digestObject
    {
        get
        {
            if(_digestObject == null || _digestObject.digestExpiration < DateTime.Now)
            {
                lock (_lockObject)
                {
                    if (_digestObject == null || _digestObject.digestExpiration < DateTime.Now)
                    {
                        _digestObject = getDigest(authenticationCookie);
                    }
                }
            }
            return _digestObject;
        }
    }
    static void Main(string[] args)
    {
        HttpWebRequest read = HttpWebRequest.Create("https://tenant.sharepoint.com/_api/web/title") as HttpWebRequest;
        read.Headers.Add("Cookie", authenticationCookie);
        using (StreamReader sr = new StreamReader(read.GetResponse().GetResponseStream()))
        {
            Console.WriteLine(sr.ReadToEnd());
        }
        HttpWebRequest write = HttpWebRequest.Create("https://tenant.sharepoint.com//_api/web/lists") as HttpWebRequest;
        write.ContentType = "application/json;odata=verbose";
        write.Accept = "application/json;odata=verbose";
        write.Headers.Add("Cookie", authenticationCookie);
        write.Headers.Add("X-RequestDigest", digestObject.digestValue);
        write.Method = "POST";
        string data = "{'__metadata': { 'type': 'SP.List' }, 'AllowContentTypes': true,'BaseTemplate': 100, 'ContentTypesEnabled': true, 'Description': 'My list description', 'Title': 'listfromcode8' }";
        write.ContentLength = data.Length;
        using (StreamWriter sw = new StreamWriter(write.GetRequestStream()))
        {
            sw.Write(data);
        }
        using (StreamReader sr = new StreamReader(write.GetResponse().GetResponseStream()))
        {
            Console.WriteLine(sr.ReadToEnd());
        }

        Console.Read();
    }
    #region Helper Methods
    static string getBinaryToken()
    {
        HttpWebRequest soap = HttpWebRequest.Create("https://login.microsoftonline.com/rst2.srf") as HttpWebRequest;
        soap.Method = "POST";
        soap.ContentType = "application/soap+xml; charset=utf-8";
        string soapData = null;
        using (StreamReader sr = new StreamReader("soap.xml"))
        {
            soapData = sr.ReadToEnd();
        }
        soapData = soapData.Replace("{User}", "cloudonly@tenant.onmicrosoft.com");
        soapData = soapData.Replace("{Password}", "thepassword");
        soapData = soapData.Replace("{Created}", DateTime.Now.ToString());
        soapData = soapData.Replace("{Expires}", DateTime.Now.AddHours(1).ToString());
        soap.ContentLength = soapData.Length;
        using (StreamWriter sw = new StreamWriter(soap.GetRequestStream()))
        {
            sw.Write(soapData);
        }
        using (StreamReader sr = new StreamReader(soap.GetResponse().GetResponseStream()))
        {
            XDocument doc = XDocument.Parse(sr.ReadToEnd());
            XNamespace wsa = "http://www.w3.org/2005/08/addressing";
            XNamespace S = "http://www.w3.org/2003/05/soap-envelope";
            XNamespace wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
            XNamespace wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
            XNamespace wst = "http://schemas.xmlsoap.org/ws/2005/02/trust";
            return doc.Descendants(S + "Body")
                            .Elements(wst + "RequestSecurityTokenResponse")
                            .Elements(wst + "RequestedSecurityToken")
                            .Elements(wsse + "BinarySecurityToken")
                            .SingleOrDefault().Value;

        }

    }
    static string getCookie(string token)
    {

        HttpWebRequest cookie = HttpWebRequest.Create("https://tenant.sharepoint.com/_vti_bin/idcrl.svc/") as HttpWebRequest;
        cookie.Headers.Add("Authorization", string.Concat("BPOSIDCRL ",token));
        cookie.Headers.Add("X-IDCRL_ACCEPTED", "t");
        HttpWebResponse cookieResponse = cookie.GetResponse() as HttpWebResponse;
        string cookieValue = cookieResponse.Headers["Set-Cookie"];
        cookieResponse.Close();
        return cookieValue;

    }
    static Digest getDigest(string cookie)
    {
        HttpWebRequest digestRequest = HttpWebRequest.Create("https://tenant.sharepoint.com/_api/contextinfo") as HttpWebRequest;
        digestRequest.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
        digestRequest.Headers.Add("Cookie", cookie);
        digestRequest.Method = "POST";
        digestRequest.ContentLength = 0;
        using (StreamReader sr = new StreamReader(digestRequest.GetResponse().GetResponseStream()))
        {
            XDocument doc = XDocument.Parse(sr.ReadToEnd());
            XNamespace d = "http://schemas.microsoft.com/ado/2007/08/dataservices";
            XNamespace m = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
            XNamespace georss = "http://www.georss.org/georss";
            XNamespace gml = "http://www.opengis.net/gml";
            Digest returnedDigest = new Digest();
            returnedDigest.digestValue = doc.Descendants(d + "FormDigestValue")
                            .SingleOrDefault().Value;
            returnedDigest.digestExpiration = DateTime.Now.AddSeconds(
                Convert.ToInt32(doc.Descendants(d + "FormDigestTimeoutSeconds")
                            .SingleOrDefault().Value));
            return returnedDigest;
        }
    }
    #endregion

}

Of course you should replace the value tenant with your own as well as using your own credentials. The first part consisting in retrieving the binary token is ensured via a SOAP HTTP request. I found it more readable to isolate the XML data ton include in the body in a separate file that is located within my bin/debug directory. Here is the content of the file :


<?xml version="1.0" encoding="UTF-8"?>
<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: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:wst="http://schemas.xmlsoap.org/ws/2005/02/trust">
<S:Header>
<wsa:Action S:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>
<wsa:To S:mustUnderstand="1">https://login.microsoftonline.com/rst2.srf</wsa:To>
<ps:AuthInfo xmlns:ps="http://schemas.microsoft.com/LiveID/SoapServices/v1" Id="PPAuthInfo">
<ps:BinaryVersion>5</ps:BinaryVersion>
<ps:HostingApp>Managed IDCRL</ps:HostingApp>
</ps:AuthInfo>
<wsse:Security>
<wsse:UsernameToken wsu:Id="user">
<wsse:Username>{User}</wsse:Username>
<wsse:Password>{Password}</wsse:Password>
</wsse:UsernameToken>
<wsu:Timestamp Id="Timestamp">
<wsu:Created>{Created}</wsu:Created>
<wsu:Expires>{Expires}</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</S:Header>
<S:Body>
<wst:RequestSecurityToken xmlns:wst="http://schemas.xmlsoap.org/ws/2005/02/trust" Id="RST0">
<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>
<wsp:AppliesTo>
<wsa:EndpointReference>
<wsa:Address>sharepoint.com</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<wsp:PolicyReference URI="MBI"></wsp:PolicyReference>
</wst:RequestSecurityToken>
</S:Body>
</S:Envelope>

I tokenized it to make it more dynamic. Hope this helps fellow developers!

Happy Coding!

Advertisements

About Stephane Eyskens

Office 365, Azure PaaS and SharePoint platform expert
This entry was posted in SharePoint Online and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s