diff --git a/src/Tgstation.Server.Host/Authority/IPermissionSetAuthority.cs b/src/Tgstation.Server.Host/Authority/IPermissionSetAuthority.cs
new file mode 100644
index 0000000000..bb10fed383
--- /dev/null
+++ b/src/Tgstation.Server.Host/Authority/IPermissionSetAuthority.cs
@@ -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
+{
+ ///
+ /// for managing s.
+ ///
+ public interface IPermissionSetAuthority : IAuthority
+ {
+ ///
+ /// Gets the with a given .
+ ///
+ /// The to lookup.
+ /// The of .
+ /// The for the operation.
+ /// A resulting in a .
+ [TgsAuthorize(AdministrationRights.ReadUsers)]
+ ValueTask> GetId(long id, PermissionSetLookupType lookupType, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Authority/PermissionSetAuthority.cs b/src/Tgstation.Server.Host/Authority/PermissionSetAuthority.cs
new file mode 100644
index 0000000000..12e63a9ce6
--- /dev/null
+++ b/src/Tgstation.Server.Host/Authority/PermissionSetAuthority.cs
@@ -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
+{
+ ///
+ sealed class PermissionSetAuthority : AuthorityBase, IPermissionSetAuthority
+ {
+ ///
+ /// The for the .
+ ///
+ readonly IPermissionSetsDataLoader permissionSetsDataLoader;
+
+ ///
+ /// Implements .
+ ///
+ /// The of IDs and their s to load.
+ /// The to load from.
+ /// The for the operation.
+ /// A resulting in a of the requested s.
+ [DataLoader]
+ public static async ValueTask> GetPermissionSets(
+ IReadOnlyList<(long Id, PermissionSetLookupType LookupType)> ids,
+ IDatabaseContext databaseContext,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(ids);
+ ArgumentNullException.ThrowIfNull(databaseContext);
+
+ var idLookups = new List(ids.Count);
+ var userIdLookups = new List(ids.Count);
+ var groupIdLookups = new List(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;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to use.
+ /// The to use.
+ /// The to use.
+ /// The value of .
+ public PermissionSetAuthority(
+ IAuthenticationContext authenticationContext,
+ IDatabaseContext databaseContext,
+ ILogger logger,
+ IPermissionSetsDataLoader permissionSetsDataLoader)
+ : base(
+ authenticationContext,
+ databaseContext,
+ logger)
+ {
+ this.permissionSetsDataLoader = permissionSetsDataLoader ?? throw new ArgumentNullException(nameof(permissionSetsDataLoader));
+ }
+
+ ///
+ public async ValueTask> GetId(long id, PermissionSetLookupType lookupType, CancellationToken cancellationToken)
+ {
+ if (id != AuthenticationContext.PermissionSet.Id && !((AdministrationRights)AuthenticationContext.GetRight(RightsType.Administration)).HasFlag(AdministrationRights.ReadUsers))
+ return Forbid();
+
+ var permissionSet = await permissionSetsDataLoader.LoadAsync((Id: id, LookupType: lookupType), cancellationToken);
+ if (permissionSet == null)
+ return NotFound();
+
+ return new AuthorityResponse(permissionSet);
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Authority/PermissionSetLookupType.cs b/src/Tgstation.Server.Host/Authority/PermissionSetLookupType.cs
new file mode 100644
index 0000000000..12925dbf8f
--- /dev/null
+++ b/src/Tgstation.Server.Host/Authority/PermissionSetLookupType.cs
@@ -0,0 +1,23 @@
+namespace Tgstation.Server.Host.Authority
+{
+ ///
+ /// Indicates the type of to lookup on a .
+ ///
+ public enum PermissionSetLookupType
+ {
+ ///
+ /// Lookup the of the .
+ ///
+ Id,
+
+ ///
+ /// Lookup the of the .
+ ///
+ UserId,
+
+ ///
+ /// Lookup the of the .
+ ///
+ GroupId,
+ }
+}
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index 353d0063a9..0cc7d0fb2b 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -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();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
// configure misc services
services.AddSingleton();
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs b/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs
index ba0c070582..018fa26f47 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/PermissionSet.cs
@@ -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
{
///
/// Represents a set of permissions for the server.
///
+ [Node]
public sealed class PermissionSet : Entity
{
+ ///
+ /// Node resolver for s.
+ ///
+ /// The to lookup.
+ /// The .
+ /// The for the operation.
+ /// A resulting in the queried , if present.
+ [TgsGraphQLAuthorize]
+ public static ValueTask GetPermissionSet(
+ long id,
+ [Service] IGraphQLAuthorityInvoker userAuthority,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(userAuthority);
+ return userAuthority.InvokeTransformable(
+ authority => authority.GetId(id, PermissionSetLookupType.Id, cancellationToken));
+ }
+
///
/// The for the .
///
- public AdministrationRights AdministrationRights { get; }
+ public required AdministrationRights AdministrationRights { get; init; }
///
/// The for the .
///
- public InstanceManagerRights InstanceManagerRights { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The .
- /// The value of .
- /// The value of .
- [SetsRequiredMembers]
- public PermissionSet(long id, AdministrationRights administrationRights, InstanceManagerRights instanceManagerRights)
- : base(id)
- {
- AdministrationRights = administrationRights;
- InstanceManagerRights = instanceManagerRights;
- }
+ public required InstanceManagerRights InstanceManagerRights { get; init; }
}
}
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/User.cs b/src/Tgstation.Server.Host/GraphQL/Types/User.cs
index 0a9aa5c692..6d9f062898 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/User.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/User.cs
@@ -108,11 +108,49 @@ namespace Tgstation.Server.Host.GraphQL.Types
}
///
- /// The directly associated with the , if any.
+ /// The associated with the .
///
- /// A resulting in the directly associated with the , if any.
- public ValueTask PermissionSet()
- => throw new NotImplementedException();
+ /// The .
+ /// The for the operation.
+ /// A resulting in the associated with the .
+ public async ValueTask EffectivePermissionSet(
+ [Service] IGraphQLAuthorityInvoker 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(
+ authority => authority.GetId(lookupId, lookupType, cancellationToken)))!;
+ }
+
+ ///
+ /// The owned by the , if any.
+ ///
+ /// The .
+ /// The for the operation.
+ /// A resulting in the owned by the , if any.
+ public ValueTask OwnedPermissionSet(
+ [Service] IGraphQLAuthorityInvoker permissionSetAuthority,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(permissionSetAuthority);
+
+ return permissionSetAuthority.InvokeTransformable(
+ authority => authority.GetId(Id, PermissionSetLookupType.UserId, cancellationToken));
+ }
///
/// The asociated with the user, if any.
diff --git a/src/Tgstation.Server.Host/GraphQL/Types/UserGroup.cs b/src/Tgstation.Server.Host/GraphQL/Types/UserGroup.cs
index 1d457be2a8..80dec4b65c 100644
--- a/src/Tgstation.Server.Host/GraphQL/Types/UserGroup.cs
+++ b/src/Tgstation.Server.Host/GraphQL/Types/UserGroup.cs
@@ -38,11 +38,20 @@ namespace Tgstation.Server.Host.GraphQL.Types
}
///
- /// The of the .
+ /// The owned by the .
///
- /// A resulting in the for the .
- public ValueTask PermissionSet()
- => throw new NotImplementedException();
+ /// The .
+ /// The for the operation.
+ /// A resulting in the owned by the .
+ public async ValueTask PermissionSet(
+ [Service] IGraphQLAuthorityInvoker permissionSetAuthority,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(permissionSetAuthority);
+
+ return (await permissionSetAuthority.InvokeTransformable(
+ authority => authority.GetId(Id, PermissionSetLookupType.GroupId, cancellationToken)))!;
+ }
///
/// Gets the s in the .
diff --git a/src/Tgstation.Server.Host/Models/PermissionSet.cs b/src/Tgstation.Server.Host/Models/PermissionSet.cs
index 2c18a2ae1f..94b769e4a8 100644
--- a/src/Tgstation.Server.Host/Models/PermissionSet.cs
+++ b/src/Tgstation.Server.Host/Models/PermissionSet.cs
@@ -1,9 +1,11 @@
using System.Collections.Generic;
+using Tgstation.Server.Host.Models.Transformers;
+
namespace Tgstation.Server.Host.Models
{
///
- public sealed class PermissionSet : Api.Models.PermissionSet
+ public sealed class PermissionSet : Api.Models.PermissionSet, IApiTransformable
{
///
/// The of .
diff --git a/src/Tgstation.Server.Host/Models/Transformers/PermissionSetGraphQLTransformer.cs b/src/Tgstation.Server.Host/Models/Transformers/PermissionSetGraphQLTransformer.cs
new file mode 100644
index 0000000000..1575f04df9
--- /dev/null
+++ b/src/Tgstation.Server.Host/Models/Transformers/PermissionSetGraphQLTransformer.cs
@@ -0,0 +1,21 @@
+namespace Tgstation.Server.Host.Models.Transformers
+{
+ ///
+ /// for s.
+ ///
+ sealed class PermissionSetGraphQLTransformer : TransformerBase
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PermissionSetGraphQLTransformer()
+ : base(model => new GraphQL.Types.PermissionSet
+ {
+ Id = model.Id!.Value,
+ AdministrationRights = model.AdministrationRights!.Value,
+ InstanceManagerRights = model.InstanceManagerRights!.Value,
+ })
+ {
+ }
+ }
+}