Added OAuth login methods to client[NugetDeploy][TGSDeploy]

This commit is contained in:
Jordan Brown
2021-10-07 15:13:18 -04:00
parent 575484b124
commit cd71d77841
7 changed files with 144 additions and 36 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
<TgsConfigVersion>4.0.0</TgsConfigVersion>
<TgsApiVersion>9.3.0</TgsApiVersion>
<TgsApiLibraryVersion>9.3.0</TgsApiLibraryVersion>
<TgsClientVersion>10.3.0</TgsClientVersion>
<TgsClientVersion>10.4.0</TgsClientVersion>
<TgsDmapiVersion>6.0.4</TgsDmapiVersion>
<TgsInteropVersion>5.3.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.1.1</TgsHostWatchdogVersion>
+9 -6
View File
@@ -30,7 +30,7 @@ namespace Tgstation.Server.Client
/// <inheritdoc />
public ApiHeaders Headers
{
get => headers;
get => headers ?? throw new InvalidOperationException("ApiClient constructed without headers!");
set => headers = value ?? throw new InvalidOperationException("Cannot set null headers!");
}
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Client
/// <summary>
/// Backing field for <see cref="Headers"/>.
/// </summary>
ApiHeaders headers;
ApiHeaders? headers;
/// <summary>
/// Get the <see cref="JsonSerializerSettings"/> to use.
@@ -131,11 +131,11 @@ namespace Tgstation.Server.Client
/// <param name="url">The value of <see cref="Url"/>.</param>
/// <param name="apiHeaders">The value of <see cref="Headers"/>.</param>
/// <param name="tokenRefreshHeaders">The value of <see cref="tokenRefreshHeaders"/>.</param>
public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders)
public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders? apiHeaders, ApiHeaders? tokenRefreshHeaders)
{
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
Url = url ?? throw new ArgumentNullException(nameof(url));
headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
headers = apiHeaders;
this.tokenRefreshHeaders = tokenRefreshHeaders;
requestLoggers = new List<IRequestLogger>();
@@ -257,6 +257,9 @@ namespace Tgstation.Server.Client
if (tokenRefreshHeaders == null)
return false;
if (headers == null)
throw new InvalidOperationException("Cannot refresh token with no headers!");
var startingToken = headers.Token;
await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
try
@@ -351,8 +354,8 @@ namespace Tgstation.Server.Client
if (content != null)
request.Content = content;
var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers;
headersToUse.SetRequestHeaders(request.Headers, instanceId);
var headersToUse = tokenRefresh ? tokenRefreshHeaders : headers;
headersToUse?.SetRequestHeaders(request.Headers, instanceId);
if (fileDownload)
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Octet));
@@ -8,6 +8,6 @@ namespace Tgstation.Server.Client
sealed class ApiClientFactory : IApiClientFactory
{
/// <inheritdoc />
public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders) => new ApiClient(new HttpClientImplementation(), url, apiHeaders, tokenRefreshHeaders);
public IApiClient CreateApiClient(Uri url, ApiHeaders? apiHeaders, ApiHeaders? tokenRefreshHeaders) => new ApiClient(new HttpClientImplementation(), url, apiHeaders, tokenRefreshHeaders);
}
}
@@ -16,6 +16,6 @@ namespace Tgstation.Server.Client
/// <param name="apiHeaders">The <see cref="ApiHeaders"/> for the <see cref="IApiClient"/>.</param>
/// <param name="tokenRefreshHeaders">The <see cref="ApiHeaders"/> to use to generate a new <see cref="Api.Models.Response.TokenResponse"/>.</param>
/// <returns>A new <see cref="IApiClient"/>.</returns>
IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders);
IApiClient CreateApiClient(Uri url, ApiHeaders? apiHeaders, ApiHeaders? tokenRefreshHeaders);
}
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
namespace Tgstation.Server.Client
@@ -13,7 +14,21 @@ namespace Tgstation.Server.Client
public interface IServerClientFactory
{
/// <summary>
/// Create a <see cref="IServerClient"/>.
/// Gets the <see cref="ServerInformationResponse"/> for a given <paramref name="host"/>.
/// </summary>
/// <param name="host">The URL to access TGS.</param>
/// <param name="requestLoggers">Optional <see cref="IRequestLogger"/>s.</param>
/// <param name="timeout">Optional <see cref="TimeSpan"/> representing timeout for the HTTP request.</param>
/// <param name="cancellationToken">Optional <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="ServerInformationResponse"/>.</returns>
Task<ServerInformationResponse> GetServerInformation(
Uri host,
IEnumerable<IRequestLogger>? requestLoggers = null,
TimeSpan? timeout = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Create a <see cref="IServerClient"/> using a password login.
/// </summary>
/// <param name="host">The URL to access TGS.</param>
/// <param name="username">The username to for the <see cref="IServerClient"/>.</param>
@@ -32,6 +47,24 @@ namespace Tgstation.Server.Client
bool attemptLoginRefresh = true,
CancellationToken cancellationToken = default);
/// <summary>
/// Create a <see cref="IServerClient"/> using am OAuth login.
/// </summary>
/// <param name="host">The URL to access TGS.</param>
/// <param name="oAuthCode">The OAuth code used to complete the flow.</param>
/// <param name="oAuthProvider">The <see cref="OAuthProvider"/>.</param>
/// <param name="requestLoggers">Optional initial <see cref="IRequestLogger"/>s to add to the <see cref="IServerClient"/>.</param>
/// <param name="timeout">Optional <see cref="TimeSpan"/> representing timeout for the connection.</param>
/// <param name="cancellationToken">Optional <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IServerClient"/>.</returns>
Task<IServerClient> CreateFromOAuth(
Uri host,
string oAuthCode,
OAuthProvider oAuthProvider,
IEnumerable<IRequestLogger>? requestLoggers = null,
TimeSpan? timeout = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Create a <see cref="IServerClient"/>.
/// </summary>
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
namespace Tgstation.Server.Client
@@ -33,7 +34,7 @@ namespace Tgstation.Server.Client
}
/// <inheritdoc />
public async Task<IServerClient> CreateFromLogin(
public Task<IServerClient> CreateFromLogin(
Uri host,
string username,
string password,
@@ -49,33 +50,38 @@ namespace Tgstation.Server.Client
if (password == null)
throw new ArgumentNullException(nameof(password));
requestLoggers ??= Enumerable.Empty<IRequestLogger>();
TokenResponse token;
var loginHeaders = new ApiHeaders(productHeaderValue, username, password);
using (var api = ApiClientFactory.CreateApiClient(host, loginHeaders, null))
{
foreach (var requestLogger in requestLoggers)
api.AddRequestLogger(requestLogger);
return CreateWithNewToken(
host,
loginHeaders,
requestLoggers,
timeout,
false,
cancellationToken);
}
if (timeout.HasValue)
api.Timeout = timeout.Value;
token = await api.Update<TokenResponse>(Routes.Root, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public Task<IServerClient> CreateFromOAuth(
Uri host,
string oAuthCode,
OAuthProvider oAuthProvider,
IEnumerable<IRequestLogger>? requestLoggers = null,
TimeSpan? timeout = null,
CancellationToken cancellationToken = default)
{
if (host == null)
throw new ArgumentNullException(nameof(host));
if (oAuthCode == null)
throw new ArgumentNullException(nameof(oAuthCode));
if (!attemptLoginRefresh)
loginHeaders = null;
var apiHeaders = new ApiHeaders(productHeaderValue, token.Bearer!);
var client = new ServerClient(ApiClientFactory.CreateApiClient(host, apiHeaders, loginHeaders), token);
if (timeout.HasValue)
client.Timeout = timeout.Value;
foreach (var requestLogger in requestLoggers)
client.AddRequestLogger(requestLogger);
return client;
var loginHeaders = new ApiHeaders(productHeaderValue, oAuthCode, oAuthProvider);
return CreateWithNewToken(
host,
loginHeaders,
requestLoggers,
timeout,
false,
cancellationToken);
}
/// <inheritdoc />
@@ -90,5 +96,71 @@ namespace Tgstation.Server.Client
return new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer), null), token);
}
/// <inheritdoc />
public async Task<ServerInformationResponse> GetServerInformation(
Uri host,
IEnumerable<IRequestLogger>? requestLoggers = null,
TimeSpan? timeout = null,
CancellationToken cancellationToken = default)
{
using var api = ApiClientFactory.CreateApiClient(host, null, null);
if (requestLoggers != null)
foreach (var requestLogger in requestLoggers)
api.AddRequestLogger(requestLogger);
if (timeout.HasValue)
api.Timeout = timeout.Value;
return await api.Read<ServerInformationResponse>(Routes.Root, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Creates a <see cref="IServerClient"/> from a login operation.
/// </summary>
/// <param name="host">The URL to access TGS.</param>
/// <param name="loginHeaders">The <see cref="ApiHeaders"/> to use for the login operation.</param>
/// <param name="requestLoggers">Optional initial <see cref="IRequestLogger"/>s to add to the <see cref="IServerClient"/>.</param>
/// <param name="timeout">Optional <see cref="TimeSpan"/> representing timeout for the connection.</param>
/// <param name="attemptLoginRefresh">If <paramref name="loginHeaders"/> may be used to re-login in the future.</param>
/// <param name="cancellationToken">Optional <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IServerClient"/>.</returns>
async Task<IServerClient> CreateWithNewToken(
Uri host,
ApiHeaders loginHeaders,
IEnumerable<IRequestLogger>? requestLoggers,
TimeSpan? timeout,
bool attemptLoginRefresh,
CancellationToken cancellationToken)
{
requestLoggers ??= Enumerable.Empty<IRequestLogger>();
TokenResponse token;
using (var api = ApiClientFactory.CreateApiClient(host, loginHeaders, null))
{
foreach (var requestLogger in requestLoggers)
api.AddRequestLogger(requestLogger);
if (timeout.HasValue)
api.Timeout = timeout.Value;
token = await api.Update<TokenResponse>(Routes.Root, cancellationToken).ConfigureAwait(false);
}
var apiHeaders = new ApiHeaders(productHeaderValue, token.Bearer!);
var client = new ServerClient(
ApiClientFactory.CreateApiClient(
host,
apiHeaders,
attemptLoginRefresh ? loginHeaders : null),
token);
if (timeout.HasValue)
client.Timeout = timeout.Value;
foreach (var requestLogger in requestLoggers)
client.AddRequestLogger(requestLogger);
return client;
}
}
}
@@ -16,7 +16,7 @@
<RepositoryUrl>https://github.com/tgstation/tgstation-server</RepositoryUrl>
<Copyright>2018</Copyright>
<PackageTags>json web api tgstation-server tgstation ss13 byond client</PackageTags>
<PackageReleaseNotes>Fixed automatic refresh erroring internally. Fixed request loggers given to ServerClientFactory not being passed to the IServerClient.</PackageReleaseNotes>
<PackageReleaseNotes>Added OAuth login methods to IServerClientFactory.</PackageReleaseNotes>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>