diff --git a/src/Tgstation.Server.Host/Authority/Core/GraphQLAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/Core/GraphQLAuthorityInvoker{TAuthority}.cs
index f160d48af6..e5eacacafa 100644
--- a/src/Tgstation.Server.Host/Authority/Core/GraphQLAuthorityInvoker{TAuthority}.cs
+++ b/src/Tgstation.Server.Host/Authority/Core/GraphQLAuthorityInvoker{TAuthority}.cs
@@ -85,7 +85,28 @@ namespace Tgstation.Server.Host.Authority.Core
if (result == null)
return default;
- return result.ToApi();
+ var transformedResult = new TTransformer().CompiledExpression(result);
+ return transformedResult;
+ }
+
+ ///
+ async ValueTask IGraphQLAuthorityInvoker.InvokeTransformableAllowMissing(
+ Func>> authorityInvoker,
+ QueryContext? queryContext)
+ where TApiModel : default
+ {
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+
+ var requirementsGate = authorityInvoker(Authority);
+ var projectable = await ExecuteIfRequirementsSatisfied(requirementsGate);
+ var authorityResponse = await projectable.Resolve(
+ queryable => queryable
+ .Select(new TTransformer().ProjectedExpression)
+ .With(queryContext));
+ ThrowGraphQLErrorIfNecessary(authorityResponse, false);
+ var result = authorityResponse.Result;
+
+ return result;
}
///
@@ -102,7 +123,10 @@ namespace Tgstation.Server.Host.Authority.Core
queryable = preTransformer(queryable);
if (typeof(EntityId).IsAssignableFrom(typeof(TResult)))
- queryable = queryable.OrderBy(item => ((EntityId)(object)item).Id!.Value); // order by ID to fix an EFCore warning
+ queryable = queryable
+ .Cast()
+ .OrderBy(item => item.Id!.Value) // order by ID to fix an EFCore warning
+ .Cast();
var expression = new TTransformer().Expression;
return queryable
@@ -148,6 +172,13 @@ namespace Tgstation.Server.Host.Authority.Core
=> await ((IGraphQLAuthorityInvoker)this).InvokeTransformableAllowMissing(authorityInvoker)
?? throw new InvalidOperationException("Authority invocation should have returned a non-nullable result!");
+ ///
+ async ValueTask IGraphQLAuthorityInvoker.InvokeTransformable(
+ Func>> authorityInvoker,
+ QueryContext? queryContext)
+ => await ((IGraphQLAuthorityInvoker)this).InvokeTransformable(authorityInvoker, queryContext)
+ ?? throw new InvalidOperationException("Authority invocation should have returned a non-nullable result!");
+
///
protected override void OnRequirementsFailure(AuthorizationFailure authFailure)
=> throw authFailure.ForbiddenGraphQLException();
diff --git a/src/Tgstation.Server.Host/Authority/Core/RestAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/Core/RestAuthorityInvoker{TAuthority}.cs
index f0ac777801..70c52deede 100644
--- a/src/Tgstation.Server.Host/Authority/Core/RestAuthorityInvoker{TAuthority}.cs
+++ b/src/Tgstation.Server.Host/Authority/Core/RestAuthorityInvoker{TAuthority}.cs
@@ -129,5 +129,31 @@ namespace Tgstation.Server.Host.Authority.Core
return CreateSuccessfulActionResult(controller, result => result.ToApi(), authorityResponse!);
}
+
+ ///
+ async ValueTask IRestAuthorityInvoker.InvokeTransformable(
+ ApiController controller,
+ Func>> authorityInvoker)
+ {
+ ArgumentNullException.ThrowIfNull(controller);
+ ArgumentNullException.ThrowIfNull(authorityInvoker);
+
+ var requirementsGate = authorityInvoker(Authority);
+ var authorityResponse = await ExecuteIfRequirementsSatisfied(requirementsGate);
+ var erroredResult = CreateErroredActionResult(controller, authorityResponse);
+ if (erroredResult != null)
+ return erroredResult;
+
+ return CreateSuccessfulActionResult(controller, result => new TTransformer().CompiledExpression(result), authorityResponse!);
+ }
+
+ ///
+ async ValueTask IRestAuthorityInvoker.InvokeTransformable(
+ ApiController controller,
+ Func>> authorityInvoker)
+ {
+ await Task.Yield();
+ throw new NotImplementedException();
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Authority/IGraphQLAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/IGraphQLAuthorityInvoker{TAuthority}.cs
index 12d044844a..d3cc6e9146 100644
--- a/src/Tgstation.Server.Host/Authority/IGraphQLAuthorityInvoker{TAuthority}.cs
+++ b/src/Tgstation.Server.Host/Authority/IGraphQLAuthorityInvoker{TAuthority}.cs
@@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Authority
/// The .
/// The resulting of the return value.
/// The resulting in the .
- /// A resulting in the generated for the resulting .
+ /// A resulting in the generated for the resulting if any.
ValueTask InvokeAllowMissing(Func>> authorityInvoker)
where TResult : TApiModel
where TApiModel : notnull;
@@ -45,9 +45,25 @@ namespace Tgstation.Server.Host.Authority
/// The resulting of the return value.
/// The for converting s to s.
/// The resulting in the .
- /// A resulting in the generated for the resulting .
+ /// A resulting in the generated for the resulting if any.
ValueTask InvokeTransformableAllowMissing(Func>> authorityInvoker)
- where TResult : notnull, IApiTransformable
+ where TResult : notnull
+ where TApiModel : notnull
+ where TTransformer : ITransformer, new();
+
+ ///
+ /// Invoke a method and get the non-nullable result.
+ ///
+ /// The .
+ /// The resulting of the return value.
+ /// The for converting s to s.
+ /// The resulting in the .
+ /// The active .
+ /// A resulting in the generated for the resulting if any.
+ ValueTask InvokeTransformableAllowMissing(
+ Func>> authorityInvoker,
+ QueryContext? queryContext)
+ where TResult : EntityId
where TApiModel : notnull
where TTransformer : ITransformer, new();
@@ -71,7 +87,23 @@ namespace Tgstation.Server.Host.Authority
/// The resulting in the .
/// A resulting in the generated for the resulting .
ValueTask InvokeTransformable(Func>> authorityInvoker)
- where TResult : notnull, IApiTransformable
+ where TResult : notnull
+ where TApiModel : notnull
+ where TTransformer : ITransformer, new();
+
+ ///
+ /// Invoke a method and get the non-nullable result.
+ ///
+ /// The .
+ /// The resulting of the return value.
+ /// The for converting s to s.
+ /// The resulting in the .
+ /// The active .
+ /// A resulting in the generated for the resulting .
+ ValueTask InvokeTransformable(
+ Func>> authorityInvoker,
+ QueryContext? queryContext)
+ where TResult : EntityId
where TApiModel : notnull
where TTransformer : ITransformer, new();
@@ -87,7 +119,6 @@ namespace Tgstation.Server.Host.Authority
ValueTask> InvokeTransformableQueryable(
Func>> authorityInvoker,
Func, IQueryable>? preTransformer = null)
- where TResult : IApiTransformable
where TApiModel : notnull
where TTransformer : ITransformer, new();
@@ -105,7 +136,7 @@ namespace Tgstation.Server.Host.Authority
Func>> authorityInvoker,
IReadOnlyList ids,
QueryContext>? queryContext)
- where TResult : EntityId, IApiTransformable
+ where TResult : EntityId
where TApiModel : Entity
where TTransformer : ITransformer, new();
}
diff --git a/src/Tgstation.Server.Host/Authority/ILoginAuthority.cs b/src/Tgstation.Server.Host/Authority/ILoginAuthority.cs
index 111d83271f..527cf41b85 100644
--- a/src/Tgstation.Server.Host/Authority/ILoginAuthority.cs
+++ b/src/Tgstation.Server.Host/Authority/ILoginAuthority.cs
@@ -15,8 +15,8 @@ namespace Tgstation.Server.Host.Authority
/// Attempt to login to the server with the current Basic or OAuth credentials.
///
/// The for the operation.
- /// A resulting in a .
- RequirementsGated> AttemptLogin(CancellationToken cancellationToken);
+ /// A resulting in an authenticated token .
+ RequirementsGated> AttemptLogin(CancellationToken cancellationToken);
///
/// Attempt to login to an OAuth service with the current OAuth credentials.
diff --git a/src/Tgstation.Server.Host/Authority/IRestAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/IRestAuthorityInvoker{TAuthority}.cs
index 0e3b6f3b14..3b993ded6c 100644
--- a/src/Tgstation.Server.Host/Authority/IRestAuthorityInvoker{TAuthority}.cs
+++ b/src/Tgstation.Server.Host/Authority/IRestAuthorityInvoker{TAuthority}.cs
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
+using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Authority.Core;
using Tgstation.Server.Host.Controllers;
using Tgstation.Server.Host.Models;
@@ -47,5 +48,33 @@ namespace Tgstation.Server.Host.Authority
ValueTask InvokeTransformable(ApiController controller, Func>> authorityInvoker)
where TResult : notnull, ILegacyApiTransformable
where TApiModel : notnull;
+
+ ///
+ /// Invoke a method and get the result.
+ ///
+ /// The .
+ /// The returned REST .
+ /// The for converting s to s.
+ /// The invoking the .
+ /// The resulting in the .
+ /// A resulting in the generated for the resulting .
+ ValueTask InvokeTransformable(ApiController controller, Func>> authorityInvoker)
+ where TResult : notnull
+ where TApiModel : notnull
+ where TTransformer : ITransformer, new();
+
+ ///
+ /// Invoke a method and get the result.
+ ///
+ /// The .
+ /// The returned REST .
+ /// The for converting s to s.
+ /// The invoking the .
+ /// The resulting in the .
+ /// A resulting in the generated for the resulting .
+ ValueTask InvokeTransformable(ApiController controller, Func>> authorityInvoker)
+ where TResult : EntityId
+ where TApiModel : notnull
+ where TTransformer : ITransformer, new();
}
}
diff --git a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs
index 4bb1fa17eb..395727833d 100644
--- a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs
+++ b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs
@@ -13,33 +13,16 @@ namespace Tgstation.Server.Host.Authority
///
public interface IUserAuthority : IAuthority
{
- ///
- /// Gets the currently authenticated user.
- ///
- /// The for the operation.
- /// A .
- RequirementsGated> Read(CancellationToken cancellationToken);
-
- ///
- /// Gets the with a given .
- ///
- /// The of the .
- /// If related entities should be loaded.
- /// If the may be returned.
- /// The for the operation.
- /// A .
- RequirementsGated> GetId(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken);
-
///
/// Gets the with a given .
///
/// The result type after projection.
/// The of the .
- /// If the may be returned.
+ /// If the may be returned or will result in a response.
/// The for the operation.
/// A for .
RequirementsGated> GetId(long id, bool allowSystemUser, CancellationToken cancellationToken)
- where TResult : class;
+ where TResult : notnull;
///
/// Gets the s for the with a given .
diff --git a/src/Tgstation.Server.Host/Authority/LoginAuthority.cs b/src/Tgstation.Server.Host/Authority/LoginAuthority.cs
index 1d1ba896db..f540ce6a4b 100644
--- a/src/Tgstation.Server.Host/Authority/LoginAuthority.cs
+++ b/src/Tgstation.Server.Host/Authority/LoginAuthority.cs
@@ -15,7 +15,6 @@ using Tgstation.Server.Host.Authority.Core;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
-using Tgstation.Server.Host.GraphQL.Transformers;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Security.OAuth;
@@ -140,7 +139,7 @@ namespace Tgstation.Server.Host.Authority
}
///
- public RequirementsGated> AttemptLogin(CancellationToken cancellationToken)
+ public RequirementsGated> AttemptLogin(CancellationToken cancellationToken)
=> new(
() => null,
() => AttemptLoginImpl(cancellationToken),
@@ -177,19 +176,19 @@ namespace Tgstation.Server.Host.Authority
/// Login process.
///
/// The for the operation.
- /// A resulting in the for the .
- private async ValueTask> AttemptLoginImpl(CancellationToken cancellationToken)
+ /// A resulting in the containing the authenticated bearer token.
+ private async ValueTask> AttemptLoginImpl(CancellationToken cancellationToken)
{
// password and oauth logins disabled
if (securityConfigurationOptions.Value.OidcStrictMode)
- return Unauthorized();
+ return Unauthorized();
var headers = apiHeadersProvider.ApiHeaders;
if (headers == null)
- return GenerateHeadersExceptionResponse(apiHeadersProvider.HeadersException!);
+ return GenerateHeadersExceptionResponse(apiHeadersProvider.HeadersException!);
if (headers.IsTokenAuthentication)
- return BadRequest(ErrorCode.TokenWithToken);
+ return BadRequest(ErrorCode.TokenWithToken);
var oAuthLogin = headers.OAuthProvider.HasValue;
@@ -212,7 +211,7 @@ namespace Tgstation.Server.Host.Authority
if (oAuthLogin)
{
var oAuthProvider = headers.OAuthProvider!.Value;
- var (errorResponse, oauthResult) = await TryOAuthenticate(headers, oAuthProvider, true, cancellationToken);
+ var (errorResponse, oauthResult) = await TryOAuthenticate(headers, oAuthProvider, true, cancellationToken);
if (errorResponse != null)
return errorResponse;
@@ -225,7 +224,7 @@ namespace Tgstation.Server.Host.Authority
{
var canonicalUserName = User.CanonicalizeName(headers.Username!);
if (canonicalUserName == User.CanonicalizeName(User.TgsSystemUserName))
- return Unauthorized();
+ return Unauthorized();
if (systemIdentity == null)
query = query.Where(x => x.CanonicalName == canonicalUserName);
@@ -237,7 +236,7 @@ namespace Tgstation.Server.Host.Authority
// No user? You're not allowed
if (user == null)
- return Unauthorized();
+ return Unauthorized();
// A system user may have had their name AND password changed to one in our DB...
// Or a DB user was created that had the same user/pass as a system user
@@ -252,7 +251,7 @@ namespace Tgstation.Server.Host.Authority
{
// DB User password check and update
if (!isLikelyDbUser || !cryptographySuite.CheckUserPassword(user, headers.Password!))
- return Unauthorized();
+ return Unauthorized();
if (user.PasswordHash != originalHash)
{
Logger.LogDebug("User ID {userId}'s password hash needs a refresh, updating database.", user.Id);
@@ -295,22 +294,17 @@ namespace Tgstation.Server.Host.Authority
if (!user.Enabled!.Value)
{
Logger.LogTrace("Not logging in disabled user {userId}.", user.Id);
- return Forbid();
+ return Forbid();
}
- var token = tokenFactory.CreateToken(user, oAuthLogin);
- var payload = new LoginResult
- {
- Bearer = token,
- User = ((IApiTransformable)user).ToApi(),
- };
+ var (token, expiresAt) = tokenFactory.CreateToken(user, oAuthLogin);
if (usingSystemIdentity)
- await CacheSystemIdentity(systemIdentity!, user, payload);
+ await CacheSystemIdentity(systemIdentity!, user, expiresAt);
Logger.LogDebug("Successfully logged in user {userId}!", user.Id);
- return new AuthorityResponse(payload);
+ return new AuthorityResponse(token);
}
}
@@ -319,12 +313,12 @@ namespace Tgstation.Server.Host.Authority
///
/// The to cache.
/// The the was generated for.
- /// The for the successful login.
+ /// When the user's session exipres.
/// A representing the running operation.
- private async ValueTask CacheSystemIdentity(ISystemIdentity systemIdentity, User user, LoginResult loginPayload)
+ private async ValueTask CacheSystemIdentity(ISystemIdentity systemIdentity, User user, DateTimeOffset validTo)
{
// expire the identity slightly after the auth token in case of lag
- var identExpiry = loginPayload.ToApi().ParseJwt().ValidTo;
+ var identExpiry = validTo;
identExpiry += tokenFactory.ValidationParameters.ClockSkew;
identExpiry += TimeSpan.FromSeconds(15);
await identityCache.CacheSystemIdentity(user, systemIdentity!, identExpiry);
diff --git a/src/Tgstation.Server.Host/Authority/UserAuthority.cs b/src/Tgstation.Server.Host/Authority/UserAuthority.cs
index 4d404c7a3c..5784dbd07b 100644
--- a/src/Tgstation.Server.Host/Authority/UserAuthority.cs
+++ b/src/Tgstation.Server.Host/Authority/UserAuthority.cs
@@ -24,7 +24,6 @@ using Tgstation.Server.Host.Authority.Core;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
-using Tgstation.Server.Host.GraphQL.Transformers;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Security.RightsEvaluation;
@@ -229,27 +228,6 @@ namespace Tgstation.Server.Host.Authority
return failResponse != null;
}
- ///
- public RequirementsGated> Read(CancellationToken cancellationToken)
- => new(
- () => Enumerable.Empty(),
- () => GetIdImpl(claimsPrincipalAccessor.User.RequireTgsUserId(), true, false, cancellationToken));
-
- ///
- public RequirementsGated> GetId(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken)
- => new(
- () =>
- {
- if (id != claimsPrincipalAccessor.User.GetTgsUserId())
- return Enumerable.Empty();
-
- return new List
- {
- Flag(AdministrationRights.ReadUsers),
- };
- },
- () => GetIdImpl(id, includeJoins, allowSystemUser, cancellationToken));
-
///
public RequirementsGated> Queryable(bool includeJoins)
=> new(
@@ -565,7 +543,7 @@ namespace Tgstation.Server.Host.Authority
///
public RequirementsGated> GetId(long id, bool allowSystemUser, CancellationToken cancellationToken)
- where TResult : class
+ where TResult : notnull
=> new(
() =>
{
@@ -600,37 +578,6 @@ namespace Tgstation.Server.Host.Authority
},
cancellationToken)));
- ///
- /// Implementation of retrieving a by ID.
- ///
- /// The of the user to retrieve.
- /// If related entities should be loaded.
- /// If the may be returned.
- /// The for the operation.
- /// A .
- async ValueTask> GetIdImpl(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken)
- {
- User? user;
- if (includeJoins)
- {
- var queryable = Queryable(true, true);
-
- user = await queryable.FirstOrDefaultAsync(
- dbModel => dbModel.Id == id,
- cancellationToken);
- }
- else
- user = await usersDataLoader.LoadAsync(id, cancellationToken);
-
- if (user == default)
- return NotFound();
-
- if (!allowSystemUser && user.CanonicalName == User.CanonicalizeName(User.TgsSystemUserName))
- return Forbid();
-
- return new AuthorityResponse(user);
- }
-
///
/// Create the for an .
///
@@ -667,7 +614,7 @@ namespace Tgstation.Server.Host.Authority
user.Require(x => x.Id))
.Select(topic => topicEventSender.SendAsync(
topic,
- ((IApiTransformable)user).ToApi(),
+ ((IApiTransformable)user).ToApi(),
CancellationToken.None))); // DCT: Operation should always run
///
diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
index 1b9b20f198..b56022edac 100644
--- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
@@ -208,7 +208,6 @@ namespace Tgstation.Server.Host.Controllers
}));
}
},
- null,
page,
pageSize,
cancellationToken);
diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs
index 771972a0f7..dd61412501 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs
@@ -243,19 +243,45 @@ namespace Tgstation.Server.Host.Controllers
///
/// The of model being generated and returned.
/// A resulting in a resulting in the generated .
- /// Optional to transform the s after being queried.
/// The requested page from the query.
/// The requested page size from the query.
/// The for the operation.
/// A resulting in the for the operation.
protected ValueTask Paginated(
Func?>> queryGenerator,
- Func? resultTransformer,
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken) => PaginatedImpl(
queryGenerator,
- resultTransformer,
+ model => model,
+ null,
+ pageQuery,
+ pageSizeQuery,
+ cancellationToken);
+
+ ///
+ /// Generates a paginated response.
+ ///
+ /// The of model being generated.
+ /// The of model being returned.
+ /// The of the for /.
+ /// A resulting in a resulting in the generated .
+ /// The requested page from the query.
+ /// The requested page size from the query.
+ /// The for the operation.
+ /// A resulting in the for the operation.
+ protected ValueTask Paginated(
+ Func?>> queryGenerator,
+ int? pageQuery,
+ int? pageSizeQuery,
+ CancellationToken cancellationToken)
+ where TModel : IApiTransformable
+ where TApiModel : notnull
+ where TTransformer : ITransformer, new()
+ => PaginatedImpl(
+ queryGenerator,
+ model => model.ToApi(),
+ null,
pageQuery,
pageSizeQuery,
cancellationToken);
@@ -266,21 +292,22 @@ namespace Tgstation.Server.Host.Controllers
/// The of model being generated.
/// The of model being returned.
/// A resulting in a resulting in the generated .
- /// A to transform the s after being queried.
+ /// A to mutate the s after being queried.
/// The requested page from the query.
/// The requested page size from the query.
/// The for the operation.
/// A resulting in the for the operation.
protected ValueTask Paginated(
Func?>> queryGenerator,
- Func? resultTransformer,
+ Func? resultMutator,
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken)
where TModel : ILegacyApiTransformable
=> PaginatedImpl(
queryGenerator,
- resultTransformer,
+ model => model.ToApi(),
+ resultMutator,
pageQuery,
pageSizeQuery,
cancellationToken);
@@ -291,14 +318,16 @@ namespace Tgstation.Server.Host.Controllers
/// The of model being generated. If different from , must implement for .
/// The of model being returned.
/// A resulting in a resulting in the generated or if an authorization requirment failed.
- /// A to transform the s after being queried.
+ /// The conversion from to .
+ /// A to mutate the s after being queried.
/// The requested page from the query.
/// The requested page size from the query.
/// The for the operation.
/// A resulting in the for the operation.
async ValueTask PaginatedImpl(
Func?>> queryGenerator,
- Func? resultTransformer,
+ Func resultTransformer,
+ Func? resultMutator,
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken)
@@ -343,18 +372,13 @@ namespace Tgstation.Server.Host.Controllers
pagedResults = [.. queriedResults];
}
- ICollection finalResults;
- if (typeof(TResultModel).IsAssignableFrom(typeof(TModel)))
- finalResults = pagedResults.Cast().ToList(); // clearly a safe cast
- else
- finalResults = pagedResults
- .Cast>()
- .Select(x => x.ToApi())
- .ToList();
+ var finalResults = pagedResults
+ .Select(resultTransformer)
+ .ToList();
- if (resultTransformer != null)
+ if (resultMutator != null)
foreach (var finalResult in finalResults)
- await resultTransformer(finalResult);
+ await resultMutator(finalResult);
var carryTheOne = totalResults % pageSize != 0
? 1
diff --git a/src/Tgstation.Server.Host/Controllers/ApiRootController.cs b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs
index 148f5bbc7b..0163af7bc3 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiRootController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiRootController.cs
@@ -16,6 +16,7 @@ using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Configuration;
+using Tgstation.Server.Host.Controllers.Transformers;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
@@ -194,7 +195,7 @@ namespace Tgstation.Server.Host.Controllers
return ValueTask.FromResult(HeadersIssue(ApiHeadersProvider.HeadersException!));
}
- return loginAuthority.InvokeTransformable(this, authority => authority.AttemptLogin(cancellationToken));
+ return loginAuthority.InvokeTransformable(this, authority => authority.AttemptLogin(cancellationToken));
}
///
diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
index f060946784..5af21ca792 100644
--- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
@@ -204,7 +204,6 @@ namespace Tgstation.Server.Host.Controllers
}));
}
},
- null,
page,
pageSize,
cancellationToken));
diff --git a/src/Tgstation.Server.Host/Controllers/EngineController.cs b/src/Tgstation.Server.Host/Controllers/EngineController.cs
index 89a1ba8abe..eedd54bf2b 100644
--- a/src/Tgstation.Server.Host/Controllers/EngineController.cs
+++ b/src/Tgstation.Server.Host/Controllers/EngineController.cs
@@ -121,7 +121,6 @@ namespace Tgstation.Server.Host.Controllers
})
.AsQueryable()
.OrderBy(x => x.EngineVersion!.ToString()))),
- null,
page,
pageSize,
cancellationToken));
diff --git a/src/Tgstation.Server.Host/Controllers/Transformers/PermissionSetTransformer.cs b/src/Tgstation.Server.Host/Controllers/Transformers/PermissionSetTransformer.cs
new file mode 100644
index 0000000000..b6ab842c45
--- /dev/null
+++ b/src/Tgstation.Server.Host/Controllers/Transformers/PermissionSetTransformer.cs
@@ -0,0 +1,24 @@
+using Tgstation.Server.Api.Models;
+using Tgstation.Server.Api.Rights;
+
+namespace Tgstation.Server.Host.Controllers.Transformers
+{
+ ///
+ /// for s.
+ ///
+ sealed class PermissionSetTransformer : Models.TransformerBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PermissionSetTransformer()
+ : base(
+ model => new PermissionSet
+ {
+ AdministrationRights = model.AdministrationRights ?? NotNullFallback(),
+ InstanceManagerRights = model.InstanceManagerRights ?? NotNullFallback(),
+ })
+ {
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Controllers/Transformers/TokenResponseTransformer.cs b/src/Tgstation.Server.Host/Controllers/Transformers/TokenResponseTransformer.cs
new file mode 100644
index 0000000000..7b7851100f
--- /dev/null
+++ b/src/Tgstation.Server.Host/Controllers/Transformers/TokenResponseTransformer.cs
@@ -0,0 +1,23 @@
+using Tgstation.Server.Api.Models.Response;
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Controllers.Transformers
+{
+ ///
+ /// for s.
+ ///
+ sealed class TokenResponseTransformer : TransformerBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TokenResponseTransformer()
+ : base(
+ token => new TokenResponse
+ {
+ Bearer = token,
+ })
+ {
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Controllers/Transformers/UpdatedUserResponseTransformer.cs b/src/Tgstation.Server.Host/Controllers/Transformers/UpdatedUserResponseTransformer.cs
new file mode 100644
index 0000000000..f569b8c9e7
--- /dev/null
+++ b/src/Tgstation.Server.Host/Controllers/Transformers/UpdatedUserResponseTransformer.cs
@@ -0,0 +1,25 @@
+using Tgstation.Server.Api.Models.Response;
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Controllers.Transformers
+{
+ ///
+ /// for s to s.
+ ///
+ sealed class UpdatedUserResponseTransformer : TransformerBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UpdatedUserResponseTransformer()
+ : base(
+ BuildSubProjection(
+ (model, fullUser) => fullUser ?? new UserResponse
+ {
+ Id = model.Id,
+ },
+ model => model.User))
+ {
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Controllers/Transformers/UserGroupTransformer.cs b/src/Tgstation.Server.Host/Controllers/Transformers/UserGroupTransformer.cs
new file mode 100644
index 0000000000..285c7039de
--- /dev/null
+++ b/src/Tgstation.Server.Host/Controllers/Transformers/UserGroupTransformer.cs
@@ -0,0 +1,27 @@
+using Tgstation.Server.Api.Models;
+using Tgstation.Server.Api.Models.Internal;
+
+namespace Tgstation.Server.Host.Controllers.Transformers
+{
+ ///
+ /// for s.
+ ///
+ sealed class UserGroupTransformer : Models.TransformerBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UserGroupTransformer()
+ : base(
+ BuildSubProjection(
+ (model, permissionSet) => new UserGroup
+ {
+ Id = model.Id,
+ Name = model.Name,
+ PermissionSet = permissionSet,
+ },
+ model => model.PermissionSet))
+ {
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Controllers/Transformers/UserNameTransformer.cs b/src/Tgstation.Server.Host/Controllers/Transformers/UserNameTransformer.cs
new file mode 100644
index 0000000000..7023079ad0
--- /dev/null
+++ b/src/Tgstation.Server.Host/Controllers/Transformers/UserNameTransformer.cs
@@ -0,0 +1,24 @@
+using Tgstation.Server.Api.Models;
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Controllers.Transformers
+{
+ ///
+ /// for s.
+ ///
+ sealed class UserNameTransformer : TransformerBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UserNameTransformer()
+ : base(
+ model => new UserName
+ {
+ Id = model.Id,
+ Name = model.Name,
+ })
+ {
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Controllers/Transformers/UserResponseTransformer.cs b/src/Tgstation.Server.Host/Controllers/Transformers/UserResponseTransformer.cs
new file mode 100644
index 0000000000..866d2e561b
--- /dev/null
+++ b/src/Tgstation.Server.Host/Controllers/Transformers/UserResponseTransformer.cs
@@ -0,0 +1,64 @@
+using System.Linq;
+
+using Tgstation.Server.Api.Models;
+using Tgstation.Server.Api.Models.Internal;
+using Tgstation.Server.Api.Models.Response;
+
+namespace Tgstation.Server.Host.Controllers.Transformers
+{
+ ///
+ /// for s.
+ ///
+ sealed class UserResponseTransformer : Models.TransformerBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public UserResponseTransformer()
+ : base(
+ BuildSubProjection<
+ Models.UserGroup,
+ Models.PermissionSet,
+ Models.User,
+ UserGroup,
+ PermissionSet,
+ UserName,
+ UserGroupTransformer,
+ PermissionSetTransformer,
+ UserNameTransformer>(
+ (model, group, permissionSet, createdBy) => new UserResponse
+ {
+ Id = model.Id,
+ CreatedAt = model.CreatedAt,
+ OAuthConnections = model.OAuthConnections != null
+ ? model.OAuthConnections.Select(
+ oAuthConnection => new OAuthConnection
+ {
+ ExternalUserId = oAuthConnection.ExternalUserId,
+ Provider = oAuthConnection.Provider,
+ })
+ .ToList()
+ : null,
+ CreatedBy = createdBy,
+ Enabled = model.Enabled,
+ Group = group,
+ Name = model.Name,
+ OidcConnections = model.OidcConnections != null
+ ? model.OidcConnections.Select(
+ oAuthConnection => new OidcConnection
+ {
+ ExternalUserId = oAuthConnection.ExternalUserId,
+ SchemeKey = oAuthConnection.SchemeKey,
+ })
+ .ToList()
+ : null,
+ PermissionSet = permissionSet,
+ SystemIdentifier = model.SystemIdentifier,
+ },
+ model => model.Group,
+ model => model.PermissionSet,
+ model => model.CreatedBy))
+ {
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs
index fc858c41bd..73537ba942 100644
--- a/src/Tgstation.Server.Host/Controllers/UserController.cs
+++ b/src/Tgstation.Server.Host/Controllers/UserController.cs
@@ -14,7 +14,9 @@ using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.Controllers.Results;
+using Tgstation.Server.Host.Controllers.Transformers;
using Tgstation.Server.Host.Database;
+using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Utils;
@@ -33,18 +35,25 @@ namespace Tgstation.Server.Host.Controllers
///
readonly IRestAuthorityInvoker userAuthority;
+ ///
+ /// The for the request.
+ ///
+ readonly IClaimsPrincipalAccessor claimsPrincipalAccessor;
+
///
/// Initializes a new instance of the class.
///
/// The for the .
/// The for the .
/// The value of .
+ /// The value of .
/// The for the .
/// The for the .
public UserController(
IDatabaseContext databaseContext,
IAuthenticationContext authenticationContext,
IRestAuthorityInvoker userAuthority,
+ IClaimsPrincipalAccessor claimsPrincipalAccessor,
ILogger logger,
IApiHeadersProvider apiHeaders)
: base(
@@ -55,6 +64,7 @@ namespace Tgstation.Server.Host.Controllers
true)
{
this.userAuthority = userAuthority ?? throw new ArgumentNullException(nameof(userAuthority));
+ this.claimsPrincipalAccessor = claimsPrincipalAccessor ?? throw new ArgumentNullException(nameof(claimsPrincipalAccessor));
}
///
@@ -68,7 +78,7 @@ namespace Tgstation.Server.Host.Controllers
[HttpPut]
[ProducesResponseType(typeof(UserResponse), 201)]
public ValueTask Create([FromBody] UserCreateRequest model, CancellationToken cancellationToken)
- => userAuthority.InvokeTransformable(this, authority => authority.Create(model, null, cancellationToken));
+ => userAuthority.InvokeTransformable(this, authority => authority.Create(model, null, cancellationToken));
///
/// Update a .
@@ -86,7 +96,7 @@ namespace Tgstation.Server.Host.Controllers
[ProducesResponseType(typeof(ErrorMessageResponse), 404)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
public ValueTask Update([FromBody] UserUpdateRequest model, CancellationToken cancellationToken)
- => userAuthority.InvokeTransformable(this, authority => authority.Update(model, cancellationToken));
+ => userAuthority.InvokeTransformable(this, authority => authority.Update(model, cancellationToken));
///
/// Get information about the current .
@@ -98,7 +108,10 @@ namespace Tgstation.Server.Host.Controllers
[Authorize]
[ProducesResponseType(typeof(UserResponse), 200)]
public ValueTask Read(CancellationToken cancellationToken)
- => userAuthority.InvokeTransformable(this, authority => authority.Read(cancellationToken));
+ => userAuthority.InvokeTransformable(this, authority => authority.GetId(
+ claimsPrincipalAccessor.User.RequireTgsUserId(),
+ false,
+ cancellationToken));
///
/// List all s in the server.
@@ -111,7 +124,7 @@ namespace Tgstation.Server.Host.Controllers
[HttpGet(Routes.List)]
[ProducesResponseType(typeof(PaginatedResponse), 200)]
public ValueTask List([FromQuery] int? page, [FromQuery] int? pageSize, CancellationToken cancellationToken)
- => Paginated(
+ => Paginated(
async () =>
{
var queryable = await userAuthority.InvokeQueryable(
@@ -121,7 +134,6 @@ namespace Tgstation.Server.Host.Controllers
return new PaginatableResult(queryable.OrderBy(x => x.Id));
},
- null,
page,
pageSize,
cancellationToken);
@@ -145,9 +157,9 @@ namespace Tgstation.Server.Host.Controllers
if (!((AdministrationRights)AuthenticationContext.GetRight(RightsType.Administration)).HasFlag(AdministrationRights.ReadUsers))
return Forbid();
- return await userAuthority.InvokeTransformable(
+ return await userAuthority.InvokeTransformable(
this,
- authority => authority.GetId(id, true, false, cancellationToken));
+ authority => authority.GetId(id, false, cancellationToken));
}
}
}
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index 8b73470640..65d8d4a2c8 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -861,7 +861,7 @@ namespace Tgstation.Server.Host.Core
var authenticationContext = services
.GetRequiredService();
context.HandleResponse();
- context.HttpContext.Response.Redirect($"{config.ReturnPath}?code={HttpUtility.UrlEncode(tokenFactory.CreateToken(authenticationContext.User, true))}&state=oidc.{HttpUtility.UrlEncode(configName)}");
+ context.HttpContext.Response.Redirect($"{config.ReturnPath}?code={HttpUtility.UrlEncode(tokenFactory.CreateToken(authenticationContext.User, true).Token)}&state=oidc.{HttpUtility.UrlEncode(configName)}");
return Task.CompletedTask;
},
};
diff --git a/src/Tgstation.Server.Host/GraphQL/Mutation.cs b/src/Tgstation.Server.Host/GraphQL/Mutation.cs
index 2bb7231021..cee0635398 100644
--- a/src/Tgstation.Server.Host/GraphQL/Mutation.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Mutation.cs
@@ -8,6 +8,7 @@ using HotChocolate.Types;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
+using Tgstation.Server.Host.GraphQL.Transformers;
namespace Tgstation.Server.Host.GraphQL
{
@@ -38,7 +39,7 @@ namespace Tgstation.Server.Host.GraphQL
{
ArgumentNullException.ThrowIfNull(loginAuthority);
- return loginAuthority.Invoke(
+ return loginAuthority.InvokeTransformable(
authority => authority.AttemptLogin(cancellationToken));
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Mutations/Payloads/LoginResult.cs b/src/Tgstation.Server.Host/GraphQL/Mutations/Payloads/LoginResult.cs
index a28589c21d..8a75d61018 100644
--- a/src/Tgstation.Server.Host/GraphQL/Mutations/Payloads/LoginResult.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Mutations/Payloads/LoginResult.cs
@@ -1,14 +1,12 @@
using HotChocolate;
-using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.GraphQL.Scalars;
-using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.GraphQL.Mutations.Payloads
{
///
/// Success response for a login attempt.
///
- public sealed class LoginResult : ILegacyApiTransformable
+ public sealed class LoginResult
{
///
/// The JSON Web Token (JWT) to use as a Bearer token for accessing the server at non-login endpoints. Contains an expiry time.
@@ -16,18 +14,5 @@ namespace Tgstation.Server.Host.GraphQL.Mutations.Payloads
[GraphQLType]
[GraphQLNonNullType]
public required string Bearer { get; init; }
-
- ///
- /// The that was logged in.
- ///
- public required Types.User User { get; init; }
-
- ///
- [GraphQLIgnore]
- public TokenResponse ToApi()
- => new()
- {
- Bearer = Bearer,
- };
}
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Transformers/LoginResultTransformer.cs b/src/Tgstation.Server.Host/GraphQL/Transformers/LoginResultTransformer.cs
new file mode 100644
index 0000000000..14fd2b32d5
--- /dev/null
+++ b/src/Tgstation.Server.Host/GraphQL/Transformers/LoginResultTransformer.cs
@@ -0,0 +1,23 @@
+using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.GraphQL.Transformers
+{
+ ///
+ /// for s.
+ ///
+ sealed class LoginResultTransformer : TransformerBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LoginResultTransformer()
+ : base(
+ token => new LoginResult
+ {
+ Bearer = token,
+ })
+ {
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/NamedEntity.cs b/src/Tgstation.Server.Host/GraphQL/Types/NamedEntity.cs
index 933e047b00..63ca95ffa6 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/NamedEntity.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/NamedEntity.cs
@@ -32,17 +32,5 @@ namespace Tgstation.Server.Host.GraphQL.Types
{
Name = copy.Name;
}
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID for the .
- /// The value of .
- [SetsRequiredMembers]
- protected NamedEntity(long id, string name)
- : base(id)
- {
- Name = name ?? throw new ArgumentNullException(nameof(name));
- }
}
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/User.cs b/src/Tgstation.Server.Host/GraphQL/Types/User.cs
index 8df9ef3c82..c372a4a813 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/User.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/User.cs
@@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// A user registered in the server.
///
[Node]
- public sealed class User : NamedEntity, IUserName
+ public sealed class User : UserName
{
///
[IsProjected(true)]
@@ -79,7 +79,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
///
/// Implements the .
///
- /// The of s to load.
+ /// The of s to load paired with if the system user should be allowed.
/// The for the .
/// The for mapped to an .
/// The for the operation.
@@ -108,31 +108,43 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// The for the operation.
/// The for the operation.
/// A resulting in the queried , if present.
- public static ValueTask GetUser(
+ public static async ValueTask GetUser(
long id,
[Service] IUsersDataLoader usersDataLoader,
QueryContext? queryContext,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(usersDataLoader);
- return usersDataLoader.LoadAuthorityResponse(queryContext, id, cancellationToken);
+ var user = await usersDataLoader.LoadAuthorityResponse(queryContext, id, cancellationToken);
+ if (user?.CanonicalName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
+ throw new NotImplementedException();
+
+ return user;
}
///
/// The who created this .
///
/// The for the .
+ /// The for the operation.
/// The for the operation.
/// The that created this , if any.
public async ValueTask CreatedBy(
[Service] IGraphQLAuthorityInvoker userAuthority,
+ QueryContext? queryContext,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(userAuthority);
if (!CreatedById.HasValue)
return null;
- var user = await userAuthority.InvokeTransformable(authority => authority.GetId(CreatedById.Value, false, true, cancellationToken));
+ // This one is particular and cannot be data-loaded due to necessitating a different parameter
+ var user = await userAuthority.InvokeTransformable(
+ authority => authority.GetId(CreatedById.Value, true, cancellationToken),
+ queryContext);
+ if (user == null)
+ throw new InvalidOperationException($"Query for created by of user ID {CreatedById.Value} returned null!");
+
if (user.CanonicalName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
return new UserName(user);
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/UserName.cs b/src/Tgstation.Server.Host/GraphQL/Types/UserName.cs
index c3d9117241..1a72927851 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/UserName.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/UserName.cs
@@ -1,47 +1,21 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.Threading;
-using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
-using HotChocolate;
-using HotChocolate.Types.Relay;
-
-using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.GraphQL.Interfaces;
-using Tgstation.Server.Host.GraphQL.Transformers;
namespace Tgstation.Server.Host.GraphQL.Types
{
///
/// A with limited fields.
///
- [Node]
- public sealed class UserName : NamedEntity, IUserName
+ public class UserName : NamedEntity, IUserName
{
- ///
- /// Node resolver for s.
- ///
- /// The to lookup.
- /// The for the .
- /// The for the operation.
- /// A resulting in the queried , if present.
- public static ValueTask GetUserName(
- long id,
- [Service] IGraphQLAuthorityInvoker userAuthority,
- CancellationToken cancellationToken)
- {
- ArgumentNullException.ThrowIfNull(userAuthority);
- return userAuthority.InvokeTransformableAllowMissing(
- authority => authority.GetId(id, false, true, cancellationToken));
- }
-
///
/// Initializes a new instance of the class.
///
- /// The to copy.
+ /// The to copy.
[SetsRequiredMembers]
- public UserName(NamedEntity copy)
- : base(copy)
+ public UserName(User user)
+ : base(user)
{
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/UsersRepository.cs b/src/Tgstation.Server.Host/GraphQL/Types/UsersRepository.cs
index 7b0d4bbc4e..fcb1b6faba 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/UsersRepository.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/UsersRepository.cs
@@ -14,7 +14,9 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.Configuration;
+using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.GraphQL.Transformers;
+using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.GraphQL.Types
{
@@ -38,15 +40,27 @@ namespace Tgstation.Server.Host.GraphQL.Types
///
/// Gets the current .
///
- /// The for the .
+ /// The for getting the current user ID.
+ /// The to use.
+ /// The active .
/// The for the operation.
/// A resulting in the current .
- public ValueTask Current(
- [Service] IGraphQLAuthorityInvoker userAuthority,
+ [Error(typeof(ErrorMessageException))]
+ public async ValueTask Current(
+ [Service] IClaimsPrincipalAccessor claimsPrincipalAccessor,
+ [Service] IUsersDataLoader usersDataLoader,
+ QueryContext? queryContext,
CancellationToken cancellationToken)
{
- ArgumentNullException.ThrowIfNull(userAuthority);
- return userAuthority.InvokeTransformable(authority => authority.Read(cancellationToken));
+ var user = await ById(
+ (claimsPrincipalAccessor ?? throw new ArgumentNullException(nameof(claimsPrincipalAccessor))).User.RequireTgsUserId(),
+ usersDataLoader,
+ queryContext,
+ cancellationToken);
+ if (user == null)
+ throw new InvalidOperationException("Reading the current user returned null!");
+
+ return user;
}
///
diff --git a/src/Tgstation.Server.Host/Models/IApiTransformable{TModel,TApiModel,TTransformer}.cs b/src/Tgstation.Server.Host/Models/IApiTransformable{TModel,TApiModel}.cs
similarity index 54%
rename from src/Tgstation.Server.Host/Models/IApiTransformable{TModel,TApiModel,TTransformer}.cs
rename to src/Tgstation.Server.Host/Models/IApiTransformable{TModel,TApiModel}.cs
index 0fff10e229..8b75aa418b 100644
--- a/src/Tgstation.Server.Host/Models/IApiTransformable{TModel,TApiModel,TTransformer}.cs
+++ b/src/Tgstation.Server.Host/Models/IApiTransformable{TModel,TApiModel}.cs
@@ -1,7 +1,5 @@
using System;
-#pragma warning disable CA1005
-
namespace Tgstation.Server.Host.Models
{
///
@@ -9,17 +7,17 @@ namespace Tgstation.Server.Host.Models
///
/// The internal model .
/// The API model .
- /// The .
- public interface IApiTransformable
+ public interface IApiTransformable
where TApiModel : notnull
- where TModel : IApiTransformable
- where TTransformer : ITransformer, new()
+ where TModel : IApiTransformable
{
///
- /// Convert the to it's .
+ /// Convert the to it's .
///
- /// A new based on the .
- TApiModel ToApi()
+ /// The .
+ /// A new based on the .
+ TApiModel ToApi()
+ where TTransformer : ITransformer, new()
=> new TTransformer()
.CompiledExpression((TModel)this);
}
diff --git a/src/Tgstation.Server.Host/Models/OAuthConnection.cs b/src/Tgstation.Server.Host/Models/OAuthConnection.cs
index 1b652f79be..e04a20e442 100644
--- a/src/Tgstation.Server.Host/Models/OAuthConnection.cs
+++ b/src/Tgstation.Server.Host/Models/OAuthConnection.cs
@@ -1,13 +1,11 @@
using System.ComponentModel.DataAnnotations;
-using Tgstation.Server.Host.GraphQL.Transformers;
-
namespace Tgstation.Server.Host.Models
{
///
public sealed class OAuthConnection : Api.Models.OAuthConnection,
ILegacyApiTransformable,
- IApiTransformable
+ IApiTransformable
{
///
/// The row Id.
diff --git a/src/Tgstation.Server.Host/Models/OidcConnection.cs b/src/Tgstation.Server.Host/Models/OidcConnection.cs
index c80daceb87..7eeff69a26 100644
--- a/src/Tgstation.Server.Host/Models/OidcConnection.cs
+++ b/src/Tgstation.Server.Host/Models/OidcConnection.cs
@@ -1,13 +1,11 @@
using System.ComponentModel.DataAnnotations;
-using Tgstation.Server.Host.GraphQL.Transformers;
-
namespace Tgstation.Server.Host.Models
{
///
public sealed class OidcConnection : Api.Models.OidcConnection,
ILegacyApiTransformable,
- IApiTransformable
+ IApiTransformable
{
///
/// The row Id.
diff --git a/src/Tgstation.Server.Host/Models/PermissionSet.cs b/src/Tgstation.Server.Host/Models/PermissionSet.cs
index 3a5bbdaede..3530c2f407 100644
--- a/src/Tgstation.Server.Host/Models/PermissionSet.cs
+++ b/src/Tgstation.Server.Host/Models/PermissionSet.cs
@@ -1,11 +1,9 @@
using System.Collections.Generic;
-using Tgstation.Server.Host.GraphQL.Transformers;
-
namespace Tgstation.Server.Host.Models
{
///
- public sealed class PermissionSet : Api.Models.PermissionSet, IApiTransformable
+ public sealed class PermissionSet : Api.Models.PermissionSet, IApiTransformable
{
///
/// The of .
diff --git a/src/Tgstation.Server.Host/Models/TransformerBase{TInput,TOutput}.cs b/src/Tgstation.Server.Host/Models/TransformerBase{TInput,TOutput}.cs
index 893f352067..7eb4f1506b 100644
--- a/src/Tgstation.Server.Host/Models/TransformerBase{TInput,TOutput}.cs
+++ b/src/Tgstation.Server.Host/Models/TransformerBase{TInput,TOutput}.cs
@@ -137,6 +137,47 @@ namespace Tgstation.Server.Host.Models
return global::System.Linq.Expressions.Expression.Lambda>(outputExpression, primaryInput);
}
+ ///
+ /// Build an for to when contains three sub-inputs with their own s.
+ ///
+ /// The first field in that needs transforming.
+ /// The second field in that needs transforming.
+ /// The third field in that needs transforming.
+ /// The first transformed of .
+ /// The second transformed of .
+ /// The third transformed of .
+ /// The for /.
+ /// The for /.
+ /// The for /.
+ /// The to take a , , and and produce a .
+ /// The to select from .
+ /// The to select from .
+ /// The to select from .
+ /// An expression converting into based on with its other arguments generated from the transformation result of , , .
+ protected static Expression> BuildSubProjection<
+ TSubInput1,
+ TSubInput2,
+ TSubInput3,
+ TSubOutput1,
+ TSubOutput2,
+ TSubOutput3,
+ TTransformer1,
+ TTransformer2,
+ TTransformer3>(
+ Expression> transformerExpression,
+ Expression> subInput1SelectionExpression,
+ Expression> subInput2SelectionExpression,
+ Expression> subInput3SelectionExpression)
+ where TSubOutput1 : class
+ where TSubOutput2 : class
+ where TSubOutput3 : class
+ where TTransformer1 : ITransformer, new()
+ where TTransformer2 : ITransformer, new()
+ where TTransformer3 : ITransformer, new()
+ {
+ throw new NotImplementedException();
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/src/Tgstation.Server.Host/Models/UpdatedUser.cs b/src/Tgstation.Server.Host/Models/UpdatedUser.cs
index 837a5146df..99d7384982 100644
--- a/src/Tgstation.Server.Host/Models/UpdatedUser.cs
+++ b/src/Tgstation.Server.Host/Models/UpdatedUser.cs
@@ -1,7 +1,6 @@
using System;
using Tgstation.Server.Api.Models.Response;
-using Tgstation.Server.Host.GraphQL.Transformers;
namespace Tgstation.Server.Host.Models
{
@@ -9,8 +8,8 @@ namespace Tgstation.Server.Host.Models
/// Represents a that has been updated.
///
public sealed class UpdatedUser :
- ILegacyApiTransformable,
- IApiTransformable
+ IApiTransformable,
+ IApiTransformable
{
///
/// The 's .
@@ -40,12 +39,5 @@ namespace Tgstation.Server.Host.Models
{
Id = id;
}
-
- ///
- public UserResponse ToApi()
- => User?.ToApi() ?? new UserResponse
- {
- Id = Id,
- };
}
}
diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs
index dba5616b6d..fcf92eebdb 100644
--- a/src/Tgstation.Server.Host/Models/User.cs
+++ b/src/Tgstation.Server.Host/Models/User.cs
@@ -1,19 +1,17 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.Linq;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
-using Tgstation.Server.Host.GraphQL.Transformers;
namespace Tgstation.Server.Host.Models
{
///
public sealed class User : Api.Models.Internal.UserModelBase,
- ILegacyApiTransformable,
- IApiTransformable,
- IApiTransformable
+ IApiTransformable,
+ IApiTransformable,
+ IApiTransformable
{
///
/// Username used when creating jobs automatically.
@@ -88,33 +86,5 @@ namespace Tgstation.Server.Host.Models
/// The .
/// The .
public static string CanonicalizeName(string name) => name?.ToUpperInvariant() ?? throw new ArgumentNullException(nameof(name));
-
- ///
- public UserResponse ToApi() => CreateUserResponse(true);
-
- ///
- /// Generate a from .
- ///
- /// If we should recurse on .
- /// A new .
- UserResponse CreateUserResponse(bool recursive)
- {
- var result = CreateUserName();
- if (recursive)
- result.CreatedBy = CreatedBy?.CreateUserName();
-
- result.CreatedAt = CreatedAt;
- result.Enabled = Enabled;
- result.SystemIdentifier = SystemIdentifier;
- result.OAuthConnections = OAuthConnections
- ?.Select(x => x.ToApi())
- .ToList();
- result.OidcConnections = OidcConnections
- ?.Select(x => x.ToApi())
- .ToList();
- result.Group = Group?.ToApi(false);
- result.PermissionSet = PermissionSet?.ToApi();
- return result;
- }
}
}
diff --git a/src/Tgstation.Server.Host/Models/UserGroup.cs b/src/Tgstation.Server.Host/Models/UserGroup.cs
index 23907d9968..7b0bbd6882 100644
--- a/src/Tgstation.Server.Host/Models/UserGroup.cs
+++ b/src/Tgstation.Server.Host/Models/UserGroup.cs
@@ -5,14 +5,13 @@ using System.Linq;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
-using Tgstation.Server.Host.GraphQL.Transformers;
namespace Tgstation.Server.Host.Models
{
///
/// Represents a group of s.
///
- public sealed class UserGroup : NamedEntity, ILegacyApiTransformable, IApiTransformable
+ public sealed class UserGroup : NamedEntity, ILegacyApiTransformable, IApiTransformable
{
///
/// The the has.
diff --git a/src/Tgstation.Server.Host/Security/IClaimsPrincipalAccessor.cs b/src/Tgstation.Server.Host/Security/IClaimsPrincipalAccessor.cs
index 9ac3da2494..1ff2177c88 100644
--- a/src/Tgstation.Server.Host/Security/IClaimsPrincipalAccessor.cs
+++ b/src/Tgstation.Server.Host/Security/IClaimsPrincipalAccessor.cs
@@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Security
///
/// Interface for accessing the current request's .
///
- interface IClaimsPrincipalAccessor
+ public interface IClaimsPrincipalAccessor
{
///
/// Get the current .
diff --git a/src/Tgstation.Server.Host/Security/ITokenFactory.cs b/src/Tgstation.Server.Host/Security/ITokenFactory.cs
index bfa7bb360f..1baa29e6cc 100644
--- a/src/Tgstation.Server.Host/Security/ITokenFactory.cs
+++ b/src/Tgstation.Server.Host/Security/ITokenFactory.cs
@@ -26,7 +26,7 @@ namespace Tgstation.Server.Host.Security
///
/// The to create the token for. Must have the field available.
/// Whether or not this is an external service login.
- /// A new token .
- string CreateToken(Models.User user, bool serviceLogin);
+ /// A new token and the that it expires.
+ (string Token, DateTimeOffset Expiry) CreateToken(Models.User user, bool serviceLogin);
}
}
diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs
index 9e2b18e668..d41803ef4d 100644
--- a/src/Tgstation.Server.Host/Security/TokenFactory.cs
+++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs
@@ -101,7 +101,7 @@ namespace Tgstation.Server.Host.Security
}
///
- public string CreateToken(User user, bool serviceLogin)
+ public (string Token, DateTimeOffset Expiry) CreateToken(User user, bool serviceLogin)
{
ArgumentNullException.ThrowIfNull(user);
@@ -141,7 +141,7 @@ namespace Tgstation.Server.Host.Security
var tokenResponse = tokenHandler.WriteToken(securityToken);
- return tokenResponse;
+ return (Token: tokenResponse, Expiry: expiry);
}
}
}
diff --git a/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs b/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs
index 5f5c9aec76..60e465c342 100644
--- a/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs
+++ b/tests/Tgstation.Server.Host.Tests/Swarm/TestableSwarmNode.cs
@@ -89,7 +89,7 @@ namespace Tgstation.Server.Host.Swarm.Tests
public TokenValidationParameters ValidationParameters => throw new NotSupportedException();
- public string CreateToken(User user, bool serviceLogin)
+ public (string, DateTimeOffset) CreateToken(User user, bool serviceLogin)
{
throw new NotSupportedException();
}