Implement OAuthConnections loading for GraphQL

This commit is contained in:
Jordan Dominion
2024-09-14 00:57:59 -04:00
parent 30f3bd58f2
commit dcedf3a24a
4 changed files with 70 additions and 10 deletions
@@ -34,6 +34,14 @@ namespace Tgstation.Server.Host.Authority
[TgsAuthorize(AdministrationRights.ReadUsers)]
public ValueTask<AuthorityResponse<User>> GetId(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken);
/// <summary>
/// Gets the <see cref="OAuthConnection"/>s for the <see cref="User"/> with a given <paramref name="userId"/>.
/// </summary>
/// <param name="userId">The <see cref="EntityId.Id"/> of the <see cref="User"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in an <see cref="global::System.Array"/> of <see cref="GraphQL.Types.OAuthConnection"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
public ValueTask<AuthorityResponse<GraphQL.Types.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken);
/// <summary>
/// Gets all registered <see cref="User"/>s.
/// </summary>
@@ -27,7 +27,12 @@ namespace Tgstation.Server.Host.Authority
/// <summary>
/// The <see cref="IUsersDataLoader"/> for the <see cref="UserAuthority"/>.
/// </summary>
readonly IUsersDataLoader dataLoader;
readonly IUsersDataLoader usersDataLoader;
/// <summary>
/// The <see cref="IOAuthConnectionsDataLoader"/> for the <see cref="UserAuthority"/>.
/// </summary>
readonly IOAuthConnectionsDataLoader oAuthConnectionsDataLoader;
/// <summary>
/// The <see cref="IAuthenticationContext"/> for the <see cref="UserAuthority"/>.
@@ -35,7 +40,7 @@ namespace Tgstation.Server.Host.Authority
readonly IAuthenticationContext authenticationContext;
/// <summary>
/// Implements the <see cref="dataLoader"/>.
/// Implements the <see cref="usersDataLoader"/>.
/// </summary>
/// <param name="ids">The <see cref="IReadOnlyCollection{T}"/> of <see cref="User"/> <see cref="Api.Models.EntityId.Id"/>s to load.</param>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to load from.</param>
@@ -57,22 +62,52 @@ namespace Tgstation.Server.Host.Authority
.ToDictionaryAsync(user => user.Id!.Value, cancellationToken);
}
/// <summary>
/// Implements the <see cref="usersDataLoader"/>.
/// </summary>
/// <param name="userIds">The <see cref="IReadOnlyCollection{T}"/> of <see cref="User"/> <see cref="Api.Models.EntityId.Id"/>s to load the OAuthConnections for.</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="User"/>s.</returns>
[DataLoader]
public static async ValueTask<ILookup<long, GraphQL.Types.OAuthConnection>> GetOAuthConnections(
IReadOnlyList<long> userIds,
IDatabaseContext databaseContext,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(userIds);
ArgumentNullException.ThrowIfNull(databaseContext);
var list = await databaseContext
.OAuthConnections
.AsQueryable()
.Where(x => userIds.Contains(x.User!.Id!.Value))
.ToListAsync(cancellationToken);
return list.ToLookup(
oauthConnection => oauthConnection.UserId,
x => new GraphQL.Types.OAuthConnection(x.ExternalUserId!, x.Provider));
}
/// <summary>
/// Initializes a new instance of the <see cref="UserAuthority"/> class.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> to use.</param>
/// <param name="databaseContext">The value of <see cref="databaseContext"/>.</param>
/// <param name="dataLoader">The value of <see cref="dataLoader"/>.</param>
/// <param name="usersDataLoader">The value of <see cref="usersDataLoader"/>.</param>
/// <param name="oAuthConnectionsDataLoader">The value of <see cref="oAuthConnectionsDataLoader"/>.</param>
/// <param name="authenticationContext">The value of <see cref="authenticationContext"/>.</param>
public UserAuthority(
ILogger<UserAuthority> logger,
IDatabaseContext databaseContext,
IUsersDataLoader dataLoader,
IUsersDataLoader usersDataLoader,
IOAuthConnectionsDataLoader oAuthConnectionsDataLoader,
IAuthenticationContext authenticationContext)
: base(logger)
{
this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
this.dataLoader = dataLoader ?? throw new ArgumentNullException(nameof(dataLoader));
this.usersDataLoader = usersDataLoader ?? throw new ArgumentNullException(nameof(usersDataLoader));
this.oAuthConnectionsDataLoader = oAuthConnectionsDataLoader ?? throw new ArgumentNullException(nameof(oAuthConnectionsDataLoader));
this.authenticationContext = authenticationContext ?? throw new ArgumentNullException(nameof(authenticationContext));
}
@@ -93,7 +128,7 @@ namespace Tgstation.Server.Host.Authority
cancellationToken);
}
else
user = await dataLoader.LoadAsync(id, cancellationToken);
user = await usersDataLoader.LoadAsync(id, cancellationToken);
if (user == default)
return NotFound<User>();
@@ -108,6 +143,11 @@ namespace Tgstation.Server.Host.Authority
public IQueryable<User> Queryable(bool includeJoins)
=> Queryable(includeJoins, false);
/// <inheritdoc />
public async ValueTask<AuthorityResponse<GraphQL.Types.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken)
=> new AuthorityResponse<GraphQL.Types.OAuthConnection[]>(
await oAuthConnectionsDataLoader.LoadRequiredAsync(userId, cancellationToken));
/// <summary>
/// Gets all registered <see cref="User"/>s.
/// </summary>
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -94,9 +93,17 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// <summary>
/// List of <see cref="OAuthConnection"/>s associated with the user if OAuth is configured.
/// </summary>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="List{T}"/> of <see cref="OAuthConnection"/>s for the <see cref="User"/> if OAuth is configured.</returns>
public ValueTask<List<OAuthConnection>>? OAuthConnections()
=> 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>A <see cref="ValueTask{TResult}"/> resulting in a new <see cref="Array"/> of <see cref="OAuthConnection"/>s for the <see cref="User"/> if OAuth is configured.</returns>
public async ValueTask<OAuthConnection[]> OAuthConnections(
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(userAuthority);
return (await userAuthority.Invoke<OAuthConnection[], OAuthConnection[]>(
authority => authority.OAuthConnections(Id, cancellationToken)))!;
}
/// <summary>
/// The <see cref="Types.PermissionSet"/> directly associated with the <see cref="User"/>, if any.
@@ -8,6 +8,11 @@
/// </summary>
public long Id { get; set; }
/// <summary>
/// The <see cref="Api.Models.EntityId.Id"/> of <see cref="User"/>.
/// </summary>
public long UserId { get; set; }
/// <summary>
/// The owning <see cref="Models.User"/>.
/// </summary>