Properly setup GraphQL IUserName for TGS user

This commit is contained in:
Jordan Dominion
2024-09-11 16:21:08 -04:00
parent edd41aab62
commit 84f5ee7e7f
13 changed files with 89 additions and 11 deletions
@@ -28,10 +28,11 @@ namespace Tgstation.Server.Host.Authority
/// </summary>
/// <param name="id">The <see cref="EntityId.Id"/> of the <see cref="User"/>.</param>
/// <param name="includeJoins">If related entities should be loaded.</param>
/// <param name="allowSystemUser">If the <see cref="User.TgsSystemUserName"/> may be returned.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a <see cref="User"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
[TgsAuthorize(AdministrationRights.ReadUsers)]
public ValueTask<AuthorityResponse<User>> GetId(long id, bool includeJoins, CancellationToken cancellationToken);
public ValueTask<AuthorityResponse<User>> GetId(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken);
/// <summary>
/// Gets all registered <see cref="User"/>s.
@@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.Authority
=> ValueTask.FromResult(new AuthorityResponse<User>(authenticationContext.User));
/// <inheritdoc />
public async ValueTask<AuthorityResponse<User>> GetId(long id, bool includeJoins, CancellationToken cancellationToken)
public async ValueTask<AuthorityResponse<User>> GetId(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken)
{
var queryable = ListCore(includeJoins);
@@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Authority
if (user == default)
return NotFound<User>();
if (user.CanonicalName == User.CanonicalizeName(User.TgsSystemUserName))
if (!allowSystemUser && user.CanonicalName == User.CanonicalizeName(User.TgsSystemUserName))
return Forbid<User>();
return new AuthorityResponse<User>(user);
@@ -414,7 +414,7 @@ namespace Tgstation.Server.Host.Controllers
return await userAuthority.InvokeTransformable<User, UserResponse>(
this,
authority => authority.GetId(id, true, cancellationToken));
authority => authority.GetId(id, true, false, cancellationToken));
}
/// <summary>
@@ -300,6 +300,7 @@ namespace Tgstation.Server.Host.Core
.AddErrorFilter<ErrorMessageFilter>()
.AddType<LocalGateway>()
.AddType<RemoteGateway>()
.AddType<GraphQL.Types.UserName>()
.AddType<UnsignedIntType>()
.BindRuntimeType<Version, SemverType>()
.AddQueryType<Query>()
@@ -2,13 +2,13 @@
using HotChocolate.Authorization;
using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.GraphQL.Types;
using Tgstation.Server.Host.Security.OAuth;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.GraphQL.Types
namespace Tgstation.Server.Host.GraphQL.Interfaces
{
/// <summary>
/// Management interface for the parent <see cref="Node"/>.
@@ -0,0 +1,21 @@
using HotChocolate.Types.Relay;
namespace Tgstation.Server.Host.GraphQL.Interfaces
{
/// <summary>
/// A lightly scoped <see cref="Types.User"/>.
/// </summary>
public interface IUserName
{
/// <summary>
/// The ID of the user.
/// </summary>
[ID]
public long Id { get; }
/// <summary>
/// The name of the user.
/// </summary>
public string Name { get; }
}
}
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.GraphQL.Interfaces;
using Tgstation.Server.Host.Security.OAuth;
using Tgstation.Server.Host.System;
@@ -1,5 +1,7 @@
using System;
using Tgstation.Server.Host.GraphQL.Interfaces;
namespace Tgstation.Server.Host.GraphQL.Types
{
/// <summary>
@@ -12,6 +14,16 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// </summary>
public string Name { get; }
/// <summary>
/// Initializes a new instance of the <see cref="NamedEntity"/> class.
/// </summary>
/// <param name="copy">The <see cref="IUserName"/> to copy.</param>
protected NamedEntity(NamedEntity copy)
: base(copy?.Id ?? throw new ArgumentNullException(nameof(copy)))
{
Name = copy.Name;
}
/// <summary>
/// Initializes a new instance of the <see cref="NamedEntity"/> class.
/// </summary>
@@ -5,6 +5,7 @@ using HotChocolate;
using Microsoft.Extensions.Options;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.GraphQL.Interfaces;
namespace Tgstation.Server.Host.GraphQL.Types
{
@@ -7,6 +7,7 @@ using Microsoft.Extensions.Options;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.GraphQL.Interfaces;
using Tgstation.Server.Host.Security.OAuth;
using Tgstation.Server.Host.System;
@@ -1,13 +1,20 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using HotChocolate;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.Authority.Core;
using Tgstation.Server.Host.GraphQL.Interfaces;
namespace Tgstation.Server.Host.GraphQL.Types
{
/// <summary>
/// A user registered in the server.
/// </summary>
public sealed class User : NamedEntity
public sealed class User : NamedEntity, IUserName
{
/// <summary>
/// If the <see cref="User"/> is enabled since users cannot be deleted. System users cannot be disabled.
@@ -72,9 +79,23 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// <summary>
/// The <see cref="User"/> who created this <see cref="User"/>.
/// </summary>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="User"/> who created this <see cref="User"/>, if any.</returns>
public ValueTask<User?> CreatedBy()
=> throw new NotImplementedException();
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> <see cref="IUserAuthority"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>The <see cref="IUserName"/> that created this <see cref="User"/>, if any.</returns>
public async ValueTask<IUserName?> CreatedBy(
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(userAuthority);
if (!createdById.HasValue)
return null;
var user = await userAuthority.InvokeTransformable<Models.User, User>(authority => authority.GetId(createdById.Value, false, true, cancellationToken));
if (user.CanonicalName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
return new UserName(user);
return user;
}
/// <summary>
/// List of <see cref="OAuthConnection"/>s associated with the user if OAuth is configured.
@@ -0,0 +1,19 @@
using Tgstation.Server.Host.GraphQL.Interfaces;
namespace Tgstation.Server.Host.GraphQL.Types
{
/// <summary>
/// A <see cref="User"/> with limited fields.
/// </summary>
public sealed class UserName : NamedEntity, IUserName
{
/// <summary>
/// Initializes a new instance of the <see cref="UserName"/> class.
/// </summary>
/// <param name="copy">The <see cref="NamedEntity"/> to copy.</param>
public UserName(NamedEntity copy)
: base(copy)
{
}
}
}
@@ -51,7 +51,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(userAuthority);
return await userAuthority.InvokeTransformable<Models.User, User>(authority => authority.GetId(id, false, cancellationToken));
return await userAuthority.InvokeTransformable<Models.User, User>(authority => authority.GetId(id, false, false, cancellationToken));
}
/// <summary>