/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* Kind, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
using PortCMIS.Binding.Http; | |
using PortCMIS.Binding.Impl; | |
using PortCMIS.Binding.Services; | |
using PortCMIS.Client; | |
using System; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Http; | |
using System.Net.Http.Headers; | |
using System.Text; | |
namespace PortCMIS.Binding | |
{ | |
/// <summary> | |
/// Low-level CMIS binding interface. | |
/// </summary> | |
public interface ICmisBinding : IDisposable | |
{ | |
/// <value> | |
/// Binding type. | |
/// </value> | |
string BindingType { get; } | |
/// <summary> | |
/// Returns the repository service instance. | |
/// </summary> | |
/// <returns>the repository service instance</returns> | |
IRepositoryService GetRepositoryService(); | |
/// <summary> | |
/// Returns the navigation service instance. | |
/// </summary> | |
/// <returns>the navigation service instance</returns> | |
INavigationService GetNavigationService(); | |
/// <summary> | |
/// Returns the object service instance. | |
/// </summary> | |
/// <returns>the object service instance</returns> | |
IObjectService GetObjectService(); | |
/// <summary> | |
/// Returns the versioning service instance. | |
/// </summary> | |
/// <returns>the versioning service instance</returns> | |
IVersioningService GetVersioningService(); | |
/// <summary> | |
/// Returns the relationship service instance. | |
/// </summary> | |
/// <returns>the relationship service instance</returns> | |
IRelationshipService GetRelationshipService(); | |
/// <summary> | |
/// Returns the discovery service instance. | |
/// </summary> | |
/// <returns>the discovery service instance</returns> | |
IDiscoveryService GetDiscoveryService(); | |
/// <summary> | |
/// Returns the multi-filing service instance. | |
/// </summary> | |
/// <returns>the multi-filing service instance</returns> | |
IMultiFilingService GetMultiFilingService(); | |
/// <summary> | |
/// Returns the ACL service instance. | |
/// </summary> | |
/// <returns>the ACL service instance</returns> | |
IAclService GetAclService(); | |
/// <summary> | |
/// Returns the policy service instance. | |
/// </summary> | |
/// <returns>the policy service instance</returns> | |
IPolicyService GetPolicyService(); | |
/// <summary> | |
/// Returns the authentication provider instance. | |
/// </summary> | |
/// <returns>the authentication provider instance</returns> | |
IAuthenticationProvider GetAuthenticationProvider(); | |
/// <summary> | |
/// Clears all low-level caches. | |
/// </summary> | |
void ClearAllCaches(); | |
/// <summary> | |
/// Clears all low-level caches for the given repository. | |
/// </summary> | |
/// <param name="repositoryId">the repository ID</param> | |
void ClearRepositoryCache(string repositoryId); | |
} | |
/// <summary> | |
/// Binding Session interface. | |
/// </summary> | |
public interface IBindingSession | |
{ | |
/// <summary> | |
/// Gets a value from the session. | |
/// </summary> | |
/// <param name="key">the key</param> | |
/// <returns>the value or <c>null</c> if the key is unknown</returns> | |
object GetValue(string key); | |
/// <summary> | |
/// Gets a value from the session. | |
/// </summary> | |
/// <param name="key">the key</param> | |
/// <param name="defValue">the default value</param> | |
/// <returns>the value or the default value if the key is unknown</returns> | |
object GetValue(string key, object defValue); | |
/// <summary> | |
/// Gets a value as an integer from the session. | |
/// </summary> | |
/// <param name="key">the key</param> | |
/// <param name="defValue">the default value</param> | |
/// <returns>the value or the default value if the key is unknown or the value cannot be returned as an integer</returns> | |
int GetValue(string key, int defValue); | |
/// <summary> | |
/// Gets a value as a boolean from the session. | |
/// </summary> | |
/// <param name="key">the key</param> | |
/// <param name="defValue">the default value</param> | |
/// <returns>the value or the default value if the key is unknown or the value cannot be returned as a boolean</returns> | |
bool GetValue(string key, bool defValue); | |
/// <summary> | |
/// Adds a key-value pair to the session. | |
/// </summary> | |
/// <param name="key">the key</param> | |
/// <param name="value">the value</param> | |
void PutValue(string key, object value); | |
/// <summary> | |
/// Removes a key-value pair. | |
/// </summary> | |
/// <param name="key">the key</param> | |
void RemoveValue(string key); | |
/// <summary> | |
/// Gets the authentication provider from the session. | |
/// </summary> | |
/// <returns>the authentication provider or <c>null</c> if no authentication provider has been set</returns> | |
IAuthenticationProvider GetAuthenticationProvider(); | |
/// <summary> | |
/// Gets the HTTP invoker from the session. | |
/// </summary> | |
/// <remarks> | |
/// If no HTTP invoker has been set, a HTTP invoker is created, added to the session and returned. | |
/// </remarks> | |
/// <returns>the HTTP invoker</returns> | |
IHttpInvoker GetHttpInvoker(); | |
} | |
/// <summary> | |
/// SPI interface. | |
/// </summary> | |
public interface ICmisSpi : IDisposable | |
{ | |
/// <summary> | |
/// Initializes the SPI with a binding session. | |
/// </summary> | |
/// <param name="session">the binding session</param> | |
void Initialize(IBindingSession session); | |
/// <summary> | |
/// Returns the repository service instance. | |
/// </summary> | |
IRepositoryService GetRepositoryService(); | |
/// <summary> | |
/// Returns the navigation service instance. | |
/// </summary> | |
INavigationService GetNavigationService(); | |
/// <summary> | |
/// Returns the object service instance. | |
/// </summary> | |
IObjectService GetObjectService(); | |
/// <summary> | |
/// Returns the versioning service instance. | |
/// </summary> | |
IVersioningService GetVersioningService(); | |
/// <summary> | |
/// Returns the relationship service instance. | |
/// </summary> | |
IRelationshipService GetRelationshipService(); | |
/// <summary> | |
/// Returns the discovery service instance. | |
/// </summary> | |
IDiscoveryService GetDiscoveryService(); | |
/// <summary> | |
/// Returns the multi-filing service instance. | |
/// </summary> | |
IMultiFilingService GetMultiFilingService(); | |
/// <summary> | |
/// Returns the ACL service instance. | |
/// </summary> | |
IAclService GetAclService(); | |
/// <summary> | |
/// Returns the repository service instance. | |
/// </summary> | |
IPolicyService GetPolicyService(); | |
/// <summary> | |
/// Clears all caches. | |
/// </summary> | |
void ClearAllCaches(); | |
/// <summary> | |
/// Clears all caches of a repository. | |
/// </summary> | |
/// <param name="repositoryId">the repository ID</param> | |
void ClearRepositoryCache(string repositoryId); | |
} | |
/// <summary> | |
/// Authentication provider interface. | |
/// </summary> | |
public interface IAuthenticationProvider | |
{ | |
/// <value> | |
/// Gets the binding session instance. | |
/// </value> | |
IBindingSession Session { get; set; } | |
} | |
/// <summary> | |
/// Authentication provider interface for the portable HTTP client. | |
/// </summary> | |
public interface IPortableAuthenticationProvider : IAuthenticationProvider | |
{ | |
/// <summary> | |
/// Creates a HttpClientHandler object. | |
/// </summary> | |
/// <remarks> | |
/// If this method returns <c>null</c>, a default HttpClientHandler object will be created. | |
/// </remarks> | |
/// <returns>a new HttpClientHandler object</returns> | |
HttpClientHandler CreateHttpClientHandler(); | |
/// <summary> | |
/// Prepares the HTTP client handler before it is used. | |
/// </summary> | |
/// <param name="httpClientHandler">the HTTP client handler</param> | |
void PrepareHttpClientHandler(HttpClientHandler httpClientHandler); | |
/// <summary> | |
/// Prepares the HTTP request message before it is used. | |
/// </summary> | |
/// <param name="httpRequestMessage">the HTTP request message</param> | |
void PrepareHttpRequestMessage(HttpRequestMessage httpRequestMessage); | |
/// <summary> | |
/// Handles the HTTP response if necessary. | |
/// </summary> | |
/// <param name="httpResponseMessage">the HTTP response message</param> | |
void HandleResponse(HttpResponseMessage httpResponseMessage); | |
} | |
/// <summary> | |
/// Base implementation of a portable authentication provider. | |
/// </summary> | |
public abstract class AbstractAuthenticationProvider : IPortableAuthenticationProvider | |
{ | |
/// <inheritdoc/> | |
public IBindingSession Session { get; set; } | |
/// <value> | |
/// Gets the HTTP cookie container. | |
/// </value> | |
public CookieContainer CookieContainer { get; private set; } | |
/// <value> | |
/// Gets the user name. | |
/// </value> | |
public string User { get { return Session.GetValue(SessionParameter.User) as string; } } | |
/// <value> | |
/// Gets the Password. | |
/// </value> | |
public string Password { get { return Session.GetValue(SessionParameter.Password) as string; } } | |
/// <value> | |
/// Gets the proxy user. | |
/// </value> | |
public string ProxyUser { get { return Session.GetValue(SessionParameter.ProxyUser) as string; } } | |
/// <value> | |
/// Gets the proxy password | |
/// </value> | |
public string ProxyPassword { get { return Session.GetValue(SessionParameter.ProxyPassword) as string; } } | |
/// <inheritdoc/> | |
public virtual HttpClientHandler CreateHttpClientHandler() | |
{ | |
return null; | |
} | |
/// <inheritdoc/> | |
public virtual void PrepareHttpClientHandler(HttpClientHandler httpClientHandler) | |
{ | |
httpClientHandler.PreAuthenticate = true; | |
httpClientHandler.UseCookies = true; | |
httpClientHandler.UseProxy = true; | |
CookieContainer = new CookieContainer(); | |
httpClientHandler.CookieContainer = CookieContainer; | |
} | |
/// <inheritdoc/> | |
public virtual void PrepareHttpRequestMessage(HttpRequestMessage httpRequestMessage) | |
{ | |
} | |
/// <inheritdoc/> | |
public virtual void HandleResponse(HttpResponseMessage httpResponseMessage) | |
{ | |
} | |
} | |
/// <summary> | |
/// Standard Authentication Provider. | |
/// </summary> | |
public class StandardAuthenticationProvider : AbstractAuthenticationProvider | |
{ | |
/// <value> | |
/// Gets the OAuth bearer token. | |
/// </value> | |
public string BearerToken { get { return Session.GetValue(SessionParameter.OAuthBearerToken) as string; } } | |
/// <value> | |
/// Gets the CSRF header. | |
/// r</value> | |
public string CsrfHeader { get { return Session.GetValue(SessionParameter.CsrfHeader) as string; } } | |
/// <value> | |
/// Gets the authentication header. | |
/// </value> | |
protected AuthenticationHeaderValue AuthenticationHeader { get; set; } | |
/// <value> | |
/// Gets the proxy authentication header. | |
/// </value> | |
protected AuthenticationHeaderValue ProxyAuthenticationHeader { get; set; } | |
private object tokenLock = new object(); | |
private string token = "fetch"; | |
/// <value> | |
/// Gets the CSRF header name. | |
/// </value> | |
protected string CsrfHeaderName { get; set; } | |
/// <value> | |
/// Gets the CSRF header token. | |
/// </value> | |
protected string CsrfToken | |
{ | |
get { lock (tokenLock) { return token; } } | |
set { lock (tokenLock) { token = value; } } | |
} | |
/// <inheritdoc/> | |
public override void PrepareHttpClientHandler(HttpClientHandler httpClientHandler) | |
{ | |
base.PrepareHttpClientHandler(httpClientHandler); | |
if (User != null) | |
{ | |
httpClientHandler.PreAuthenticate = true; | |
string preemptiveFlag = Session.GetValue(SessionParameter.PreemptivAuthentication) as string; | |
if (preemptiveFlag != null && preemptiveFlag.ToLowerInvariant().Equals("true")) | |
{ | |
var userPassword = Encoding.UTF8.GetBytes(User + ":" + Password); | |
AuthenticationHeader = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(userPassword)); | |
} | |
else | |
{ | |
httpClientHandler.Credentials = new NetworkCredential(User, Password); | |
} | |
} | |
else | |
{ | |
if (BearerToken != null) | |
{ | |
httpClientHandler.PreAuthenticate = true; | |
httpClientHandler.UseDefaultCredentials = false; | |
AuthenticationHeader = new AuthenticationHeaderValue("Bearer", BearerToken); | |
} | |
else | |
{ | |
httpClientHandler.UseDefaultCredentials = true; | |
} | |
} | |
if (ProxyUser != null) | |
{ | |
var userPassword = Encoding.UTF8.GetBytes(ProxyUser + ":" + ProxyPassword); | |
ProxyAuthenticationHeader = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(userPassword)); | |
} | |
if (CsrfHeader != null) | |
{ | |
CsrfHeaderName = CsrfHeader; | |
} | |
} | |
/// <inheritdoc/> | |
public override void PrepareHttpRequestMessage(HttpRequestMessage httpRequestMessage) | |
{ | |
base.PrepareHttpRequestMessage(httpRequestMessage); | |
if (AuthenticationHeader != null) | |
{ | |
httpRequestMessage.Headers.Authorization = AuthenticationHeader; | |
} | |
if (ProxyAuthenticationHeader != null) | |
{ | |
httpRequestMessage.Headers.ProxyAuthorization = ProxyAuthenticationHeader; | |
} | |
if (CsrfHeaderName != null && CsrfToken != null) | |
{ | |
httpRequestMessage.Headers.Add(CsrfHeaderName, CsrfToken); | |
} | |
} | |
/// <inheritdoc/> | |
public override void HandleResponse(HttpResponseMessage httpResponseMessage) | |
{ | |
base.HandleResponse(httpResponseMessage); | |
if (CsrfHeaderName != null) | |
{ | |
IEnumerable<string> values; | |
if (httpResponseMessage.Headers.TryGetValues(CsrfHeaderName, out values)) | |
{ | |
CsrfToken = values.First(); | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// A factory that created low-level binding objects. | |
/// </summary> | |
public class CmisBindingFactory | |
{ | |
/// <summary> | |
/// Default CMIS AtomPub binding SPI implementation class name. | |
/// </summary> | |
public const string BindingSpiAtomPub = "PortCMIS.Binding.AtomPub.CmisAtomPubSpi"; | |
/// <summary> | |
/// Default CMIS Browser binding SPI implementation class name. | |
/// </summary> | |
public const string BindingSpiBrowser = "PortCMIS.Binding.Browser.CmisBrowserSpi"; | |
/// <summary> | |
/// Standard authentication provider class name. | |
/// </summary> | |
public const string StandardAuthenticationProviderClass = "PortCMIS.Binding.StandardAuthenticationProvider"; | |
/// <summary> | |
/// Default HTTP invoker class name. | |
/// </summary> | |
public const string DefaultHttpInvokerClass = "PortCMIS.Binding.Http.DefaultHttpInvoker"; | |
private IDictionary<string, string> defaults; | |
/// <summary> | |
/// This is a factory. | |
/// </summary> | |
private CmisBindingFactory() | |
{ | |
defaults = CreateNewDefaultParameters(); | |
} | |
/// <summary> | |
/// Creates a new instance of this factory. | |
/// </summary> | |
/// <returns>a factory object</returns> | |
public static CmisBindingFactory NewInstance() | |
{ | |
return new CmisBindingFactory(); | |
} | |
/// <value> | |
/// Gets and sets default session parameters. | |
/// </value> | |
public IDictionary<string, string> DefaultSessionParameters | |
{ | |
get { return defaults; } | |
set | |
{ | |
if (value == null) | |
{ | |
defaults = CreateNewDefaultParameters(); | |
} | |
else | |
{ | |
defaults = value; | |
} | |
} | |
} | |
/// <summary> | |
/// Creates a binding object for custom binding implementations. | |
/// </summary> | |
/// <param name="sessionParameters">the session parameters</param> | |
/// <param name="authenticationProvider">an authentication provider instance or <c>null</c> to use the default implementation</param> | |
/// <returns>a low-level binding object</returns> | |
public ICmisBinding CreateCmisBinding(IDictionary<string, string> sessionParameters, IAuthenticationProvider authenticationProvider) | |
{ | |
CheckSessionParameters(sessionParameters, true); | |
AddDefaultParameters(sessionParameters); | |
return new CmisBinding(sessionParameters, authenticationProvider); | |
} | |
/// <summary> | |
/// Creates an Browser binding object. | |
/// </summary> | |
/// <param name="sessionParameters">the session parameters</param> | |
/// <param name="authenticationProvider">an authentication provider instance or <c>null</c> to use the default implementation</param> | |
/// <returns>a low-level binding object</returns> | |
public ICmisBinding CreateCmisBrowserBinding(IDictionary<string, string> sessionParameters, IAuthenticationProvider authenticationProvider) | |
{ | |
CheckSessionParameters(sessionParameters, false); | |
sessionParameters[SessionParameter.BindingSpiClass] = BindingSpiBrowser; | |
if (authenticationProvider == null) | |
{ | |
if (!sessionParameters.ContainsKey(SessionParameter.AuthenticationProviderClass)) | |
{ | |
sessionParameters[SessionParameter.AuthenticationProviderClass] = StandardAuthenticationProviderClass; | |
} | |
} | |
if (!sessionParameters.ContainsKey(SessionParameter.HttpInvokerClass)) | |
{ | |
sessionParameters[SessionParameter.HttpInvokerClass] = DefaultHttpInvokerClass; | |
} | |
AddDefaultParameters(sessionParameters); | |
if (!sessionParameters.ContainsKey(SessionParameter.BrowserSuccinct)) | |
{ | |
sessionParameters.Add(SessionParameter.BrowserSuccinct, "true"); | |
} | |
Check(sessionParameters, SessionParameter.BrowserUrl); | |
return new CmisBinding(sessionParameters, authenticationProvider); | |
} | |
/// <summary> | |
/// Creates an AtomPub binding object. | |
/// </summary> | |
/// <param name="sessionParameters">the session parameters</param> | |
/// <param name="authenticationProvider">an authentication provider instance or <c>null</c> to use the default implementation</param> | |
/// <returns>a low-level binding object</returns> | |
public ICmisBinding CreateCmisAtomPubBinding(IDictionary<string, string> sessionParameters, IAuthenticationProvider authenticationProvider) | |
{ | |
CheckSessionParameters(sessionParameters, false); | |
sessionParameters[SessionParameter.BindingSpiClass] = BindingSpiAtomPub; | |
if (authenticationProvider == null) | |
{ | |
if (!sessionParameters.ContainsKey(SessionParameter.AuthenticationProviderClass)) | |
{ | |
sessionParameters[SessionParameter.AuthenticationProviderClass] = StandardAuthenticationProviderClass; | |
} | |
} | |
if (!sessionParameters.ContainsKey(SessionParameter.HttpInvokerClass)) | |
{ | |
sessionParameters[SessionParameter.HttpInvokerClass] = DefaultHttpInvokerClass; | |
} | |
AddDefaultParameters(sessionParameters); | |
Check(sessionParameters, SessionParameter.AtomPubUrl); | |
return new CmisBinding(sessionParameters, authenticationProvider); | |
} | |
/// <summary> | |
/// Creates a Web Services binding object. | |
/// </summary> | |
/// <remarks> | |
/// PortCMIS doesn't support the Web Services binding. It may be implemented in the future. | |
/// </remarks> | |
/// <param name="sessionParameters">the session parameters</param> | |
/// <param name="authenticationProvider">an authentication provider instance or <c>null</c> to use the default implementation</param> | |
/// <returns>a low-level binding object</returns> | |
public ICmisBinding CreateCmisWebServicesBinding(IDictionary<string, string> sessionParameters, IAuthenticationProvider authenticationProvider) | |
{ | |
throw new ArgumentException("The Web Services binding is not supported!"); | |
} | |
// ---- internals ---- | |
private void CheckSessionParameters(IDictionary<string, string> sessionParameters, bool mustContainSpi) | |
{ | |
// don't accept null | |
if (sessionParameters == null) | |
{ | |
throw new ArgumentNullException(nameof(sessionParameters)); | |
} | |
// check binding entry | |
if (mustContainSpi) | |
{ | |
string spiClass; | |
if (sessionParameters.TryGetValue(SessionParameter.BindingSpiClass, out spiClass)) | |
{ | |
throw new ArgumentException("SPI class entry (" + SessionParameter.BindingSpiClass + ") is missing!"); | |
} | |
if ((spiClass == null) || (spiClass.Trim().Length == 0)) | |
{ | |
throw new ArgumentException("SPI class entry (" + SessionParameter.BindingSpiClass + ") is invalid!"); | |
} | |
} | |
} | |
private void Check(IDictionary<string, string> sessionParameters, string parameter) | |
{ | |
if (!sessionParameters.ContainsKey(parameter)) | |
{ | |
throw new ArgumentException("Parameter '" + parameter + "' is missing!"); | |
} | |
} | |
private void AddDefaultParameters(IDictionary<string, string> sessionParameters) | |
{ | |
foreach (string key in defaults.Keys) | |
{ | |
if (!sessionParameters.ContainsKey(key)) | |
{ | |
sessionParameters[key] = defaults[key]; | |
} | |
} | |
} | |
private IDictionary<string, string> CreateNewDefaultParameters() | |
{ | |
IDictionary<string, string> result = new Dictionary<string, string>(); | |
result[SessionParameter.CacheSizeRepositories] = SessionParameterDefaults.CacheSizeRepositories.ToString(CultureInfo.InvariantCulture); | |
result[SessionParameter.CacheSizeTypes] = SessionParameterDefaults.CacheSizeTypes.ToString(CultureInfo.InvariantCulture); | |
result[SessionParameter.CacheSizeLinks] = SessionParameterDefaults.CacheSizeLinks.ToString(CultureInfo.InvariantCulture); | |
return result; | |
} | |
} | |
} |