diff --git a/build/Version.props b/build/Version.props index d6c8e492e3..a06f8924e2 100644 --- a/build/Version.props +++ b/build/Version.props @@ -7,7 +7,7 @@ 4.0.0 9.3.0 9.3.0 - 10.3.0 + 10.4.0 6.0.4 5.3.0 1.1.1 diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index c155355b7e..2915a42865 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -30,7 +30,7 @@ namespace Tgstation.Server.Client /// 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 /// /// Backing field for . /// - ApiHeaders headers; + ApiHeaders? headers; /// /// Get the to use. @@ -131,11 +131,11 @@ namespace Tgstation.Server.Client /// The value of . /// The value of . /// The value of . - 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(); @@ -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)); diff --git a/src/Tgstation.Server.Client/ApiClientFactory.cs b/src/Tgstation.Server.Client/ApiClientFactory.cs index 26881d2d26..1e9e0f64ac 100644 --- a/src/Tgstation.Server.Client/ApiClientFactory.cs +++ b/src/Tgstation.Server.Client/ApiClientFactory.cs @@ -8,6 +8,6 @@ namespace Tgstation.Server.Client sealed class ApiClientFactory : IApiClientFactory { /// - 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); } } diff --git a/src/Tgstation.Server.Client/IApiClientFactory.cs b/src/Tgstation.Server.Client/IApiClientFactory.cs index 8e5b7d704b..077802e254 100644 --- a/src/Tgstation.Server.Client/IApiClientFactory.cs +++ b/src/Tgstation.Server.Client/IApiClientFactory.cs @@ -16,6 +16,6 @@ namespace Tgstation.Server.Client /// The for the . /// The to use to generate a new . /// A new . - IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders); + IApiClient CreateApiClient(Uri url, ApiHeaders? apiHeaders, ApiHeaders? tokenRefreshHeaders); } } diff --git a/src/Tgstation.Server.Client/IServerClientFactory.cs b/src/Tgstation.Server.Client/IServerClientFactory.cs index 0216fa0e4e..69784ec765 100644 --- a/src/Tgstation.Server.Client/IServerClientFactory.cs +++ b/src/Tgstation.Server.Client/IServerClientFactory.cs @@ -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 { /// - /// Create a . + /// Gets the for a given . + /// + /// The URL to access TGS. + /// Optional s. + /// Optional representing timeout for the HTTP request. + /// Optional for the operation. + /// A resulting in the . + Task GetServerInformation( + Uri host, + IEnumerable? requestLoggers = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default); + + /// + /// Create a using a password login. /// /// The URL to access TGS. /// The username to for the . @@ -32,6 +47,24 @@ namespace Tgstation.Server.Client bool attemptLoginRefresh = true, CancellationToken cancellationToken = default); + /// + /// Create a using am OAuth login. + /// + /// The URL to access TGS. + /// The OAuth code used to complete the flow. + /// The . + /// Optional initial s to add to the . + /// Optional representing timeout for the connection. + /// Optional for the operation. + /// A resulting in a new . + Task CreateFromOAuth( + Uri host, + string oAuthCode, + OAuthProvider oAuthProvider, + IEnumerable? requestLoggers = null, + TimeSpan? timeout = null, + CancellationToken cancellationToken = default); + /// /// Create a . /// diff --git a/src/Tgstation.Server.Client/ServerClientFactory.cs b/src/Tgstation.Server.Client/ServerClientFactory.cs index 4acd77d8ee..c3ed8ed17d 100644 --- a/src/Tgstation.Server.Client/ServerClientFactory.cs +++ b/src/Tgstation.Server.Client/ServerClientFactory.cs @@ -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 } /// - public async Task CreateFromLogin( + public Task 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(); - - 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(Routes.Root, cancellationToken).ConfigureAwait(false); - } + /// + public Task CreateFromOAuth( + Uri host, + string oAuthCode, + OAuthProvider oAuthProvider, + IEnumerable? 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); } /// @@ -90,5 +96,71 @@ namespace Tgstation.Server.Client return new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer), null), token); } + + /// + public async Task GetServerInformation( + Uri host, + IEnumerable? 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(Routes.Root, cancellationToken).ConfigureAwait(false); + } + + /// + /// Creates a from a login operation. + /// + /// The URL to access TGS. + /// The to use for the login operation. + /// Optional initial s to add to the . + /// Optional representing timeout for the connection. + /// If may be used to re-login in the future. + /// Optional for the operation. + /// A resulting in a new . + async Task CreateWithNewToken( + Uri host, + ApiHeaders loginHeaders, + IEnumerable? requestLoggers, + TimeSpan? timeout, + bool attemptLoginRefresh, + CancellationToken cancellationToken) + { + requestLoggers ??= Enumerable.Empty(); + + 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(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; + } } } diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index cdaaf000b2..72003fcec5 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -16,7 +16,7 @@ https://github.com/tgstation/tgstation-server 2018 json web api tgstation-server tgstation ss13 byond client - Fixed automatic refresh erroring internally. Fixed request loggers given to ServerClientFactory not being passed to the IServerClient. + Added OAuth login methods to IServerClientFactory. true snupkg ../../build/analyzers.ruleset