mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-21 12:06:59 +01:00
Implement permission set querying and authority
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Authority.Core;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Authority
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IAuthority"/> for managing <see cref="PermissionSet"/>s.
|
||||
/// </summary>
|
||||
public interface IPermissionSetAuthority : IAuthority
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="User"/> with a given <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="Api.Models.EntityId.Id"/> to lookup.</param>
|
||||
/// <param name="lookupType">The <see cref="PermissionSetLookupType"/> of <paramref name="id"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a <see cref="PermissionSet"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
|
||||
[TgsAuthorize(AdministrationRights.ReadUsers)]
|
||||
ValueTask<AuthorityResponse<PermissionSet>> GetId(long id, PermissionSetLookupType lookupType, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using GreenDonut;
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Authority.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Authority
|
||||
{
|
||||
/// <inheritdoc cref="IPermissionSetAuthority" />
|
||||
sealed class PermissionSetAuthority : AuthorityBase, IPermissionSetAuthority
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IPermissionSetsDataLoader"/> for the <see cref="PermissionSetAuthority"/>.
|
||||
/// </summary>
|
||||
readonly IPermissionSetsDataLoader permissionSetsDataLoader;
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="permissionSetsDataLoader"/>.
|
||||
/// </summary>
|
||||
/// <param name="ids">The <see cref="IReadOnlyList{T}"/> of IDs and their <see cref="PermissionSetLookupType"/>s to load.</param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to load from.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a <see cref="Dictionary{TKey, TValue}"/> of the requested <see cref="PermissionSet"/>s.</returns>
|
||||
[DataLoader]
|
||||
public static async ValueTask<Dictionary<(long Id, PermissionSetLookupType LookupType), PermissionSet>> GetPermissionSets(
|
||||
IReadOnlyList<(long Id, PermissionSetLookupType LookupType)> ids,
|
||||
IDatabaseContext databaseContext,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ids);
|
||||
ArgumentNullException.ThrowIfNull(databaseContext);
|
||||
|
||||
var idLookups = new List<long>(ids.Count);
|
||||
var userIdLookups = new List<long>(ids.Count);
|
||||
var groupIdLookups = new List<long>(ids.Count);
|
||||
|
||||
foreach (var (id, lookupType) in ids)
|
||||
switch (lookupType)
|
||||
{
|
||||
case PermissionSetLookupType.Id:
|
||||
idLookups.Add(id);
|
||||
break;
|
||||
case PermissionSetLookupType.UserId:
|
||||
userIdLookups.Add(id);
|
||||
break;
|
||||
case PermissionSetLookupType.GroupId:
|
||||
groupIdLookups.Add(id);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid {nameof(PermissionSetLookupType)}: {lookupType}");
|
||||
}
|
||||
|
||||
var selectedPermissionSets = await databaseContext
|
||||
.PermissionSets
|
||||
.Where(dbModel => idLookups.Contains(dbModel.Id!.Value)
|
||||
|| (dbModel.UserId.HasValue && userIdLookups.Contains(dbModel.UserId.Value))
|
||||
|| (dbModel.GroupId.HasValue && groupIdLookups.Contains(dbModel.GroupId.Value)))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var results = new Dictionary<(long Id, PermissionSetLookupType LookupType), PermissionSet>(selectedPermissionSets.Count * 2);
|
||||
foreach (var permissionSet in selectedPermissionSets)
|
||||
{
|
||||
results.Add((permissionSet.Id!.Value, PermissionSetLookupType.Id), permissionSet);
|
||||
if (permissionSet.GroupId.HasValue)
|
||||
results.Add((permissionSet.GroupId.Value, PermissionSetLookupType.GroupId), permissionSet);
|
||||
if (permissionSet.UserId.HasValue)
|
||||
results.Add((permissionSet.UserId.Value, PermissionSetLookupType.UserId), permissionSet);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PermissionSetAuthority"/> class.
|
||||
/// </summary>
|
||||
/// <param name="authenticationContext">The <see cref="IAuthenticationContext"/> to use.</param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to use.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> to use.</param>
|
||||
/// <param name="permissionSetsDataLoader">The value of <see cref="permissionSetsDataLoader"/>.</param>
|
||||
public PermissionSetAuthority(
|
||||
IAuthenticationContext authenticationContext,
|
||||
IDatabaseContext databaseContext,
|
||||
ILogger<AuthorityBase> logger,
|
||||
IPermissionSetsDataLoader permissionSetsDataLoader)
|
||||
: base(
|
||||
authenticationContext,
|
||||
databaseContext,
|
||||
logger)
|
||||
{
|
||||
this.permissionSetsDataLoader = permissionSetsDataLoader ?? throw new ArgumentNullException(nameof(permissionSetsDataLoader));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<AuthorityResponse<PermissionSet>> GetId(long id, PermissionSetLookupType lookupType, CancellationToken cancellationToken)
|
||||
{
|
||||
if (id != AuthenticationContext.PermissionSet.Id && !((AdministrationRights)AuthenticationContext.GetRight(RightsType.Administration)).HasFlag(AdministrationRights.ReadUsers))
|
||||
return Forbid<PermissionSet>();
|
||||
|
||||
var permissionSet = await permissionSetsDataLoader.LoadAsync((Id: id, LookupType: lookupType), cancellationToken);
|
||||
if (permissionSet == null)
|
||||
return NotFound<PermissionSet>();
|
||||
|
||||
return new AuthorityResponse<PermissionSet>(permissionSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Tgstation.Server.Host.Authority
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the type of <see cref="Api.Models.EntityId.Id"/> to lookup on a <see cref="Models.PermissionSet"/>.
|
||||
/// </summary>
|
||||
public enum PermissionSetLookupType
|
||||
{
|
||||
/// <summary>
|
||||
/// Lookup the <see cref="Api.Models.EntityId.Id"/> of the <see cref="Models.PermissionSet"/>.
|
||||
/// </summary>
|
||||
Id,
|
||||
|
||||
/// <summary>
|
||||
/// Lookup the <see cref="Api.Models.EntityId.Id"/> of the <see cref="Models.PermissionSet.User"/>.
|
||||
/// </summary>
|
||||
UserId,
|
||||
|
||||
/// <summary>
|
||||
/// Lookup the <see cref="Api.Models.EntityId.Id"/> of the <see cref="Models.PermissionSet.Group"/>.
|
||||
/// </summary>
|
||||
GroupId,
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -299,6 +299,8 @@ namespace Tgstation.Server.Host.Core
|
||||
.ModifyOptions(options =>
|
||||
{
|
||||
options.EnsureAllNodesCanBeResolved = true;
|
||||
options.EnableFlagEnums = true;
|
||||
})
|
||||
})
|
||||
.AddMutationConventions()
|
||||
.AddGlobalObjectIdentification()
|
||||
@@ -466,6 +468,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddScoped<ILoginAuthority, LoginAuthority>();
|
||||
services.AddScoped<IUserAuthority, UserAuthority>();
|
||||
services.AddScoped<IUserGroupAuthority, UserGroupAuthority>();
|
||||
services.AddScoped<IPermissionSetAuthority, PermissionSetAuthority>();
|
||||
|
||||
// configure misc services
|
||||
services.AddSingleton<IProcessExecutor, ProcessExecutor>();
|
||||
|
||||
@@ -1,36 +1,49 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using HotChocolate;
|
||||
using HotChocolate.Types.Relay;
|
||||
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Authority;
|
||||
using Tgstation.Server.Host.Models.Transformers;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.GraphQL.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a set of permissions for the server.
|
||||
/// </summary>
|
||||
[Node]
|
||||
public sealed class PermissionSet : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// Node resolver for <see cref="PermissionSet"/>s.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="Entity.Id"/> to lookup.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> <see cref="IPermissionSetAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> resulting in the queried <see cref="PermissionSet"/>, if present.</returns>
|
||||
[TgsGraphQLAuthorize]
|
||||
public static ValueTask<PermissionSet?> GetPermissionSet(
|
||||
long id,
|
||||
[Service] IGraphQLAuthorityInvoker<IPermissionSetAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(userAuthority);
|
||||
return userAuthority.InvokeTransformable<Models.PermissionSet, PermissionSet, PermissionSetGraphQLTransformer>(
|
||||
authority => authority.GetId(id, PermissionSetLookupType.Id, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Rights.AdministrationRights"/> for the <see cref="PermissionSet"/>.
|
||||
/// </summary>
|
||||
public AdministrationRights AdministrationRights { get; }
|
||||
public required AdministrationRights AdministrationRights { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Rights.InstanceManagerRights"/> for the <see cref="PermissionSet"/>.
|
||||
/// </summary>
|
||||
public InstanceManagerRights InstanceManagerRights { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PermissionSet"/> class.
|
||||
/// </summary>
|
||||
/// <param name="id">The <see cref="Entity.Id"/>.</param>
|
||||
/// <param name="administrationRights">The value of <see cref="AdministrationRights"/>.</param>
|
||||
/// <param name="instanceManagerRights">The value of <see cref="InstanceManagerRights"/>.</param>
|
||||
[SetsRequiredMembers]
|
||||
public PermissionSet(long id, AdministrationRights administrationRights, InstanceManagerRights instanceManagerRights)
|
||||
: base(id)
|
||||
{
|
||||
AdministrationRights = administrationRights;
|
||||
InstanceManagerRights = instanceManagerRights;
|
||||
}
|
||||
public required InstanceManagerRights InstanceManagerRights { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,11 +108,49 @@ namespace Tgstation.Server.Host.GraphQL.Types
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Types.PermissionSet"/> directly associated with the <see cref="User"/>, if any.
|
||||
/// The <see cref="PermissionSet"/> associated with the <see cref="User"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="Types.PermissionSet"/> directly associated with the <see cref="User"/>, if any.</returns>
|
||||
public ValueTask<PermissionSet?> PermissionSet()
|
||||
=> throw new NotImplementedException();
|
||||
/// <param name="permissionSetAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> <see cref="IPermissionSetAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="PermissionSet"/> associated with the <see cref="User"/>.</returns>
|
||||
public async ValueTask<PermissionSet> EffectivePermissionSet(
|
||||
[Service] IGraphQLAuthorityInvoker<IPermissionSetAuthority> permissionSetAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(permissionSetAuthority);
|
||||
|
||||
long lookupId;
|
||||
PermissionSetLookupType lookupType;
|
||||
if (GroupId.HasValue)
|
||||
{
|
||||
lookupId = GroupId.Value;
|
||||
lookupType = PermissionSetLookupType.GroupId;
|
||||
}
|
||||
else
|
||||
{
|
||||
lookupId = Id;
|
||||
lookupType = PermissionSetLookupType.UserId;
|
||||
}
|
||||
|
||||
return (await permissionSetAuthority.InvokeTransformable<Models.PermissionSet, PermissionSet, PermissionSetGraphQLTransformer>(
|
||||
authority => authority.GetId(lookupId, lookupType, cancellationToken)))!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PermissionSet"/> owned by the <see cref="User"/>, if any.
|
||||
/// </summary>
|
||||
/// <param name="permissionSetAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> <see cref="IPermissionSetAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="PermissionSet"/> owned by the <see cref="User"/>, if any.</returns>
|
||||
public ValueTask<PermissionSet?> OwnedPermissionSet(
|
||||
[Service] IGraphQLAuthorityInvoker<IPermissionSetAuthority> permissionSetAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(permissionSetAuthority);
|
||||
|
||||
return permissionSetAuthority.InvokeTransformable<Models.PermissionSet, PermissionSet, PermissionSetGraphQLTransformer>(
|
||||
authority => authority.GetId(Id, PermissionSetLookupType.UserId, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="UserGroup"/> asociated with the user, if any.
|
||||
|
||||
@@ -38,11 +38,20 @@ namespace Tgstation.Server.Host.GraphQL.Types
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PermissionSet"/> of the <see cref="UserGroup"/>.
|
||||
/// The <see cref="PermissionSet"/> owned by the <see cref="UserGroup"/>.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="Types.PermissionSet"/> for the <see cref="UserGroup"/>.</returns>
|
||||
public ValueTask<PermissionSet> PermissionSet()
|
||||
=> throw new NotImplementedException();
|
||||
/// <param name="permissionSetAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> <see cref="IPermissionSetAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="PermissionSet"/> owned by the <see cref="UserGroup"/>.</returns>
|
||||
public async ValueTask<PermissionSet> PermissionSet(
|
||||
[Service] IGraphQLAuthorityInvoker<IPermissionSetAuthority> permissionSetAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(permissionSetAuthority);
|
||||
|
||||
return (await permissionSetAuthority.InvokeTransformable<Models.PermissionSet, PermissionSet, PermissionSetGraphQLTransformer>(
|
||||
authority => authority.GetId(Id, PermissionSetLookupType.GroupId, cancellationToken)))!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="User"/>s in the <see cref="UserGroup"/>.
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Tgstation.Server.Host.Models.Transformers;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class PermissionSet : Api.Models.PermissionSet
|
||||
public sealed class PermissionSet : Api.Models.PermissionSet, IApiTransformable<PermissionSet, GraphQL.Types.PermissionSet, PermissionSetGraphQLTransformer>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.EntityId.Id"/> of <see cref="User"/>.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Tgstation.Server.Host.Models.Transformers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITransformer{TInput, TOutput}"/> for <see cref="GraphQL.Types.PermissionSet"/>s.
|
||||
/// </summary>
|
||||
sealed class PermissionSetGraphQLTransformer : TransformerBase<PermissionSet, GraphQL.Types.PermissionSet>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PermissionSetGraphQLTransformer"/> class.
|
||||
/// </summary>
|
||||
public PermissionSetGraphQLTransformer()
|
||||
: base(model => new GraphQL.Types.PermissionSet
|
||||
{
|
||||
Id = model.Id!.Value,
|
||||
AdministrationRights = model.AdministrationRights!.Value,
|
||||
InstanceManagerRights = model.InstanceManagerRights!.Value,
|
||||
})
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user