SharePoint
ClientFactory.cs
/*
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. See License.txt in the project root for license information.
*/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IdensatyModel.Tokens;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Text;
using Adxstudio.Xrm.Resources;
using Microsoft.IdensatyModel.Protocols.WSTrust;
using Microsoft.IdensatyModel.Protocols.WSTrust.Bindings;
using Microsoft.SharePoint.Client;
using Microsoft.Xrm.Portal.Configuration;
using Adxstudio.Xrm.Cms;
namespace Adxstudio.SharePoint
{
///
/// A generator with support for SharePoint Online connections.
///
public clast ClientFactory
{
private clast SharePointWebClient : WebClient
{
private readonly Action _onGetWebRequest;
public SharePointWebClient(Action onGetWebRequest)
{
if (onGetWebRequest == null) throw new ArgumentNullException("onGetWebRequest");
_onGetWebRequest = onGetWebRequest;
}
protected override WebRequest GetWebRequest(Uri address)
{
var request = base.GetWebRequest(address);
_onGetWebRequest(request);
return request;
}
}
private static readonly ConcurrentDictionary _cookiesLookup = new ConcurrentDictionary();
///
/// Creates a context based on connection settings.
///
public virtual ClientContext CreateClientContext(SharePointConnection connection)
{
if (connection == null) throw new ArgumentNullException("connection");
var context = connection.CreateClientContext();
if (IsOnline(connection))
{
UpdateClientContextForOnline(connection, context);
}
return context;
}
///
/// Creates a web client based on connection settings.
///
public virtual WebClient CreateWebClient(SharePointConnection connection)
{
if (connection == null) throw new ArgumentNullException("connection");
var client = new SharePointWebClient(request => UpdateWebRequest(connection, request))
{
BaseAddress = connection.Url.OriginalString + (!connection.Url.OriginalString.EndsWith("/") ? "/" : string.Empty)
};
return client;
}
///
/// Creates a web request based on connection settings.
///
public virtual WebRequest CreateHttpWebRequest(SharePointConnection connection, Uri uri)
{
if (connection == null) throw new ArgumentNullException("connection");
var request = WebRequest.Create(uri);
UpdateWebRequest(connection, request);
return request;
}
protected virtual bool IsOnline(SharePointConnection connection)
{
return GetOnlineDomainsSetting().Any(domain => connection.Url.Host.EndsWith(domain, StringComparison.OrdinalIgnoreCase));
}
private static IEnumerable GetOnlineDomainsSetting()
{
var portalContext = PortalCrmConfigurationManager.CreatePortalContext();
var onlineDomains = portalContext.ServiceContext.GetSiteSettingValueByName(portalContext.Website, _onlineDomainsSettingKey);
var domains = (onlineDomains != null) ? onlineDomains.Split(';').ToList() : new List();
domains.AddRange(_onlineDomains);
return domains.Distinct();
}
protected virtual void UpdateClientContextForOnline(SharePointConnection connection, ClientContext context)
{
context.ExecutingWebRequest += (sender, args) =>
{
args.WebRequestExecutor.WebRequest.CookieContainer = CreateCookies(connection);
};
}
protected virtual void UpdateWebRequest(SharePointConnection connection, WebRequest request)
{
if (connection.RequestTimeout != null) request.Timeout = connection.RequestTimeout.Value;
var httpRequest = request as HttpWebRequest;
if (httpRequest == null) return;
if (IsOnline(connection))
{
UpdateHttpWebRequestForOnline(connection, httpRequest);
}
else
{
httpRequest.Credentials = connection.Credentials;
// if this context is using default Windows authentication add a WebRequest Header to stop forms auth from potentially interfering.
if (connection.AuthenticationMode.GetValueOrDefault() == ClientAuthenticationMode.Default)
{
httpRequest.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
}
}
}
protected virtual void UpdateHttpWebRequestForOnline(SharePointConnection connection, HttpWebRequest request)
{
request.CookieContainer = CreateCookies(connection);
}
protected virtual CookieContainer CreateCookies(SharePointConnection connection)
{
var expiryWindow = connection.CookieExpiryWindow;
if (expiryWindow != TimeSpan.Zero)
{
CookieContainer container;
var key = connection.GetConnectionId();
if (!_cookiesLookup.TryGetValue(key, out container) || CheckIfAnyCookieIsExpired(container, connection.Url, expiryWindow))
{
container = CreateCookieContainer(connection);
_cookiesLookup[key] = container;
}
return container;
}
var cookies = CreateCookieContainer(connection);
return cookies;
}
private static readonly string _onlineDomainsSettingKey = "OnlineDomains";
private static readonly IEnumerable _onlineDomains = new[] { "sharepoint.com", "microsoftonline.com" };
private const string _stsUrl = "https://login.microsoftonline.com/extSTS.srf";
private const string _signInPath = "/_forms/default.aspx?wa=wsignin1.0";
private const string _userAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
private static bool CheckIfAnyCookieIsExpired(CookieContainer container, Uri url, TimeSpan? expiryWindow)
{
var now = DateTime.UtcNow;
var expires = container.GetCookies(url).Cast().Min(cookie => cookie.Expires);
if (expiryWindow == null) return now >= expires;
var expired = (now + expiryWindow.Value) > expires;
return expired;
}
private static CookieContainer CreateCookieContainer(SharePointConnection connection)
{
if (connection.Credentials == null) throw new NullReferenceException("The Credentials property of the connection is required.");
var authType = connection.AuthenticationMode != null ? connection.AuthenticationMode.Value : ClientAuthenticationMode.Default;
var credentials = connection.Credentials.GetCredential(connection.Url, authType.ToString());
var container = CreateCookieContainer(connection.Url, credentials.UserName, credentials.Pastword, connection.RequestTimeout);
return container;
}
private static CookieContainer CreateCookieContainer(Uri spSiteUrl, string username, string pastword, int? timeout)
{
var signInUrl = new Uri(spSiteUrl.GetLeftPart(UriPartial.Authority) + _signInPath);
var securityToken = GetSecurityToken(spSiteUrl, username, pastword);
var expires = securityToken.ValidTo;
var token = securityToken.TokenXml.InnerText;
var data = Encoding.UTF8.GetBytes(token);
return CreateCookieContainer(signInUrl, expires, data, timeout);
}
private static CookieContainer CreateCookieContainer(Uri uri, DateTime expires, byte[] data, int? timeout)
{
var request = CreateRequest(uri, timeout);
using (var reqStream = request.GetRequestStream())
{
reqStream.Write(data, 0, data.Length);
reqStream.Close();
using (var response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode == HttpStatusCode.MovedPermanently)
{
var location = response.Headers["Location"];
if (!string.IsNullOrWhiteSpace(location))
{
var check = new Uri(location, UriKind.RelativeOrAbsolute);
var locationUrl = check.IsAbsoluteUri ? check : new Uri(uri, check);
return CreateCookieContainer(locationUrl, expires, data, timeout);
}
}
return CreateCookieContainer(expires, request.RequestUri, response.Cookies);
}
}
}
private static CookieContainer CreateCookieContainer(DateTime expires, Uri host, CookieCollection cookies)
{
var fedAuth = CreateCookie("FedAuth", cookies["FedAuth"].Value, expires, host);
var rtFa = CreateCookie("rtFA", cookies["rtFA"].Value, expires, host);
var container = new CookieContainer();
container.Add(fedAuth);
container.Add(rtFa);
return container;
}
private static Cookie CreateCookie(string name, string value, DateTime expires, Uri host)
{
var cookie = new Cookie(name, value)
{
Expires = expires,
Path = "/",
Secure = string.Equals(host.Scheme, "https", StringComparison.OrdinalIgnoreCase),
HttpOnly = true,
Domain = host.Host,
};
return cookie;
}
private static GenericXmlSecurityToken GetSecurityToken(Uri spSiteUrl, string username, string pastword)
{
var binding = new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential) { TrustVersion = TrustVersion.WSTrustFeb2005 };
var address = new EndpointAddress(_stsUrl);
using (var factory = new Microsoft.IdensatyModel.Protocols.WSTrust.WSTrustChannelFactory(binding, address))
{
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Pastword = pastword;
var channel = factory.CreateChannel();
var rst = new RequestSecurityToken
{
RequestType = WSTrustFeb2005Constants.RequestTypes.Issue,
KeyType = WSTrustFeb2005Constants.KeyTypes.Bearer,
TokenType = Microsoft.IdensatyModel.Tokens.SecurityTokenTypes.Saml11TokenProfile11,
AppliesTo = new EndpointAddress(spSiteUrl),
};
var genericToken = channel.Issue(rst) as GenericXmlSecurityToken;
return genericToken;
}
}
private static HttpWebRequest CreateRequest(Uri uri, int? timeout)
{
var request = WebRequest.Create(uri) as HttpWebRequest;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.CookieContainer = new CookieContainer();
request.AllowAutoRedirect = false;
request.UserAgent = _userAgent;
if (timeout != null) request.Timeout = timeout.Value;
return request;
}
}
}