Thank you SO much (Can't wait for the next release)!
For anyone else who lands here, here's the code I ended up using:
// VersionAccessUser and VersionAccessPassword are stored in web.config
// web.Url is loaded via the clientContext
// myVersion is the FileVersion I got from the file's Versions.GetById() method
// probably a lot of ways to get hostUrl, it just needs to be https://yourdomain.sharepoint.com/
// - I'm running my app from a subweb
// I had trouble following the links to get the full MsOnlineClaimsHelper code
// (the one on msdn.com was missing RequestBodyWriter, WSTrustFeb2005ContractClient,
// and IWSTrustFeb2005Contract
// so I've included the code I used here.
string myVersionFullUrl = string.Format("{0}/{1}", web.Url, myVersion.Url);
string userName = WebConfigurationManager.AppSettings.Get("VersionAccessUser");
string strPassword = WebConfigurationManager.AppSettings.Get("VersionAccessPassword");
string hostUrl = Regex.Replace(web.Url, "([^/]+//[^/]+/).*", "$1");
MsOnlineClaimsHelper claimsHelper = new MsOnlineClaimsHelper(hostUrl, userName, strPassword);
var client = new WebClient();
client.Headers["Accept"] = "/";
client.Headers.Add(HttpRequestHeader.UserAgent, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
client.Headers.Add(HttpRequestHeader.Cookie, claimsHelper.CookieContainer.GetCookieHeader(new Uri(hostUrl)));
var document = client.DownloadString(myVersionFullUrl);
// ***************************************************************************************** //
// These classes are needed to download old versions of files (see: http://social.msdn.microsoft.com/Forums/en-US/7746d857-d351-49cc-b2f0-496663239e02/how-to-download-a-file-version-from-office-365-using-csom?forum=sharepointdevelopment)
// I cobbled this file from http://social.technet.microsoft.com/Forums/msonline/en-US/4e304493-7ddd-4721-8f46-cb7875078f8b/problem-logging-in-to-office-365-sharepoint-online-from-webole-hosted-in-the-cloud?forum=onlineservicessharepoint
// and http://fredericloud.com/2011/01/11/connecting-to-sharepoint-with-claims-authentication/
using Microsoft.IdentityModel.Protocols.WSTrust;
using Microsoft.SharePoint.Client;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Linq;
namespace DPSiDoxAppWeb.Helpers
{
/// <summary>
/// Create a new contract to use for issue claims for the SharePoint requests
/// </summary>
[ServiceContract]
public interface IWSTrustFeb2005Contract
{
[OperationContract(ProtectionLevel = ProtectionLevel.EncryptAndSign,
Action = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue",
ReplyAction = "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue",
AsyncPattern = true)]
IAsyncResult BeginIssue(Message request, AsyncCallback callback, object state);
Message EndIssue(IAsyncResult asyncResult);
}
/// <summary>
/// Implement the client contract for the new type
/// </summary>
public class WSTrustFeb2005ContractClient : ClientBase<IWSTrustFeb2005Contract>, IWSTrustFeb2005Contract
{
public WSTrustFeb2005ContractClient(Binding binding, EndpointAddress remoteAddress)
: base(binding, remoteAddress)
{ }
public IAsyncResult BeginIssue(Message request, AsyncCallback callback, object state)
{
return Channel.BeginIssue(request, callback, state);
}
public Message EndIssue(IAsyncResult asyncResult)
{
return Channel.EndIssue(asyncResult);
}
}
/// <summary>
/// Create a class that will serialize the token into the request
/// </summary>
class RequestBodyWriter : BodyWriter
{
readonly WSTrustRequestSerializer _serializer;
readonly RequestSecurityToken _rst;
/// <summary>
/// Constructs the Body Writer.
/// </summary>
/// <param name="serializer">Serializer to use for serializing the rst.</param>
/// <param name="rst">The RequestSecurityToken object to be serialized to the outgoing Message.</param>
public RequestBodyWriter(WSTrustRequestSerializer serializer, RequestSecurityToken rst)
: base(false)
{
if (serializer == null)
throw new ArgumentNullException("serializer");
_serializer = serializer;
_rst = rst;
}
/// <summary>
/// Override of the base class method. Serializes the rst to the outgoing stream.
/// </summary>
/// <param name="writer">Writer to which the rst should be written.</param>
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
_serializer.WriteXml(_rst, writer, new WSTrustSerializationContext());
}
}
public class MsOnlineClaimsHelper
{
#region Properties
readonly string _username;
readonly string _password;
readonly bool _useRtfa;
readonly Uri _host;
CookieContainer _cachedCookieContainer = null;
DateTime _expires = DateTime.MinValue;
#endregion
#region Constructors
public MsOnlineClaimsHelper(string host, string username, string password)
: this(new Uri(host), username, password)
{
}
public MsOnlineClaimsHelper(Uri host, string username, string password)
{
_host = host;
_username = username;
_password = password;
_useRtfa = true;
}
public MsOnlineClaimsHelper(Uri host, string username, string password, bool useRtfa)
{
_host = host;
_username = username;
_password = password;
_useRtfa = useRtfa;
}
#endregion
#region Constants
public const string office365STS = "https://login.microsoftonline.com/extSTS.srf";
public const string office365Login = "https://login.microsoftonline.com/login.srf";
public const string office365Metadata = "https://nexus.microsoftonline-p.com/federationmetadata/2007-06/federationmetadata.xml";
public const string wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
public const string wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
private const string userAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
#endregion
class MsoCookies
{
public string FedAuth { get; set; }
public string rtFa { get; set; }
public DateTime Expires { get; set; }
public Uri Host { get; set; }
}
// Method used to add cookies to CSOM
public void clientContext_ExecutingWebRequest(object sender, WebRequestEventArgs e)
{
e.WebRequestExecutor.WebRequest.CookieContainer = getCookieContainer();
//e.WebRequestExecutor.WebRequest.UserAgent = userAgent;
}
// Creates or loads cached cookie container
CookieContainer getCookieContainer()
{
if (_cachedCookieContainer == null || DateTime.Now > _expires)
{
// Get the SAML tokens from SPO STS (via MSO STS) using fed auth passive approach
MsoCookies cookies = getSamlToken();
if (cookies != null && !string.IsNullOrEmpty(cookies.FedAuth))
{
// Create cookie collection with the SAML token
_expires = cookies.Expires;
CookieContainer cc = new CookieContainer();
// Set the FedAuth cookie
Cookie samlAuth = new Cookie("FedAuth", cookies.FedAuth)
{
Expires = cookies.Expires,
Path = "/",
Secure = cookies.Host.Scheme == "https",
HttpOnly = true,
Domain = cookies.Host.Host
};
cc.Add(samlAuth);
if (_useRtfa)
{
// Set the rtFA (sign-out) cookie, added march 2011
Cookie rtFa = new Cookie("rtFA", cookies.rtFa)
{
Expires = cookies.Expires,
Path = "/",
Secure = cookies.Host.Scheme == "https",
HttpOnly = true,
Domain = cookies.Host.Host
};
cc.Add(rtFa);
}
_cachedCookieContainer = cc;
return cc;
}
return null;
}
return _cachedCookieContainer;
}
public CookieContainer CookieContainer
{
get
{
if (_cachedCookieContainer == null || DateTime.Now > _expires)
{
return getCookieContainer();
}
return _cachedCookieContainer;
}
}
private MsoCookies getSamlToken()
{
MsoCookies ret = new MsoCookies();
try
{
var sharepointSite = new
{
Wctx = office365Login,
Wreply = _host.GetLeftPart(UriPartial.Authority) + "/_forms/default.aspx?wa=wsignin1.0"
};
//get token from STS
string stsResponse = getResponse(office365STS, sharepointSite.Wreply);
// parse the token response
XDocument doc = XDocument.Parse(stsResponse);
// get the security token
var crypt = from result in doc.Descendants()
where result.Name == XName.Get("BinarySecurityToken", wsse)
select result;
// get the token expiration
var expires = from result in doc.Descendants()
where result.Name == XName.Get("Expires", wsu)
select result;
ret.Expires = Convert.ToDateTime(expires.First().Value);
HttpWebRequest request = createRequest(sharepointSite.Wreply);
byte[] data = Encoding.UTF8.GetBytes(crypt.FirstOrDefault().Value);
using (Stream stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
stream.Close();
using (HttpWebResponse webResponse = request.GetResponse() as HttpWebResponse)
{
// Handle redirect, added may 2011 for P-subscriptions
if (webResponse.StatusCode == HttpStatusCode.MovedPermanently)
{
HttpWebRequest request2 = createRequest(webResponse.Headers["Location"]);
using (Stream stream2 = request2.GetRequestStream())
{
stream2.Write(data, 0, data.Length);
stream2.Close();
using (HttpWebResponse webResponse2 = request2.GetResponse() as HttpWebResponse)
{
ret.FedAuth = webResponse2.Cookies["FedAuth"].Value;
ret.rtFa = webResponse2.Cookies["rtFa"].Value;
ret.Host = request2.RequestUri;
}
}
}
else
{
ret.FedAuth = webResponse.Cookies["FedAuth"].Value;
ret.rtFa = webResponse.Cookies["rtFa"].Value;
ret.Host = request.RequestUri;
}
}
}
}
catch (Exception ex)
{
return null;
}
return ret;
}
static HttpWebRequest createRequest(string url)
{
HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.CookieContainer = new CookieContainer();
request.AllowAutoRedirect = false; // Do NOT automatically redirect
request.UserAgent = userAgent;
return request;
}
private string getResponse(string stsUrl, string realm)
{
RequestSecurityToken rst = new RequestSecurityToken
{
RequestType = WSTrustFeb2005Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(realm),
KeyType = WSTrustFeb2005Constants.KeyTypes.Bearer,
TokenType = Microsoft.IdentityModel.Tokens.SecurityTokenTypes.Saml11TokenProfile11
};
WSTrustFeb2005RequestSerializer trustSerializer = new WSTrustFeb2005RequestSerializer();
WSHttpBinding binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Message.NegotiateServiceCredential = false;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
EndpointAddress address = new EndpointAddress(stsUrl);
using (WSTrustFeb2005ContractClient trustClient = new WSTrustFeb2005ContractClient(binding, address))
{
trustClient.ClientCredentials.UserName.UserName = _username;
trustClient.ClientCredentials.UserName.Password = _password;
Message response = trustClient.EndIssue(
trustClient.BeginIssue(
Message.CreateMessage(
MessageVersion.Default,
WSTrustFeb2005Constants.Actions.Issue,
new RequestBodyWriter(trustSerializer, rst)
),
null,
null));
trustClient.Close();
using (XmlDictionaryReader reader = response.GetReaderAtBodyContents())
{
return reader.ReadOuterXml();
}
}
}
}
}