mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-26 14:37:44 +01:00
Added OAuth login methods to client[NugetDeploy][TGSDeploy]
This commit is contained in:
+1
-1
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user