Skip to content
Permalink
Browse files

Add new utility class, OAuthHttpClient.

This class extends HttpClient and uses an OAuthHttpMessageHandler under the covers to automatically authenticate requests.
Additionally, it automatically respects the global HttpContextSettings for overall timeout, read/write timeout, and SSL certificate validation.
(BufferRequests isn't currently handled, as HttpClient doesn't seem to easily expose that flag, and as of .NET 4.5, seems to not buffer by default.)
  • Loading branch information...
tygill committed Mar 23, 2018
1 parent 407bead commit f93f9dcc5ade5f418a17628938a93568e9443ae6
@@ -35,8 +35,10 @@
<HintPath>..\..\..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
</ItemGroup>
<ItemGroup>
<Compile Include="OAuthHttpClient.cs" />
<Compile Include="OAuthHttpMessageHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="OAuthHelper.cs" />
@@ -0,0 +1,108 @@
// Copyright (C) 2018, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading.Tasks;

using Duplicati.Library.Utility;

namespace Duplicati.Library
{
public class OAuthHttpClient : HttpClient
{
private static readonly string USER_AGENT_VERSION = Assembly.GetExecutingAssembly().GetName().Version.ToString();

private readonly OAuthHttpMessageHandler m_authenticator;

public OAuthHttpClient(string authid, string protocolKey)
: this(CreateMessageHandler(authid, protocolKey))
{
}

private OAuthHttpClient(OAuthHttpMessageHandler authenticator)
: base(authenticator, true)
{
this.m_authenticator = authenticator;

// Set the overall timeout
if (HttpContextSettings.OperationTimeout > TimeSpan.Zero)
{
this.Timeout = HttpContextSettings.OperationTimeout;
}

// We would also set AllowReadStreamBuffering = HttpContextSettings.BufferRequests, except HttpClient doesn't appear to expose this.
// However, starting in .NET 4.5, it looks like HttpClient doesn't buffer by default.
// https://www.strathweb.com/2012/09/dealing-with-large-files-in-asp-net-web-api/

// Set the default user agent
this.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Duplicati", USER_AGENT_VERSION));
}

/// <summary>
/// Sends an async request with optional authentication.
/// </summary>
/// <param name="request">Http request</param>
/// <param name="authenticate">Whether to authenticate the request</param>
/// <returns>Http response</returns>
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool authenticate)
{
if (!authenticate)
{
this.PreventAuthentication(request);
}

return await this.SendAsync(request);
}

/// <summary>
/// Prevents authentication from being applied on the given request
/// </summary>
/// <param name="request">Request to not authenticate</param>
/// <returns>Request to not authenticate</returns>
public HttpRequestMessage PreventAuthentication(HttpRequestMessage request)
{
return this.m_authenticator.PreventAuthentication(request);
}

/// <summary>
/// Create a message handler with the global timeout / certificate settings.
/// </summary>
/// <param name="authid">OAuth Auth-ID</param>
/// <param name="protocolKey">Protocol key</param>
/// <returns>Http message handler</returns>
private static OAuthHttpMessageHandler CreateMessageHandler(string authid, string protocolKey)
{
OAuthHttpMessageHandler handler = new OAuthHttpMessageHandler(authid, protocolKey);

// Set the read/write timeout
if (HttpContextSettings.OperationTimeout > TimeSpan.Zero)
{
handler.ReadWriteTimeout = (int)HttpContextSettings.ReadWriteTimeout.TotalMilliseconds;
}

// Set the certificate validator
if (HttpContextSettings.CertificateValidator != null)
{
handler.ServerCertificateValidationCallback = HttpContextSettings.CertificateValidator.ValidateServerCertficate;
}

return handler;
}
}
}
@@ -14,16 +14,14 @@
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace Duplicati.Library
{
public class OAuthHttpMessageHandler : HttpClientHandler
public class OAuthHttpMessageHandler : WebRequestHandler
{
/// <summary>
/// Requests which contain a property with this name (in 'request.Properties') will not have the authentication header automatically added.
@@ -47,6 +47,7 @@
<HintPath>..\..\..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
</ItemGroup>
<ItemGroup>
<Compile Include="Exceptions.cs" />
@@ -5,7 +5,6 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading;

@@ -69,15 +68,12 @@ public abstract class MicrosoftGraphBackend : IBackend, IStreamingBackend, IQuot
/// </summary>
private const int UPLOAD_SESSION_FRAGMENT_MULTIPLE_SIZE = 320 * 1024;

private static readonly string USER_AGENT_VERSION = Assembly.GetExecutingAssembly().GetName().Version.ToString();

private static readonly HttpMethod PatchMethod = new HttpMethod("PATCH");

protected delegate string DescriptionTemplateDelegate(string mssadescription, string mssalink, string msopdescription, string msoplink);

private readonly JsonSerializer m_serializer = new JsonSerializer();
private readonly OAuthHttpMessageHandler m_authenticator;
private readonly HttpClient m_client;
private readonly OAuthHttpClient m_client;
private readonly string m_path;
private readonly int fragmentSize;
private readonly int fragmentRetryCount;
@@ -121,10 +117,8 @@ protected MicrosoftGraphBackend(string url, Dictionary<string, string> options)
this.fragmentRetryDelay = UPLOAD_SESSION_FRAGMENT_DEFAULT_RETRY_DELAY;
}

this.m_authenticator = new OAuthHttpMessageHandler(authid, this.ProtocolKey);
this.m_client = new HttpClient(this.m_authenticator, true);
this.m_client = new OAuthHttpClient(authid, this.ProtocolKey);
this.m_client.BaseAddress = new System.Uri(BASE_ADDRESS);
this.m_client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Duplicati", USER_AGENT_VERSION));

// Extract out the path to the backup root folder from the given URI
this.m_path = NormalizeSlashes(this.GetRootPathFromUrl(url));
@@ -367,13 +361,11 @@ public void Put(string remotename, Stream stream)
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, uploadSession.UploadUrl);
request.Content = fragmentContent;

// The uploaded put requests will error if they are authenticated
this.m_authenticator.PreventAuthentication(request);

HttpResponseMessage response = null;
try
{
response = this.m_client.SendAsync(request).Await();
// The uploaded put requests will error if they are authenticated
response = this.m_client.SendAsync(request, false).Await();

// Note: On the last request, the json result includes the default properties of the item that was uploaded
var result = this.ParseResponse<UploadSession>(response);

1 comment on commit f93f9dc

@duplicatibot

This comment has been minimized.

Copy link

commented on f93f9dc Jul 9, 2019

This commit has been mentioned on Duplicati. There might be relevant details there:

https://forum.duplicati.com/t/error-id9-during-backup-in-onedrive/7466/5

Please sign in to comment.
You can’t perform that action at this time.