mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-07-19 03:53:02 +01:00
OIDC First Implementation
This commit is contained in:
+6
-6
@@ -3,13 +3,13 @@
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<Import Project="WebpanelVersion.props" />
|
||||
<PropertyGroup>
|
||||
<TgsCoreVersion>6.14.1</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.5.0</TgsConfigVersion>
|
||||
<TgsRestVersion>10.12.1</TgsRestVersion>
|
||||
<TgsGraphQLVersion>0.5.0</TgsGraphQLVersion>
|
||||
<TgsCoreVersion>6.15.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.6.0</TgsConfigVersion>
|
||||
<TgsRestVersion>10.13.0</TgsRestVersion>
|
||||
<TgsGraphQLVersion>0.6.0</TgsGraphQLVersion>
|
||||
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
|
||||
<TgsApiLibraryVersion>17.0.1</TgsApiLibraryVersion>
|
||||
<TgsClientVersion>20.0.0</TgsClientVersion>
|
||||
<TgsApiLibraryVersion>18.0.0</TgsApiLibraryVersion>
|
||||
<TgsClientVersion>21.0.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>7.3.1</TgsDmapiVersion>
|
||||
<TgsInteropVersion>5.10.0</TgsInteropVersion>
|
||||
<TgsHostWatchdogVersion>1.6.0</TgsHostWatchdogVersion>
|
||||
|
||||
@@ -53,6 +53,11 @@ namespace Tgstation.Server.Api
|
||||
/// </summary>
|
||||
public const string OAuthAuthenticationScheme = "OAuth";
|
||||
|
||||
/// <summary>
|
||||
/// The OIDC authentication header scheme.
|
||||
/// </summary>
|
||||
public const string OpenIDConnectAuthenticationSchemePrefix = "OpenIdConnect.";
|
||||
|
||||
/// <summary>
|
||||
/// Added to <see cref="MediaTypeNames.Application"/> in netstandard2.1. Can't use because of lack of .NET Framework support.
|
||||
/// </summary>
|
||||
@@ -103,6 +108,11 @@ namespace Tgstation.Server.Api
|
||||
/// </summary>
|
||||
public TokenResponse? Token { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The parsed <see cref="AuthenticationHeaderValue"/> if any.
|
||||
/// </summary>
|
||||
public AuthenticationHeaderValue? AuthenticationHeader { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The client's username.
|
||||
/// </summary>
|
||||
@@ -252,6 +262,7 @@ namespace Tgstation.Server.Api
|
||||
{
|
||||
splits.RemoveAt(0);
|
||||
var parameter = String.Concat(splits);
|
||||
AuthenticationHeader = new AuthenticationHeaderValue(scheme, parameter);
|
||||
if (String.IsNullOrEmpty(parameter))
|
||||
AddError(HeaderErrorTypes.AuthorizationInvalid, "Missing authentication parameter!");
|
||||
else
|
||||
@@ -320,7 +331,8 @@ namespace Tgstation.Server.Api
|
||||
Password = String.Concat(basicAuthSplits.Skip(1));
|
||||
break;
|
||||
default:
|
||||
AddError(HeaderErrorTypes.AuthorizationInvalid, "Invalid authentication scheme!");
|
||||
if (!scheme.StartsWith(OpenIDConnectAuthenticationSchemePrefix))
|
||||
AddError(HeaderErrorTypes.AuthorizationInvalid, "Invalid authentication scheme!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,7 +584,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// Attempted to set <see cref="Internal.UserApiBase.OAuthConnections"/> for the admin user.
|
||||
/// </summary>
|
||||
[Description("The admin user cannot use OAuth connections!")]
|
||||
AdminUserCannotOAuth,
|
||||
AdminUserCannotHaveServiceConnection,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to login with a disabled OAuth provider.
|
||||
|
||||
@@ -10,6 +10,11 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
public ICollection<OAuthConnection>? OAuthConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="OidcConnection"/>s associated with the user.
|
||||
/// </summary>
|
||||
public ICollection<OidcConnection>? OidcConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.PermissionSet"/> directly associated with the user.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,7 +6,7 @@ using Newtonsoft.Json.Converters;
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// List of OAuth providers supported by TGS.
|
||||
/// List of OAuth2.0 providers supported by TGS that do not support OIDC.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum OAuthProvider
|
||||
@@ -24,12 +24,13 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Pre-hostening https://tgstation13.org. No longer supported.
|
||||
/// </summary>
|
||||
[Obsolete("tgstation13.org no longer has a custom OAuth solution", true)]
|
||||
[Obsolete("tgstation13.org no longer has a custom OAuth solution. This option will be removed in a future TGS version.", true)]
|
||||
TGForums,
|
||||
|
||||
/// <summary>
|
||||
/// https://www.keycloak.org.
|
||||
/// </summary>
|
||||
[Obsolete("This should now be implemented as an OIDC provider. This option will be removed in a future TGS version.")]
|
||||
Keycloak,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a valid OIDC connection.
|
||||
/// </summary>
|
||||
public class OidcConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="OidcProviderInfo.SchemeKey"/> of the <see cref="OidcConnection"/>.
|
||||
/// </summary>]
|
||||
[Required]
|
||||
[RequestOptions(FieldPresence.Required)]
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
public string? SchemeKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the user in the OIDC proivder ("sub" claim).
|
||||
/// </summary>
|
||||
[Required]
|
||||
[RequestOptions(FieldPresence.Required)]
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
public string? ExternalUserId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a configured OIDC provider.
|
||||
/// </summary>
|
||||
public sealed class OidcProviderInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The scheme key used to reference the <see cref="OidcProviderInfo"/>. Unique amongst providers.
|
||||
/// </summary>
|
||||
public string? SchemeKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The provider's name as it should be displayed to the user.
|
||||
/// </summary>
|
||||
public string? FriendlyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Colour that should be used to theme this OIDC provider.
|
||||
/// </summary>
|
||||
public string? ThemeColour { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Image URL that should be used to theme this OIDC provider.
|
||||
/// </summary>
|
||||
public Uri? ThemeIconUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,11 @@ namespace Tgstation.Server.Api.Models.Response
|
||||
/// </summary>
|
||||
public Dictionary<OAuthProvider, OAuthProviderInfo>? OAuthProviderInfos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of configured <see cref="OidcProviderInfo"/>s.
|
||||
/// </summary>
|
||||
public List<OidcProviderInfo>? OidcProviderInfos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If there is a server update in progress.
|
||||
/// </summary>
|
||||
|
||||
@@ -44,9 +44,9 @@ namespace Tgstation.Server.Api.Rights
|
||||
DownloadLogs = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// User can modify their own <see cref="Models.Internal.UserApiBase.OAuthConnections"/>.
|
||||
/// User can modify their own <see cref="Models.Internal.UserApiBase.OAuthConnections"/> and <see cref="Models.Internal.UserApiBase.OidcConnections"/>.
|
||||
/// </summary>
|
||||
EditOwnOAuthConnections = 1 << 6,
|
||||
EditOwnServiceConnections = 1 << 6,
|
||||
|
||||
/// <summary>
|
||||
/// User can upgrade/downgrade TGS using file uploads through the API.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
mutation CreateUserFromOAuthConnection($name: String!, $oAuthConnections: [OAuthConnectionInput!]!) {
|
||||
createUserByOAuthAndPermissionSet(input: { name: $name, oAuthConnections: $oAuthConnections }) {
|
||||
createUserByServiceConnectionAndPermissionSet(input: { name: $name, oAuthConnections: $oAuthConnections }) {
|
||||
errors {
|
||||
... on ErrorMessageError {
|
||||
additionalData
|
||||
|
||||
@@ -7,7 +7,7 @@ mutation CreateUserGroup($name: String!) {
|
||||
administrationRights {
|
||||
canChangeVersion
|
||||
canDownloadLogs
|
||||
canEditOwnOAuthConnections
|
||||
canEditOwnServiceConnections
|
||||
canEditOwnPassword
|
||||
canReadUsers
|
||||
canRestartHost
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ mutation CreateUserGroupWithInstanceListPerm($name: String!) {
|
||||
administrationRights {
|
||||
canChangeVersion
|
||||
canDownloadLogs
|
||||
canEditOwnOAuthConnections
|
||||
canEditOwnServiceConnections
|
||||
canEditOwnPassword
|
||||
canReadUsers
|
||||
canRestartHost
|
||||
|
||||
@@ -6,7 +6,7 @@ mutation SetFullPermsOnUserGroup($id: ID!) {
|
||||
administrationRights: {
|
||||
canChangeVersion: true
|
||||
canDownloadLogs: true
|
||||
canEditOwnOAuthConnections: true
|
||||
canEditOwnServiceConnections: true
|
||||
canEditOwnPassword: true
|
||||
canReadUsers: true
|
||||
canWriteUsers: true
|
||||
@@ -36,7 +36,7 @@ mutation SetFullPermsOnUserGroup($id: ID!) {
|
||||
administrationRights {
|
||||
canChangeVersion
|
||||
canDownloadLogs
|
||||
canEditOwnOAuthConnections
|
||||
canEditOwnServiceConnections
|
||||
canEditOwnPassword
|
||||
canReadUsers
|
||||
canRestartHost
|
||||
|
||||
@@ -10,27 +10,27 @@ mutation SetUserGroup($id: ID!, $newGroupId: ID!) {
|
||||
user {
|
||||
ownedPermissionSet {
|
||||
instanceManagerRights {
|
||||
canCreate
|
||||
canDelete
|
||||
canGrantPermissions
|
||||
canList
|
||||
canRead
|
||||
canRelocate
|
||||
canRename
|
||||
canSetAutoUpdate
|
||||
canSetChatBotLimit
|
||||
canSetConfiguration
|
||||
canSetOnline
|
||||
canCreate
|
||||
canDelete
|
||||
canGrantPermissions
|
||||
canList
|
||||
canRead
|
||||
canRelocate
|
||||
canRename
|
||||
canSetAutoUpdate
|
||||
canSetChatBotLimit
|
||||
canSetConfiguration
|
||||
canSetOnline
|
||||
}
|
||||
administrationRights {
|
||||
canChangeVersion
|
||||
canDownloadLogs
|
||||
canEditOwnOAuthConnections
|
||||
canEditOwnPassword
|
||||
canReadUsers
|
||||
canRestartHost
|
||||
canUploadVersion
|
||||
canWriteUsers
|
||||
canChangeVersion
|
||||
canDownloadLogs
|
||||
canEditOwnServiceConnections
|
||||
canEditOwnPassword
|
||||
canReadUsers
|
||||
canRestartHost
|
||||
canUploadVersion
|
||||
canWriteUsers
|
||||
}
|
||||
}
|
||||
group {
|
||||
|
||||
@@ -12,7 +12,7 @@ mutation SetUserPermissionSet($id: ID!, $permissionSet: PermissionSetInput!) {
|
||||
administrationRights {
|
||||
canChangeVersion
|
||||
canDownloadLogs
|
||||
canEditOwnOAuthConnections
|
||||
canEditOwnServiceConnections
|
||||
canEditOwnPassword
|
||||
canReadUsers
|
||||
canRestartHost
|
||||
@@ -37,7 +37,7 @@ mutation SetUserPermissionSet($id: ID!, $permissionSet: PermissionSetInput!) {
|
||||
administrationRights {
|
||||
canChangeVersion
|
||||
canDownloadLogs
|
||||
canEditOwnOAuthConnections
|
||||
canEditOwnServiceConnections
|
||||
canEditOwnPassword
|
||||
canReadUsers
|
||||
canRestartHost
|
||||
|
||||
@@ -20,7 +20,7 @@ query GetSomeGroupInfo($id: ID!) {
|
||||
administrationRights {
|
||||
canChangeVersion
|
||||
canDownloadLogs
|
||||
canEditOwnOAuthConnections
|
||||
canEditOwnServiceConnections
|
||||
canEditOwnPassword
|
||||
canReadUsers
|
||||
canRestartHost
|
||||
|
||||
@@ -20,7 +20,7 @@ query ReadCurrentUser {
|
||||
administrationRights {
|
||||
canChangeVersion
|
||||
canDownloadLogs
|
||||
canEditOwnOAuthConnections
|
||||
canEditOwnServiceConnections
|
||||
canEditOwnPassword
|
||||
canReadUsers
|
||||
canRestartHost
|
||||
|
||||
@@ -43,6 +43,14 @@ namespace Tgstation.Server.Host.Authority
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in an <see cref="global::System.Array"/> of <see cref="GraphQL.Types.OAuth.OAuthConnection"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
|
||||
ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GraphQL.Types.OAuth.OidcConnection"/>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.OAuth.OidcConnection"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
|
||||
ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OidcConnection[]>> OidcConnections(long userId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all registered <see cref="User"/>s.
|
||||
/// </summary>
|
||||
@@ -70,7 +78,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
/// <param name="updateRequest">The <see cref="UserUpdateRequest"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in am <see cref="AuthorityResponse{TResult}"/> for the created <see cref="User"/>.</returns>
|
||||
[TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword | AdministrationRights.EditOwnOAuthConnections)]
|
||||
[TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword | AdministrationRights.EditOwnServiceConnections)]
|
||||
ValueTask<AuthorityResponse<User>> Update(UserUpdateRequest updateRequest, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,11 @@ namespace Tgstation.Server.Host.Authority
|
||||
/// </summary>
|
||||
readonly IOAuthConnectionsDataLoader oAuthConnectionsDataLoader;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IOidcConnectionsDataLoader"/> for the <see cref="UserAuthority"/>.
|
||||
/// </summary>
|
||||
readonly IOidcConnectionsDataLoader oidcConnectionsDataLoader;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISystemIdentityFactory"/> for the <see cref="UserAuthority"/>.
|
||||
/// </summary>
|
||||
@@ -95,7 +100,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the <see cref="usersDataLoader"/>.
|
||||
/// Implements the <see cref="oAuthConnectionsDataLoader"/>.
|
||||
/// </summary>
|
||||
/// <param name="userIds">The <see cref="IReadOnlyCollection{T}"/> of <see cref="User"/> <see cref="EntityId.Id"/>s to load the OAuthConnections for.</param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to load from.</param>
|
||||
@@ -117,10 +122,37 @@ namespace Tgstation.Server.Host.Authority
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return list.ToLookup(
|
||||
oauthConnection => oauthConnection.UserId,
|
||||
oAuthConnection => oAuthConnection.UserId,
|
||||
x => new GraphQL.Types.OAuth.OAuthConnection(x.ExternalUserId!, x.Provider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the <see cref="oidcConnectionsDataLoader"/>.
|
||||
/// </summary>
|
||||
/// <param name="userIds">The <see cref="IReadOnlyCollection{T}"/> of <see cref="User"/> <see cref="EntityId.Id"/>s to load the OidcConnections 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.OAuth.OidcConnection>> GetOidcConnections(
|
||||
IReadOnlyList<long> userIds,
|
||||
IDatabaseContext databaseContext,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(userIds);
|
||||
ArgumentNullException.ThrowIfNull(databaseContext);
|
||||
|
||||
var list = await databaseContext
|
||||
.OidcConnections
|
||||
.AsQueryable()
|
||||
.Where(x => userIds.Contains(x.User!.Id!.Value))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return list.ToLookup(
|
||||
oidcConnection => oidcConnection.UserId,
|
||||
x => new GraphQL.Types.OAuth.OidcConnection(x.ExternalUserId!, x.SchemeKey!));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given <paramref name="model"/> has a valid <see cref="UserName.Name"/> specified.
|
||||
/// </summary>
|
||||
@@ -147,6 +179,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
/// <param name="logger">The <see cref="ILogger"/> to use.</param>
|
||||
/// <param name="usersDataLoader">The value of <see cref="usersDataLoader"/>.</param>
|
||||
/// <param name="oAuthConnectionsDataLoader">The value of <see cref="oAuthConnectionsDataLoader"/>.</param>
|
||||
/// <param name="oidcConnectionsDataLoader">The value of <see cref="oidcConnectionsDataLoader"/>.</param>
|
||||
/// <param name="systemIdentityFactory">The value of <see cref="systemIdentityFactory"/>.</param>
|
||||
/// <param name="permissionsUpdateNotifyee">The value of <see cref="permissionsUpdateNotifyee"/>.</param>
|
||||
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/>.</param>
|
||||
@@ -159,6 +192,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
ILogger<UserAuthority> logger,
|
||||
IUsersDataLoader usersDataLoader,
|
||||
IOAuthConnectionsDataLoader oAuthConnectionsDataLoader,
|
||||
IOidcConnectionsDataLoader oidcConnectionsDataLoader,
|
||||
ISystemIdentityFactory systemIdentityFactory,
|
||||
IPermissionsUpdateNotifyee permissionsUpdateNotifyee,
|
||||
ICryptographySuite cryptographySuite,
|
||||
@@ -172,6 +206,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
{
|
||||
this.usersDataLoader = usersDataLoader ?? throw new ArgumentNullException(nameof(usersDataLoader));
|
||||
this.oAuthConnectionsDataLoader = oAuthConnectionsDataLoader ?? throw new ArgumentNullException(nameof(oAuthConnectionsDataLoader));
|
||||
this.oidcConnectionsDataLoader = oidcConnectionsDataLoader ?? throw new ArgumentNullException(nameof(oidcConnectionsDataLoader));
|
||||
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
|
||||
this.permissionsUpdateNotifyee = permissionsUpdateNotifyee ?? throw new ArgumentNullException(nameof(permissionsUpdateNotifyee));
|
||||
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
|
||||
@@ -289,6 +324,11 @@ namespace Tgstation.Server.Host.Authority
|
||||
=> new AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>(
|
||||
await oAuthConnectionsDataLoader.LoadRequiredAsync(userId, cancellationToken));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OidcConnection[]>> OidcConnections(long userId, CancellationToken cancellationToken)
|
||||
=> new AuthorityResponse<GraphQL.Types.OAuth.OidcConnection[]>(
|
||||
await oidcConnectionsDataLoader.LoadRequiredAsync(userId, cancellationToken));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<AuthorityResponse<User>> Create(
|
||||
UserCreateRequest createRequest,
|
||||
@@ -372,7 +412,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
var callerAdministrationRights = (AdministrationRights)AuthenticationContext.GetRight(RightsType.Administration);
|
||||
var canEditAllUsers = callerAdministrationRights.HasFlag(AdministrationRights.WriteUsers);
|
||||
var passwordEdit = canEditAllUsers || callerAdministrationRights.HasFlag(AdministrationRights.EditOwnPassword);
|
||||
var oAuthEdit = canEditAllUsers || callerAdministrationRights.HasFlag(AdministrationRights.EditOwnOAuthConnections);
|
||||
var oAuthEdit = canEditAllUsers || callerAdministrationRights.HasFlag(AdministrationRights.EditOwnServiceConnections);
|
||||
|
||||
var originalUser = !canEditAllUsers
|
||||
? AuthenticationContext.User
|
||||
@@ -382,6 +422,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
.Where(x => x.Id == model.Id)
|
||||
.Include(x => x.CreatedBy)
|
||||
.Include(x => x.OAuthConnections)
|
||||
.Include(x => x.OidcConnections)
|
||||
.Include(x => x.Group!)
|
||||
.ThenInclude(x => x.PermissionSet)
|
||||
.Include(x => x.PermissionSet)
|
||||
@@ -438,7 +479,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
|| !model.OAuthConnections.All(x => originalUser.OAuthConnections.Any(y => y.Provider == x.Provider && y.ExternalUserId == x.ExternalUserId))))
|
||||
{
|
||||
if (originalUser.CanonicalName == User.CanonicalizeName(DefaultCredentials.AdminUserName))
|
||||
return BadRequest<User>(ErrorCode.AdminUserCannotOAuth);
|
||||
return BadRequest<User>(ErrorCode.AdminUserCannotHaveServiceConnection);
|
||||
|
||||
if (model.OAuthConnections.Count == 0 && originalUser.PasswordHash == null && originalUser.SystemIdentifier == null)
|
||||
return BadRequest<User>(ErrorCode.CannotRemoveLastAuthenticationOption);
|
||||
@@ -452,6 +493,25 @@ namespace Tgstation.Server.Host.Authority
|
||||
});
|
||||
}
|
||||
|
||||
if (model.OidcConnections != null
|
||||
&& (model.OidcConnections.Count != originalUser.OidcConnections!.Count
|
||||
|| !model.OidcConnections.All(x => originalUser.OidcConnections.Any(y => y.SchemeKey == x.SchemeKey && y.ExternalUserId == x.ExternalUserId))))
|
||||
{
|
||||
if (originalUser.CanonicalName == User.CanonicalizeName(DefaultCredentials.AdminUserName))
|
||||
return BadRequest<User>(ErrorCode.AdminUserCannotHaveServiceConnection);
|
||||
|
||||
if (model.OidcConnections.Count == 0 && originalUser.PasswordHash == null && originalUser.SystemIdentifier == null)
|
||||
return BadRequest<User>(ErrorCode.CannotRemoveLastAuthenticationOption);
|
||||
|
||||
originalUser.OidcConnections.Clear();
|
||||
foreach (var updatedConnection in model.OidcConnections)
|
||||
originalUser.OidcConnections.Add(new Models.OidcConnection
|
||||
{
|
||||
SchemeKey = updatedConnection.SchemeKey,
|
||||
ExternalUserId = updatedConnection.ExternalUserId,
|
||||
});
|
||||
}
|
||||
|
||||
if (model.Group != null)
|
||||
{
|
||||
originalUser.Group = await DatabaseContext
|
||||
@@ -554,6 +614,7 @@ namespace Tgstation.Server.Host.Authority
|
||||
queryable = queryable
|
||||
.Include(x => x.CreatedBy)
|
||||
.Include(x => x.OAuthConnections)
|
||||
.Include(x => x.OidcConnections)
|
||||
.Include(x => x.Group!)
|
||||
.ThenInclude(x => x.PermissionSet)
|
||||
.Include(x => x.PermissionSet);
|
||||
@@ -603,6 +664,15 @@ namespace Tgstation.Server.Host.Authority
|
||||
})
|
||||
.ToList()
|
||||
?? new List<Models.OAuthConnection>(),
|
||||
OidcConnections = model
|
||||
.OidcConnections
|
||||
?.Select(x => new Models.OidcConnection
|
||||
{
|
||||
SchemeKey = x.SchemeKey,
|
||||
ExternalUserId = x.ExternalUserId,
|
||||
})
|
||||
.ToList()
|
||||
?? new List<Models.OidcConnection>(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration for an OpenID Connect provider.
|
||||
/// </summary>
|
||||
public sealed class OidcConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Uri"/> containing the .well-known endpoint for the provider.
|
||||
/// </summary>
|
||||
public Uri? Authority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The OIDC client ID.
|
||||
/// </summary>
|
||||
public string? ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The OIDC client secret.
|
||||
/// </summary>
|
||||
public string? ClientSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The provider's name as it should be displayed to the user.
|
||||
/// </summary>
|
||||
public string? FriendlyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path to return to once OIDC authentication is complete. On success the "code" and "state" query strings will be set, containing a TGS token and "oidc.(scheme key)" strings respectively. On failure the "error" query string will be set with a user readable error message.
|
||||
/// </summary>
|
||||
public string? ReturnPath { get; set; } = "/app";
|
||||
|
||||
/// <summary>
|
||||
/// Colour that should be used to theme this OIDC provider.
|
||||
/// </summary>
|
||||
public string? ThemeColour { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Image URL that should be used to theme this OIDC provider.
|
||||
/// </summary>
|
||||
public Uri? ThemeIconUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
@@ -87,5 +88,23 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// Backing field for <see cref="OAuth"/>.
|
||||
/// </summary>
|
||||
IDictionary<OAuthProvider, OAuthConfiguration>? oAuth;
|
||||
|
||||
/// <summary>
|
||||
/// OIDC provider settings keyed by scheme name.
|
||||
/// </summary>
|
||||
public IDictionary<string, OidcConfiguration>? OpenIDConnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="OidcProviderInfo"/>s from the <see cref="SecurityConfiguration"/>.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="OidcProviderInfo"/>s.</returns>
|
||||
public IEnumerable<OidcProviderInfo> OidcProviderInfos()
|
||||
=> OpenIDConnect?.Select(oidcConfig => new OidcProviderInfo
|
||||
{
|
||||
SchemeKey = oidcConfig.Key,
|
||||
FriendlyName = oidcConfig.Value.FriendlyName ?? oidcConfig.Key,
|
||||
ThemeColour = oidcConfig.Value.ThemeColour,
|
||||
ThemeIconUrl = oidcConfig.Value.ThemeIconUrl,
|
||||
}) ?? Enumerable.Empty<OidcProviderInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SecurityConfiguration"/> for the <see cref="ApiRootController"/>.
|
||||
/// </summary>
|
||||
readonly SecurityConfiguration securityConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApiRootController"/> class.
|
||||
/// </summary>
|
||||
@@ -81,6 +86,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="swarmService">The value of <see cref="swarmService"/>.</param>
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="securityConfigurationOptions">The <see cref="IOptionsSnapshot{TOptions}"/> containing the value of <see cref="securityConfiguration"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/>.</param>
|
||||
/// <param name="apiHeadersProvider">The <see cref="IApiHeadersProvider"/> for the <see cref="ApiController"/>.</param>
|
||||
/// <param name="loginAuthority">The value of <see cref="loginAuthority"/>.</param>
|
||||
@@ -93,6 +99,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
ISwarmService swarmService,
|
||||
IServerControl serverControl,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptionsSnapshot<SecurityConfiguration> securityConfigurationOptions,
|
||||
ILogger<ApiRootController> logger,
|
||||
IApiHeadersProvider apiHeadersProvider,
|
||||
IRestAuthorityInvoker<ILoginAuthority> loginAuthority)
|
||||
@@ -109,6 +116,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
this.swarmService = swarmService ?? throw new ArgumentNullException(nameof(swarmService));
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
securityConfiguration = securityConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(securityConfigurationOptions));
|
||||
this.loginAuthority = loginAuthority ?? throw new ArgumentNullException(nameof(loginAuthority));
|
||||
}
|
||||
|
||||
@@ -162,6 +170,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
?.Select(swarmServerInfo => new SwarmServerResponse(swarmServerInfo))
|
||||
.ToList(),
|
||||
OAuthProviderInfos = oAuthProviders.ProviderInfos(),
|
||||
OidcProviderInfos = securityConfiguration.OidcProviderInfos().ToList(),
|
||||
UpdateInProgress = serverControl.UpdateInProgress,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
using Cyberboss.AspNetCore.AsyncInitializer;
|
||||
|
||||
@@ -13,7 +14,9 @@ using HotChocolate.Subscriptions;
|
||||
using HotChocolate.Types;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@@ -27,6 +30,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -121,6 +125,14 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<IWatchdogFactory, TSystemWatchdogFactory>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the OIDC authentication scheme name for a given <paramref name="schemeKey"/>.
|
||||
/// </summary>
|
||||
/// <param name="schemeKey">The scheme key for the <see cref="OidcConfiguration"/>.</param>
|
||||
/// <returns>The authentication scheme name.</returns>
|
||||
static string GetOidcScheme(string schemeKey)
|
||||
=> ApiHeaders.OpenIDConnectAuthenticationSchemePrefix + schemeKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Application"/> class.
|
||||
/// </summary>
|
||||
@@ -239,7 +251,7 @@ namespace Tgstation.Server.Host.Core
|
||||
postSetupServices.FileLoggingConfiguration);
|
||||
|
||||
// configure authentication pipeline
|
||||
ConfigureAuthenticationPipeline(services);
|
||||
ConfigureAuthenticationPipeline(services, postSetupServices.SecurityConfiguration);
|
||||
|
||||
// add mvc, configure the json serializer settings
|
||||
var jsonVersionConverterList = new List<JsonConverter>
|
||||
@@ -316,9 +328,21 @@ namespace Tgstation.Server.Host.Core
|
||||
.AddScoped<GraphQL.Subscriptions.ITopicEventReceiver, ShutdownAwareTopicEventReceiver>()
|
||||
.AddGraphQLServer()
|
||||
.AddAuthorization(
|
||||
options => options.AddPolicy(
|
||||
TgsAuthorizeAttribute.PolicyName,
|
||||
builder => builder.RequireRole(TgsAuthorizeAttribute.UserEnabledRole)))
|
||||
options =>
|
||||
{
|
||||
options.AddPolicy(
|
||||
TgsAuthorizeAttribute.PolicyName,
|
||||
builder => builder
|
||||
.RequireAuthenticatedUser()
|
||||
.RequireRole(TgsAuthorizeAttribute.UserEnabledRole));
|
||||
options.AddPolicy(
|
||||
"testingasdf",
|
||||
builder =>
|
||||
{
|
||||
builder.RequireAuthenticatedUser();
|
||||
builder.AuthenticationSchemes.Add(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
});
|
||||
})
|
||||
.ModifyOptions(options =>
|
||||
{
|
||||
options.EnsureAllNodesCanBeResolved = true;
|
||||
@@ -548,6 +572,7 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <param name="controlPanelConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the <see cref="ControlPanelConfiguration"/> to use.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the <see cref="GeneralConfiguration"/> to use.</param>
|
||||
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the <see cref="DatabaseConfiguration"/> to use.</param>
|
||||
/// <param name="securityConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the <see cref="SecurityConfiguration"/> to use.</param>
|
||||
/// <param name="swarmConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the <see cref="SwarmConfiguration"/> to use.</param>
|
||||
/// <param name="internalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the <see cref="InternalConfiguration"/> to use.</param>
|
||||
/// <param name="logger">The <see cref="Microsoft.Extensions.Logging.ILogger"/> for the <see cref="Application"/>.</param>
|
||||
@@ -560,6 +585,7 @@ namespace Tgstation.Server.Host.Core
|
||||
IOptions<ControlPanelConfiguration> controlPanelConfigurationOptions,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptions<DatabaseConfiguration> databaseConfigurationOptions,
|
||||
IOptions<SecurityConfiguration> securityConfigurationOptions,
|
||||
IOptions<SwarmConfiguration> swarmConfigurationOptions,
|
||||
IOptions<InternalConfiguration> internalConfigurationOptions,
|
||||
ILogger<Application> logger)
|
||||
@@ -728,6 +754,20 @@ namespace Tgstation.Server.Host.Core
|
||||
logger.LogTrace("Prometheus disabled");
|
||||
|
||||
endpoints.MapHealthChecks("/health");
|
||||
|
||||
var oidcConfig = securityConfigurationOptions.Value.OpenIDConnect;
|
||||
if (oidcConfig == null)
|
||||
return;
|
||||
|
||||
foreach (var kvp in oidcConfig)
|
||||
endpoints.MapGet(
|
||||
$"/oidc/{kvp.Key}/signin",
|
||||
context => context.ChallengeAsync(
|
||||
GetOidcScheme(kvp.Key),
|
||||
new AuthenticationProperties
|
||||
{
|
||||
RedirectUri = $"/oidc/{kvp.Key}/landing",
|
||||
}));
|
||||
});
|
||||
|
||||
// 404 anything that gets this far
|
||||
@@ -748,7 +788,8 @@ namespace Tgstation.Server.Host.Core
|
||||
/// Configure the <paramref name="services"/> for the authentication pipeline.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to configure.</param>
|
||||
void ConfigureAuthenticationPipeline(IServiceCollection services)
|
||||
/// <param name="securityConfiguration">The <see cref="SecurityConfiguration"/>.</param>
|
||||
void ConfigureAuthenticationPipeline(IServiceCollection services, SecurityConfiguration securityConfiguration)
|
||||
{
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddScoped<IApiHeadersProvider, ApiHeadersProvider>();
|
||||
@@ -768,8 +809,11 @@ namespace Tgstation.Server.Host.Core
|
||||
.CurrentAuthenticationContext);
|
||||
services.AddScoped<IClaimsTransformation, AuthenticationContextClaimsTransformation>();
|
||||
|
||||
services
|
||||
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
var authBuilder = services
|
||||
.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(jwtBearerOptions =>
|
||||
{
|
||||
// this line isn't actually run until the first request is made
|
||||
@@ -798,13 +842,85 @@ namespace Tgstation.Server.Host.Core
|
||||
.HttpContext
|
||||
.RequestServices
|
||||
.GetRequiredService<ITokenValidator>()
|
||||
.ValidateToken(
|
||||
.ValidateTgsToken(
|
||||
context,
|
||||
context
|
||||
.HttpContext
|
||||
.RequestAborted),
|
||||
};
|
||||
});
|
||||
|
||||
var oidcConfig = securityConfiguration.OpenIDConnect;
|
||||
if (oidcConfig == null || oidcConfig.Count == 0)
|
||||
return;
|
||||
|
||||
authBuilder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
foreach (var kvp in oidcConfig)
|
||||
{
|
||||
var configName = kvp.Key;
|
||||
authBuilder
|
||||
.AddOpenIdConnect(
|
||||
GetOidcScheme(configName),
|
||||
options =>
|
||||
{
|
||||
var config = kvp.Value;
|
||||
|
||||
options.Authority = config.Authority?.ToString();
|
||||
options.ClientId = config.ClientId;
|
||||
options.ClientSecret = config.ClientSecret;
|
||||
|
||||
options.Scope.Add(OpenIdConnectScope.OpenId);
|
||||
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
|
||||
|
||||
#if DEBUG
|
||||
options.RequireHttpsMetadata = false;
|
||||
#endif
|
||||
|
||||
options.SaveTokens = true;
|
||||
options.ResponseType = OpenIdConnectResponseType.Code;
|
||||
options.MapInboundClaims = false;
|
||||
|
||||
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
|
||||
var basePath = $"/oidc/{configName}/";
|
||||
options.CallbackPath = new PathString(basePath + "signin-callback");
|
||||
options.SignedOutCallbackPath = new PathString(basePath + "signout-callback");
|
||||
options.RemoteSignOutPath = new PathString(basePath + "signout");
|
||||
|
||||
options.Events = new OpenIdConnectEvents
|
||||
{
|
||||
OnTokenValidated = context => context
|
||||
.HttpContext
|
||||
.RequestServices
|
||||
.GetRequiredService<ITokenValidator>()
|
||||
.ValidateOidcToken(
|
||||
context,
|
||||
context
|
||||
.HttpContext
|
||||
.RequestAborted),
|
||||
OnRemoteFailure = context =>
|
||||
{
|
||||
context.HandleResponse();
|
||||
context.HttpContext.Response.Redirect($"{config.ReturnPath}?error={HttpUtility.UrlEncode(context.Failure?.Message ?? $"{options.Events.OnRemoteFailure} was called without an {nameof(Exception)}!")}&state=oidc.{HttpUtility.UrlEncode(configName)}");
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnTicketReceived = context =>
|
||||
{
|
||||
var services = context
|
||||
.HttpContext
|
||||
.RequestServices;
|
||||
var tokenFactory = services
|
||||
.GetRequiredService<ITokenFactory>();
|
||||
var authenticationContext = services
|
||||
.GetRequiredService<IAuthenticationContext>();
|
||||
context.HandleResponse();
|
||||
context.HttpContext.Response.Redirect($"{config.ReturnPath}?code={HttpUtility.UrlEncode(tokenFactory.CreateToken(authenticationContext.User, true))}&state=oidc.{HttpUtility.UrlEncode(configName)}");
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,11 @@ namespace Tgstation.Server.Host.Database
|
||||
/// </summary>
|
||||
public DbSet<OAuthConnection> OAuthConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="OidcConnection"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<OidcConnection> OidcConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PermissionSet"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
@@ -153,6 +158,9 @@ namespace Tgstation.Server.Host.Database
|
||||
/// <inheritdoc />
|
||||
IDatabaseCollection<OAuthConnection> IDatabaseContext.OAuthConnections => oAuthConnections;
|
||||
|
||||
/// <inheritdoc />
|
||||
IDatabaseCollection<OidcConnection> IDatabaseContext.OidcConnections => oidcConnections;
|
||||
|
||||
/// <inheritdoc />
|
||||
IDatabaseCollection<UserGroup> IDatabaseContext.Groups => groups;
|
||||
|
||||
@@ -239,6 +247,11 @@ namespace Tgstation.Server.Host.Database
|
||||
/// </summary>
|
||||
readonly IDatabaseCollection<OAuthConnection> oAuthConnections;
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for <see cref="IDatabaseContext.OidcConnections"/>.
|
||||
/// </summary>
|
||||
readonly IDatabaseCollection<OidcConnection> oidcConnections;
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for <see cref="IDatabaseContext.Groups"/>.
|
||||
/// </summary>
|
||||
@@ -288,6 +301,7 @@ namespace Tgstation.Server.Host.Database
|
||||
jobsCollection = new DatabaseCollection<Job>(Jobs!);
|
||||
reattachInformationsCollection = new DatabaseCollection<ReattachInformation>(ReattachInformations!);
|
||||
oAuthConnections = new DatabaseCollection<OAuthConnection>(OAuthConnections!);
|
||||
oidcConnections = new DatabaseCollection<OidcConnection>(OidcConnections!);
|
||||
groups = new DatabaseCollection<UserGroup>(Groups!);
|
||||
permissionSets = new DatabaseCollection<PermissionSet>(PermissionSets!);
|
||||
}
|
||||
@@ -387,8 +401,10 @@ namespace Tgstation.Server.Host.Database
|
||||
userModel.HasIndex(x => x.SystemIdentifier).IsUnique();
|
||||
userModel.HasMany(x => x.TestMerges).WithOne(x => x.MergedBy).OnDelete(DeleteBehavior.Restrict);
|
||||
userModel.HasMany(x => x.OAuthConnections).WithOne(x => x.User).OnDelete(DeleteBehavior.Cascade);
|
||||
userModel.HasMany(x => x.OidcConnections).WithOne(x => x.User).OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<OAuthConnection>().HasIndex(x => new { x.Provider, x.ExternalUserId }).IsUnique();
|
||||
modelBuilder.Entity<OidcConnection>().HasIndex(x => new { x.SchemeKey, x.ExternalUserId }).IsUnique();
|
||||
|
||||
var groupsModel = modelBuilder.Entity<UserGroup>();
|
||||
groupsModel.HasIndex(x => x.Name).IsUnique();
|
||||
|
||||
@@ -90,6 +90,11 @@ namespace Tgstation.Server.Host.Database
|
||||
/// </summary>
|
||||
IDatabaseCollection<OAuthConnection> OAuthConnections { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DbSet{TEntity}"/> for <see cref="OidcConnection"/>s.
|
||||
/// </summary>
|
||||
IDatabaseCollection<OidcConnection> OidcConnections { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DbSet{TEntity}"/> for <see cref="UserGroup"/>s.
|
||||
/// </summary>
|
||||
|
||||
Generated
+1146
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MSAddOidcConnections : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(migrationBuilder);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OidcConnections",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
UserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
SchemeKey = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
ExternalUserId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OidcConnections", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OidcConnections_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OidcConnections_SchemeKey_ExternalUserId",
|
||||
table: "OidcConnections",
|
||||
columns: new[] { "SchemeKey", "ExternalUserId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OidcConnections_UserId",
|
||||
table: "OidcConnections",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OidcConnections");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1217
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MYAddOidcConnections : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(migrationBuilder);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OidcConnections",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
UserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
SchemeKey = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ExternalUserId = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OidcConnections", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OidcConnections_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OidcConnections_SchemeKey_ExternalUserId",
|
||||
table: "OidcConnections",
|
||||
columns: new[] { "SchemeKey", "ExternalUserId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OidcConnections_UserId",
|
||||
table: "OidcConnections",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OidcConnections");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1140
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class PGAddOidcConnections : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(migrationBuilder);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OidcConnections",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
UserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
SchemeKey = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
ExternalUserId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OidcConnections", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OidcConnections_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OidcConnections_SchemeKey_ExternalUserId",
|
||||
table: "OidcConnections",
|
||||
columns: new[] { "SchemeKey", "ExternalUserId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OidcConnections_UserId",
|
||||
table: "OidcConnections",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OidcConnections");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1111
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SLAddOidcConnections : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(migrationBuilder);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OidcConnections",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
SchemeKey = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
ExternalUserId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OidcConnections", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OidcConnections_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OidcConnections_SchemeKey_ExternalUserId",
|
||||
table: "OidcConnections",
|
||||
columns: new[] { "SchemeKey", "ExternalUserId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OidcConnections_UserId",
|
||||
table: "OidcConnections",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OidcConnections");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("ProductVersion", "8.0.13")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||
@@ -512,6 +512,37 @@ namespace Tgstation.Server.Host.Database
|
||||
b.ToTable("OAuthConnections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("ExternalUserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<string>("SchemeKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("SchemeKey", "ExternalUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OidcConnections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
|
||||
{
|
||||
b.Property<long?>("Id")
|
||||
@@ -996,6 +1027,17 @@ namespace Tgstation.Server.Host.Database
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "User")
|
||||
.WithMany("OidcConnections")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
|
||||
@@ -1152,6 +1194,8 @@ namespace Tgstation.Server.Host.Database
|
||||
|
||||
b.Navigation("OAuthConnections");
|
||||
|
||||
b.Navigation("OidcConnections");
|
||||
|
||||
b.Navigation("PermissionSet");
|
||||
|
||||
b.Navigation("TestMerges");
|
||||
|
||||
+45
-1
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("ProductVersion", "8.0.13")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
@@ -471,6 +471,37 @@ namespace Tgstation.Server.Host.Database
|
||||
b.ToTable("OAuthConnections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("ExternalUserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("SchemeKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("SchemeKey", "ExternalUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OidcConnections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
|
||||
{
|
||||
b.Property<long?>("Id")
|
||||
@@ -919,6 +950,17 @@ namespace Tgstation.Server.Host.Database
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "User")
|
||||
.WithMany("OidcConnections")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
|
||||
@@ -1075,6 +1117,8 @@ namespace Tgstation.Server.Host.Database
|
||||
|
||||
b.Navigation("OAuthConnections");
|
||||
|
||||
b.Navigation("OidcConnections");
|
||||
|
||||
b.Navigation("PermissionSet");
|
||||
|
||||
b.Navigation("TestMerges");
|
||||
|
||||
+45
-1
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("ProductVersion", "8.0.13")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
@@ -474,6 +474,37 @@ namespace Tgstation.Server.Host.Database
|
||||
b.ToTable("OAuthConnections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("ExternalUserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("SchemeKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("SchemeKey", "ExternalUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OidcConnections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
|
||||
{
|
||||
b.Property<long?>("Id")
|
||||
@@ -925,6 +956,17 @@ namespace Tgstation.Server.Host.Database
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "User")
|
||||
.WithMany("OidcConnections")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
|
||||
@@ -1081,6 +1123,8 @@ namespace Tgstation.Server.Host.Database
|
||||
|
||||
b.Navigation("OAuthConnections");
|
||||
|
||||
b.Navigation("OidcConnections");
|
||||
|
||||
b.Navigation("PermissionSet");
|
||||
|
||||
b.Navigation("TestMerges");
|
||||
|
||||
+43
-1
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Database
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.10");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.13");
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
|
||||
{
|
||||
@@ -460,6 +460,35 @@ namespace Tgstation.Server.Host.Database
|
||||
b.ToTable("OAuthConnections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ExternalUserId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SchemeKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("SchemeKey", "ExternalUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OidcConnections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
|
||||
{
|
||||
b.Property<long?>("Id")
|
||||
@@ -892,6 +921,17 @@ namespace Tgstation.Server.Host.Database
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.OidcConnection", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "User")
|
||||
.WithMany("OidcConnections")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group")
|
||||
@@ -1048,6 +1088,8 @@ namespace Tgstation.Server.Host.Database
|
||||
|
||||
b.Navigation("OAuthConnections");
|
||||
|
||||
b.Navigation("OidcConnections");
|
||||
|
||||
b.Navigation("PermissionSet");
|
||||
|
||||
b.Navigation("TestMerges");
|
||||
|
||||
@@ -66,6 +66,7 @@ namespace Tgstation.Server.Host.Extensions
|
||||
applicationBuilder.ApplicationServices.GetRequiredService<IOptions<ControlPanelConfiguration>>(),
|
||||
applicationBuilder.ApplicationServices.GetRequiredService<IOptions<GeneralConfiguration>>(),
|
||||
applicationBuilder.ApplicationServices.GetRequiredService<IOptions<DatabaseConfiguration>>(),
|
||||
applicationBuilder.ApplicationServices.GetRequiredService<IOptions<SecurityConfiguration>>(),
|
||||
applicationBuilder.ApplicationServices.GetRequiredService<IOptions<SwarmConfiguration>>(),
|
||||
applicationBuilder.ApplicationServices.GetRequiredService<IOptions<InternalConfiguration>>(),
|
||||
applicationBuilder.ApplicationServices.GetRequiredService<ILogger<Application>>());
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <param name="password">The password of the <see cref="User"/>.</param>
|
||||
/// <param name="enabled">If the <see cref="User"/> is <see cref="User.Enabled"/>.</param>
|
||||
/// <param name="oAuthConnections">The <see cref="OAuthConnection"/>s for the user.</param>
|
||||
/// <param name="oidcConnections">The <see cref="OidcConnection"/>s for the user.</param>
|
||||
/// <param name="permissionSet">The owned <see cref="PermissionSet"/> of the user.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
@@ -44,6 +45,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
string password,
|
||||
bool? enabled,
|
||||
IEnumerable<OAuthConnection>? oAuthConnections,
|
||||
IEnumerable<OidcConnection>? oidcConnections,
|
||||
PermissionSetInput? permissionSet,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -73,6 +75,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
Provider = oAuthConnection.Provider,
|
||||
})
|
||||
.ToList(),
|
||||
OidcConnections = oidcConnections
|
||||
?.Select(oidcConnection => new Api.Models.OidcConnection
|
||||
{
|
||||
ExternalUserId = oidcConnection.ExternalUserId,
|
||||
SchemeKey = oidcConnection.SchemeKey,
|
||||
})
|
||||
.ToList(),
|
||||
},
|
||||
false,
|
||||
cancellationToken));
|
||||
@@ -85,6 +94,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <param name="password">The password of the <see cref="User"/>.</param>
|
||||
/// <param name="enabled">If the <see cref="User"/> is <see cref="User.Enabled"/>.</param>
|
||||
/// <param name="oAuthConnections">The <see cref="OAuthConnection"/>s for the user.</param>
|
||||
/// <param name="oidcConnections">The <see cref="OidcConnection"/>s for the user.</param>
|
||||
/// <param name="groupId">The <see cref="Entity.Id"/> of the <see cref="UserGroup"/> the <see cref="User"/> will belong to.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
@@ -96,6 +106,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
string password,
|
||||
bool? enabled,
|
||||
IEnumerable<OAuthConnection>? oAuthConnections,
|
||||
IEnumerable<OidcConnection>? oidcConnections,
|
||||
[ID(nameof(UserGroup))] long groupId,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -122,16 +133,24 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
Provider = oAuthConnection.Provider,
|
||||
})
|
||||
.ToList(),
|
||||
OidcConnections = oidcConnections
|
||||
?.Select(oidcConnection => new Api.Models.OidcConnection
|
||||
{
|
||||
ExternalUserId = oidcConnection.ExternalUserId,
|
||||
SchemeKey = oidcConnection.SchemeKey,
|
||||
})
|
||||
.ToList(),
|
||||
},
|
||||
false,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TGS user authenticated with one or mor <see cref="OAuthConnection"/>s specifying a personal <see cref="PermissionSet"/>.
|
||||
/// Creates a TGS user authenticated with one or more <see cref="OAuthConnection"/>s or <see cref="OidcConnection"/>s specifying a personal <see cref="PermissionSet"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The <see cref="NamedEntity.Name"/> of the <see cref="User"/>.</param>
|
||||
/// <param name="oAuthConnections">The <see cref="OAuthConnection"/>s for the user.</param>
|
||||
/// <param name="oidcConnections">The <see cref="OidcConnection"/>s for the user.</param>
|
||||
/// <param name="enabled">If the <see cref="User"/> is <see cref="User.Enabled"/>.</param>
|
||||
/// <param name="permissionSet">The owned <see cref="PermissionSet"/> of the user.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
@@ -139,9 +158,10 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <returns>The created <see cref="User"/>.</returns>
|
||||
[TgsGraphQLAuthorize<IUserAuthority>(nameof(IUserAuthority.Create))]
|
||||
[Error(typeof(ErrorMessageException))]
|
||||
public ValueTask<User> CreateUserByOAuthAndPermissionSet(
|
||||
public ValueTask<User> CreateUserByServiceConnectionAndPermissionSet(
|
||||
string name,
|
||||
IEnumerable<OAuthConnection> oAuthConnections,
|
||||
IEnumerable<OAuthConnection>? oAuthConnections,
|
||||
IEnumerable<OidcConnection>? oidcConnections,
|
||||
bool? enabled,
|
||||
PermissionSetInput? permissionSet,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
@@ -172,16 +192,24 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
Provider = oAuthConnection.Provider,
|
||||
})
|
||||
.ToList(),
|
||||
OidcConnections = oidcConnections
|
||||
?.Select(oidcConnection => new Api.Models.OidcConnection
|
||||
{
|
||||
ExternalUserId = oidcConnection.ExternalUserId,
|
||||
SchemeKey = oidcConnection.SchemeKey,
|
||||
})
|
||||
.ToList(),
|
||||
},
|
||||
true,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TGS user specifying the <see cref="UserGroup"/> they will belong to.
|
||||
/// Creates a TGS user using <see cref="OAuthConnection"/>s and/or <see cref="OidcConnection"/>s specifying the <see cref="UserGroup"/> they will belong to.
|
||||
/// </summary>
|
||||
/// <param name="name">The <see cref="NamedEntity.Name"/> of the <see cref="User"/>.</param>
|
||||
/// <param name="oAuthConnections">The <see cref="OAuthConnection"/>s for the user.</param>
|
||||
/// <param name="oidcConnections">The <see cref="OidcConnection"/>s for the user.</param>
|
||||
/// <param name="groupId">The <see cref="Entity.Id"/> of the <see cref="UserGroup"/> the <see cref="User"/> will belong to.</param>
|
||||
/// <param name="enabled">If the <see cref="User"/> is <see cref="User.Enabled"/>.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
@@ -189,9 +217,10 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <returns>The created <see cref="User"/>.</returns>
|
||||
[TgsGraphQLAuthorize<IUserAuthority>(nameof(IUserAuthority.Create))]
|
||||
[Error(typeof(ErrorMessageException))]
|
||||
public ValueTask<User> CreateUserByOAuthAndGroup(
|
||||
public ValueTask<User> CreateUserByServiceConnectionAndGroup(
|
||||
string name,
|
||||
IEnumerable<OAuthConnection> oAuthConnections,
|
||||
IEnumerable<OidcConnection> oidcConnections,
|
||||
[ID(nameof(UserGroup))] long groupId,
|
||||
bool? enabled,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
@@ -219,6 +248,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
Provider = oAuthConnection.Provider,
|
||||
})
|
||||
.ToList(),
|
||||
OidcConnections = oidcConnections
|
||||
?.Select(oidcConnection => new Api.Models.OidcConnection
|
||||
{
|
||||
ExternalUserId = oidcConnection.ExternalUserId,
|
||||
SchemeKey = oidcConnection.SchemeKey,
|
||||
})
|
||||
.ToList(),
|
||||
},
|
||||
true,
|
||||
cancellationToken));
|
||||
@@ -230,6 +266,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <param name="systemIdentifier">The <see cref="User.SystemIdentifier"/> of the <see cref="User"/>.</param>
|
||||
/// <param name="enabled">If the <see cref="User"/> is <see cref="User.Enabled"/>.</param>
|
||||
/// <param name="oAuthConnections">The <see cref="OAuthConnection"/>s for the user.</param>
|
||||
/// <param name="oidcConnections">The <see cref="OidcConnection"/>s for the user.</param>
|
||||
/// <param name="permissionSet">The owned <see cref="PermissionSet"/> of the user.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
@@ -240,6 +277,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
string systemIdentifier,
|
||||
bool? enabled,
|
||||
IEnumerable<OAuthConnection>? oAuthConnections,
|
||||
IEnumerable<OidcConnection>? oidcConnections,
|
||||
PermissionSetInput permissionSet,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -267,6 +305,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
Provider = oAuthConnection.Provider,
|
||||
})
|
||||
.ToList(),
|
||||
OidcConnections = oidcConnections
|
||||
?.Select(oidcConnection => new Api.Models.OidcConnection
|
||||
{
|
||||
ExternalUserId = oidcConnection.ExternalUserId,
|
||||
SchemeKey = oidcConnection.SchemeKey,
|
||||
})
|
||||
.ToList(),
|
||||
},
|
||||
false,
|
||||
cancellationToken));
|
||||
@@ -279,6 +324,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <param name="enabled">If the <see cref="User"/> is <see cref="User.Enabled"/>.</param>
|
||||
/// <param name="groupId">The <see cref="Entity.Id"/> of the <see cref="UserGroup"/> the <see cref="User"/> will belong to.</param>
|
||||
/// <param name="oAuthConnections">The <see cref="OAuthConnection"/>s for the user.</param>
|
||||
/// <param name="oidcConnections">The <see cref="OidcConnection"/>s for the user.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>The created <see cref="User"/>.</returns>
|
||||
@@ -289,6 +335,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
bool? enabled,
|
||||
[ID(nameof(UserGroup))] long groupId,
|
||||
IEnumerable<OAuthConnection>? oAuthConnections,
|
||||
IEnumerable<OidcConnection>? oidcConnections,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -312,6 +359,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
Provider = oAuthConnection.Provider,
|
||||
})
|
||||
.ToList(),
|
||||
OidcConnections = oidcConnections
|
||||
?.Select(oidcConnection => new Api.Models.OidcConnection
|
||||
{
|
||||
ExternalUserId = oidcConnection.ExternalUserId,
|
||||
SchemeKey = oidcConnection.SchemeKey,
|
||||
})
|
||||
.ToList(),
|
||||
},
|
||||
false,
|
||||
cancellationToken));
|
||||
@@ -346,17 +400,19 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current user's <see cref="OAuthConnection"/>s.
|
||||
/// Sets the current user's <see cref="OAuthConnection"/>s and <see cref="OidcConnection"/>s.
|
||||
/// </summary>
|
||||
/// <param name="newOAuthConnections">The new <see cref="OAuthConnection"/>s for the current user.</param>
|
||||
/// <param name="newOAuthConnections">Optional new <see cref="OAuthConnection"/>s for the current <see cref="User"/>.</param>
|
||||
/// <param name="newOidcConnections">Optional new <see cref="OidcConnection"/>s for the current <see cref="User"/>.</param>
|
||||
/// <param name="authenticationContext">The <see cref="IAuthenticationContext"/> to get the <see cref="Entity.Id"/> of the user.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>The updated current <see cref="User"/>.</returns>
|
||||
[TgsGraphQLAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnOAuthConnections)]
|
||||
[TgsGraphQLAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnServiceConnections)]
|
||||
[Error(typeof(ErrorMessageException))]
|
||||
public ValueTask<User> SetCurrentOAuthConnections(
|
||||
IEnumerable<OAuthConnection> newOAuthConnections,
|
||||
public ValueTask<User> SetCurrentServiceConnections(
|
||||
IEnumerable<OAuthConnection>? newOAuthConnections,
|
||||
IEnumerable<OidcConnection>? newOidcConnections,
|
||||
[Service] IAuthenticationContext authenticationContext,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -375,6 +431,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
Provider = oAuthConnection.Provider,
|
||||
})
|
||||
.ToList(),
|
||||
OidcConnections = newOidcConnections
|
||||
?.Select(oidcConnection => new Api.Models.OidcConnection
|
||||
{
|
||||
ExternalUserId = oidcConnection.ExternalUserId,
|
||||
SchemeKey = oidcConnection.SchemeKey,
|
||||
})
|
||||
.ToList(),
|
||||
},
|
||||
cancellationToken));
|
||||
}
|
||||
@@ -387,6 +450,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <param name="newPassword">Optional new password for the <see cref="User"/>. Only applicable to TGS users.</param>
|
||||
/// <param name="enabled">Optional new <see cref="User.Enabled"/> status for the <see cref="User"/>.</param>
|
||||
/// <param name="newOAuthConnections">Optional new <see cref="OAuthConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="newOidcConnections">Optional new <see cref="OidcConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>The updated <see cref="User"/>.</returns>
|
||||
@@ -398,6 +462,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
string? newPassword,
|
||||
bool? enabled,
|
||||
IEnumerable<OAuthConnection>? newOAuthConnections,
|
||||
IEnumerable<OidcConnection>? newOidcConnections,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -410,6 +475,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
null,
|
||||
null,
|
||||
newOAuthConnections,
|
||||
newOidcConnections,
|
||||
userAuthority,
|
||||
cancellationToken);
|
||||
}
|
||||
@@ -422,7 +488,8 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <param name="newPassword">Optional new password for the <see cref="User"/>. Only applicable to TGS users.</param>
|
||||
/// <param name="enabled">Optional new <see cref="User.Enabled"/> status for the <see cref="User"/>.</param>
|
||||
/// <param name="newPermissionSet">Updated owned <see cref="PermissionSet"/> for the user. Note that setting this on a <see cref="User"/> in a <see cref="UserGroup"/> will remove them from that group.</param>
|
||||
/// <param name="newOAuthConnections">The new <see cref="OAuthConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="newOAuthConnections">Optional new <see cref="OAuthConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="newOidcConnections">Optional new <see cref="OidcConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>The updated <see cref="User"/>.</returns>
|
||||
@@ -435,6 +502,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
bool? enabled,
|
||||
PermissionSetInput newPermissionSet,
|
||||
IEnumerable<OAuthConnection>? newOAuthConnections,
|
||||
IEnumerable<OidcConnection>? newOidcConnections,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -447,6 +515,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
newPermissionSet,
|
||||
null,
|
||||
newOAuthConnections,
|
||||
newOidcConnections,
|
||||
userAuthority,
|
||||
cancellationToken);
|
||||
}
|
||||
@@ -459,7 +528,8 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <param name="newPassword">Optional new password for the <see cref="User"/>. Only applicable to TGS users.</param>
|
||||
/// <param name="enabled">Optional new <see cref="User.Enabled"/> status for the <see cref="User"/>.</param>
|
||||
/// <param name="newGroupId"><see cref="Entity.Id"/> of the <see cref="UserGroup"/> to move the <see cref="User"/> to.</param>
|
||||
/// <param name="newOAuthConnections">The new <see cref="OAuthConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="newOAuthConnections">Optional new <see cref="OAuthConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="newOidcConnections">Optional new <see cref="OidcConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>The updated <see cref="User"/>.</returns>
|
||||
@@ -472,6 +542,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
bool? enabled,
|
||||
[ID(nameof(UserGroup))] long newGroupId,
|
||||
IEnumerable<OAuthConnection>? newOAuthConnections,
|
||||
IEnumerable<OidcConnection>? newOidcConnections,
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -484,6 +555,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
null,
|
||||
newGroupId,
|
||||
newOAuthConnections,
|
||||
newOidcConnections,
|
||||
userAuthority,
|
||||
cancellationToken);
|
||||
}
|
||||
@@ -498,6 +570,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
/// <param name="newPermissionSet">Optional updated new owned <see cref="PermissionSet"/> for the user. Note that setting this on a <see cref="User"/> in a <see cref="UserGroup"/> will remove them from that group.</param>
|
||||
/// <param name="newGroupId">Optional <see cref="Entity.Id"/> of the <see cref="UserGroup"/> to move the <see cref="User"/> to.</param>
|
||||
/// <param name="newOAuthConnections">Optional new <see cref="OAuthConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="newOidcConnections">Optional new <see cref="OidcConnection"/>s for the <see cref="User"/>.</param>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <see cref="IUserAuthority"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>The updated <see cref="User"/>.</returns>
|
||||
@@ -509,6 +582,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
PermissionSetInput? newPermissionSet,
|
||||
long? newGroupId,
|
||||
IEnumerable<OAuthConnection>? newOAuthConnections,
|
||||
IEnumerable<OidcConnection>? newOidcConnections,
|
||||
IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
=> userAuthority.InvokeTransformable<Models.User, User, UserGraphQLTransformer>(
|
||||
@@ -539,6 +613,13 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
|
||||
Provider = oAuthConnection.Provider,
|
||||
})
|
||||
.ToList(),
|
||||
OidcConnections = newOidcConnections
|
||||
?.Select(oidcConnection => new Api.Models.OidcConnection
|
||||
{
|
||||
ExternalUserId = oidcConnection.ExternalUserId,
|
||||
SchemeKey = oidcConnection.SchemeKey,
|
||||
})
|
||||
.ToList(),
|
||||
},
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Host.GraphQL.Types.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="OAuthProvider"/> of the <see cref="OAuthConnection"/>.
|
||||
/// </summary>]
|
||||
/// </summary>
|
||||
public OAuthProvider Provider { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,21 +20,6 @@ namespace Tgstation.Server.Host.GraphQL.Types.OAuth
|
||||
/// </summary>
|
||||
public RedirectOAuthProviderInfo? GitHub { get; }
|
||||
|
||||
/// <summary>
|
||||
/// https://tgstation13.org.
|
||||
/// </summary>
|
||||
public RedirectOAuthProviderInfo? TGForums { get; }
|
||||
|
||||
/// <summary>
|
||||
/// https://www.keycloak.org.
|
||||
/// </summary>
|
||||
public FullOAuthProviderInfo? Keycloak { get; }
|
||||
|
||||
/// <summary>
|
||||
/// https://invisioncommunity.com.
|
||||
/// </summary>
|
||||
public FullOAuthProviderInfo? InvisionCommunity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthProviderInfos"/> class.
|
||||
/// </summary>
|
||||
@@ -58,8 +43,6 @@ namespace Tgstation.Server.Host.GraphQL.Types.OAuth
|
||||
|
||||
Discord = TryBuild(OAuthProvider.Discord, info => new BasicOAuthProviderInfo(info));
|
||||
GitHub = TryBuild(OAuthProvider.GitHub, info => new RedirectOAuthProviderInfo(info));
|
||||
Keycloak = TryBuild(OAuthProvider.Keycloak, info => new FullOAuthProviderInfo(info));
|
||||
InvisionCommunity = TryBuild(OAuthProvider.InvisionCommunity, info => new FullOAuthProviderInfo(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a valid OAuth connection.
|
||||
/// </summary>
|
||||
public sealed class OidcConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// The scheme key of the <see cref="OidcConnection"/>.
|
||||
/// </summary>]
|
||||
public string SchemeKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the user in the OIDC provider ("sub" claim).
|
||||
/// </summary>
|
||||
public string ExternalUserId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OidcConnection"/> class.
|
||||
/// </summary>
|
||||
/// <param name="externalUserId">The value of <see cref="ExternalUserId"/>.</param>
|
||||
/// <param name="schemeKey">The value of <see cref="SchemeKey"/>.</param>
|
||||
public OidcConnection(string externalUserId, string schemeKey)
|
||||
{
|
||||
ExternalUserId = externalUserId ?? throw new ArgumentNullException(nameof(externalUserId));
|
||||
SchemeKey = schemeKey ?? throw new ArgumentNullException(nameof(schemeKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,21 @@ namespace Tgstation.Server.Host.GraphQL.Types
|
||||
authority => authority.OAuthConnections(Id, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="OidcConnection"/>s associated with the user if OIDC is configured.
|
||||
/// </summary>
|
||||
/// <param name="userAuthority">The <see cref="IGraphQLAuthorityInvoker{TAuthority}"/> for the <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="OidcConnection"/>s for the <see cref="User"/> if OAuth is configured.</returns>
|
||||
public ValueTask<OidcConnection[]> OidcConnections(
|
||||
[Service] IGraphQLAuthorityInvoker<IUserAuthority> userAuthority,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(userAuthority);
|
||||
return userAuthority.Invoke<OidcConnection[], OidcConnection[]>(
|
||||
authority => authority.OidcConnections(Id, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PermissionSet"/> associated with the <see cref="User"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc cref="Api.Models.OidcConnection" />
|
||||
public sealed class OidcConnection : Api.Models.OidcConnection, ILegacyApiTransformable<Api.Models.OidcConnection>
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id.
|
||||
/// </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>
|
||||
[Required]
|
||||
public User? User { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Api.Models.OidcConnection ToApi() => new()
|
||||
{
|
||||
SchemeKey = SchemeKey,
|
||||
ExternalUserId = ExternalUserId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -73,10 +73,15 @@ namespace Tgstation.Server.Host.Models
|
||||
public ICollection<TestMerge>? TestMerges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TestMerge"/>s made by the <see cref="User"/>.
|
||||
/// The <see cref="OAuthConnection"/>s for the <see cref="User"/>.
|
||||
/// </summary>
|
||||
public ICollection<OAuthConnection>? OAuthConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="OidcConnection"/>s for the <see cref="User"/>.
|
||||
/// </summary>
|
||||
public ICollection<OidcConnection>? OidcConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Change a <see cref="UserName.Name"/> into a <see cref="CanonicalName"/>.
|
||||
/// </summary>
|
||||
@@ -104,6 +109,9 @@ namespace Tgstation.Server.Host.Models
|
||||
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;
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Security.Claims;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -63,6 +62,30 @@ namespace Tgstation.Server.Host.Security
|
||||
/// </summary>
|
||||
int initialized;
|
||||
|
||||
/// <summary>
|
||||
/// Parse a <see cref="DateTimeOffset"/> out of a <see cref="Claim"/> in a given <paramref name="principal"/>.
|
||||
/// </summary>
|
||||
/// <param name="principal">The <see cref="ClaimsPrincipal"/> containing claims.</param>
|
||||
/// <param name="key">The <see cref="Claim"/> name to parse from.</param>
|
||||
/// <returns>The parsed <see cref="DateTimeOffset"/>.</returns>
|
||||
static DateTimeOffset ParseTime(ClaimsPrincipal principal, string key)
|
||||
{
|
||||
var claim = principal.FindFirst(key);
|
||||
if (claim == default)
|
||||
throw new InvalidOperationException($"Missing '{key}' claim!");
|
||||
|
||||
try
|
||||
{
|
||||
return new DateTimeOffset(
|
||||
EpochTime.DateTime(
|
||||
Int64.Parse(claim.Value, CultureInfo.InvariantCulture)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to parse '{key}'!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthenticationContextFactory"/> class.
|
||||
/// </summary>
|
||||
@@ -94,7 +117,7 @@ namespace Tgstation.Server.Host.Security
|
||||
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
public async Task ValidateToken(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken)
|
||||
public async Task ValidateTgsToken(Microsoft.AspNetCore.Authentication.JwtBearer.TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken)
|
||||
#pragma warning restore CA1506
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tokenValidatedContext);
|
||||
@@ -121,26 +144,8 @@ namespace Tgstation.Server.Host.Security
|
||||
throw new InvalidOperationException("Failed to parse user ID!", e);
|
||||
}
|
||||
|
||||
DateTimeOffset ParseTime(string key)
|
||||
{
|
||||
var claim = principal.FindFirst(key);
|
||||
if (claim == default)
|
||||
throw new InvalidOperationException($"Missing '{key}' claim!");
|
||||
|
||||
try
|
||||
{
|
||||
return new DateTimeOffset(
|
||||
EpochTime.DateTime(
|
||||
Int64.Parse(claim.Value, CultureInfo.InvariantCulture)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to parse '{key}'!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
var notBefore = ParseTime(JwtRegisteredClaimNames.Nbf);
|
||||
var expires = ParseTime(JwtRegisteredClaimNames.Exp);
|
||||
var notBefore = ParseTime(principal, JwtRegisteredClaimNames.Nbf);
|
||||
var expires = ParseTime(principal, JwtRegisteredClaimNames.Exp);
|
||||
|
||||
var user = await databaseContext
|
||||
.Users
|
||||
@@ -202,5 +207,42 @@ namespace Tgstation.Server.Host.Security
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ValidateOidcToken(Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tokenValidatedContext);
|
||||
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(tokenValidatedContext.SecurityToken.Claims));
|
||||
|
||||
var userIdClaim = principal.FindFirst(JwtRegisteredClaimNames.Sub);
|
||||
if (userIdClaim == default)
|
||||
throw new InvalidOperationException($"Missing '{JwtRegisteredClaimNames.Sub}' claim!");
|
||||
|
||||
var userId = userIdClaim.Value;
|
||||
var scheme = tokenValidatedContext.Scheme.Name;
|
||||
var deprefixedScheme = scheme.Substring(ApiHeaders.OpenIDConnectAuthenticationSchemePrefix.Length);
|
||||
var connection = await databaseContext
|
||||
.OidcConnections
|
||||
.AsQueryable()
|
||||
.Where(oidcConnection => oidcConnection.ExternalUserId == userId && oidcConnection.SchemeKey == deprefixedScheme)
|
||||
.Include(oidcConnection => oidcConnection.User)
|
||||
.ThenInclude(user => user!.PermissionSet)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
if (connection == default)
|
||||
{
|
||||
tokenValidatedContext.Fail($"Unable to find user with OidcConnection for {deprefixedScheme}/{userId}!");
|
||||
return;
|
||||
}
|
||||
|
||||
var expires = ParseTime(principal, JwtRegisteredClaimNames.Exp);
|
||||
|
||||
currentAuthenticationContext.Initialize(
|
||||
connection.User!,
|
||||
expires,
|
||||
Guid.NewGuid().ToString(),
|
||||
null,
|
||||
null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ namespace Tgstation.Server.Host.Security
|
||||
/// Create a <see cref="TokenResponse"/> for a given <paramref name="user"/>.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="Models.User"/> to create the token for. Must have the <see cref="Api.Models.EntityId.Id"/> field available.</param>
|
||||
/// <param name="oAuth">Whether or not this is an OAuth login.</param>
|
||||
/// <param name="serviceLogin">Whether or not this is an external service login.</param>
|
||||
/// <returns>A new token <see cref="string"/>.</returns>
|
||||
string CreateToken(Models.User user, bool oAuth);
|
||||
string CreateToken(Models.User user, bool serviceLogin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles <see cref="TokenValidatedContext"/>s.
|
||||
/// Handles validating authentication tokens.
|
||||
/// </summary>
|
||||
public interface ITokenValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles <paramref name="tokenValidatedContext"/>.
|
||||
/// Handles TGS <paramref name="tokenValidatedContext"/>s.
|
||||
/// </summary>
|
||||
/// <param name="tokenValidatedContext">The <see cref="TokenValidatedContext"/>.</param>
|
||||
/// <param name="tokenValidatedContext">The <see cref="Microsoft.AspNetCore.Authentication.JwtBearer.TokenValidatedContext"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task ValidateToken(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken);
|
||||
Task ValidateTgsToken(Microsoft.AspNetCore.Authentication.JwtBearer.TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Handles OIDC <paramref name="tokenValidatedContext"/>s.
|
||||
/// </summary>
|
||||
/// <param name="tokenValidatedContext">The <see cref="Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenValidatedContext"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task ValidateOidcToken(Microsoft.AspNetCore.Authentication.OpenIdConnect.TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
sealed class InvisionCommunityOAuthValidator : GenericOAuthValidator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public override OAuthProvider Provider => OAuthProvider.InvisionCommunity;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri TokenUrl => new($"{OAuthConfiguration.ServerUrl}/oauth/token/"); // This needs the trailing slash or it doesnt get the token. Do not remove.
|
||||
|
||||
@@ -14,7 +14,9 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
sealed class KeycloakOAuthValidator : GenericOAuthValidator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public override OAuthProvider Provider => OAuthProvider.Keycloak;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri TokenUrl => new($"{BaseProtocolPath}/token");
|
||||
|
||||
@@ -57,14 +57,18 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
loggerFactory.CreateLogger<DiscordOAuthValidator>(),
|
||||
discordConfig));
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.Keycloak, out var keyCloakConfig))
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
validatorsBuilder.Add(
|
||||
new KeycloakOAuthValidator(
|
||||
httpClientFactory,
|
||||
loggerFactory.CreateLogger<KeycloakOAuthValidator>(),
|
||||
keyCloakConfig));
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (securityConfiguration.OAuth.TryGetValue(OAuthProvider.InvisionCommunity, out var invisionConfig))
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
validatorsBuilder.Add(
|
||||
new InvisionCommunityOAuthValidator(
|
||||
httpClientFactory,
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Tgstation.Server.Host.Security
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string CreateToken(User user, bool oAuth)
|
||||
public string CreateToken(User user, bool serviceLogin)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Tgstation.Server.Host.Security
|
||||
else
|
||||
notBefore = now;
|
||||
|
||||
var expiry = now.AddMinutes(oAuth
|
||||
var expiry = now.AddMinutes(serviceLogin
|
||||
? securityConfiguration.OAuthTokenExpiryMinutes
|
||||
: securityConfiguration.TokenExpiryMinutes);
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@
|
||||
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.0.3" />
|
||||
<!-- Usage: git interop -->
|
||||
<PackageReference Include="LibGit2Sharp" Version="0.31.0" />
|
||||
<!-- Usage: OpenID Connect support -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.13" />
|
||||
<!-- Usage: Support ""legacy"" Newotonsoft.Json in HTTP pipeline. The rest of our codebase uses Newtonsoft. -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.13" />
|
||||
<!-- Usage: Using target JSON serializer for API -->
|
||||
@@ -129,6 +131,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.13" />
|
||||
<!-- Usage: Database connectivity health check -->
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.13" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.6.0" />
|
||||
<!-- Usage: POSIX support for syscalls, signals, and symlinks -->
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<!-- Usage: Cron string parsing -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -10,7 +10,7 @@ using Moq;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
@@ -23,6 +23,10 @@ namespace Tgstation.Server.Host.Core.Tests
|
||||
[TestClass]
|
||||
public sealed class TestApplication
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestApiDefinedOidcSchemeMatchesMicrosofts()
|
||||
=> Assert.AreEqual($"{OpenIdConnectDefaults.AuthenticationScheme}.", ApiHeaders.OpenIDConnectAuthenticationSchemePrefix);
|
||||
|
||||
[TestMethod]
|
||||
public void TestMethodThrows()
|
||||
{
|
||||
@@ -40,43 +44,47 @@ namespace Tgstation.Server.Host.Core.Tests
|
||||
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.ConfigureServices(mockServiceCollection, null, null));
|
||||
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(null, null, null, null, null, null, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(null, null, null, null, null, null, null, null, null, null, null, null));
|
||||
|
||||
var mockAppBuilder = new Mock<IApplicationBuilder>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, null, null, null, null, null, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, null, null, null, null, null, null, null, null, null, null, null));
|
||||
|
||||
var mockServerControl = new Mock<IServerControl>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null, null, null, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null, null, null, null, null, null, null, null, null));
|
||||
|
||||
var mockTokenFactory = new Mock<ITokenFactory>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null, null, null, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null, null, null, null, null, null, null, null, null));
|
||||
|
||||
var mockServerPortProvider = new Mock<IServerPortProvider>();
|
||||
mockServerPortProvider.SetupGet(x => x.HttpApiPort).Returns(5345);
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, null, null, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, null, null, null, null, null, null, null, null));
|
||||
|
||||
var mockAssemblyInformationProvider = Mock.Of<IAssemblyInformationProvider>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, null, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, null, null, null, null, null, null, null));
|
||||
|
||||
var mockControlPanelOptions = new Mock<IOptions<ControlPanelConfiguration>>();
|
||||
mockControlPanelOptions.SetupGet(x => x.Value).Returns(new ControlPanelConfiguration()).Verifiable();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, null, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, null, null, null, null, null, null));
|
||||
|
||||
var mockGeneralOptions = new Mock<IOptions<GeneralConfiguration>>();
|
||||
mockGeneralOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration()).Verifiable();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, null, null, null, null, null));
|
||||
|
||||
var mockDatabaseOptions = new Mock<IOptions<DatabaseConfiguration>>();
|
||||
mockDatabaseOptions.SetupGet(x => x.Value).Returns(new DatabaseConfiguration()).Verifiable();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, null, null, null, null));
|
||||
|
||||
var mockSecurityOptions = new Mock<IOptions<SecurityConfiguration>>();
|
||||
mockDatabaseOptions.SetupGet(x => x.Value).Returns(new DatabaseConfiguration()).Verifiable();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSecurityOptions.Object, null, null, null));
|
||||
|
||||
var mockSwarmOptions = new Mock<IOptions<SwarmConfiguration>>();
|
||||
mockSwarmOptions.SetupGet(x => x.Value).Returns(new SwarmConfiguration()).Verifiable();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSwarmOptions.Object, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSecurityOptions.Object, mockSwarmOptions.Object, null, null));
|
||||
|
||||
var mockInternalOptions = new Mock<IOptions<InternalConfiguration>>();
|
||||
mockInternalOptions.SetupGet(x => x.Value).Returns(new InternalConfiguration()).Verifiable();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSwarmOptions.Object, mockInternalOptions.Object, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, mockServerPortProvider.Object, mockAssemblyInformationProvider, mockControlPanelOptions.Object, mockGeneralOptions.Object, mockDatabaseOptions.Object, mockSecurityOptions.Object, mockSwarmOptions.Object, mockInternalOptions.Object, null));
|
||||
|
||||
mockControlPanelOptions.VerifyAll();
|
||||
mockInternalOptions.VerifyAll();
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Tgstation.Server.Host.Swarm.Tests
|
||||
|
||||
public TokenValidationParameters ValidationParameters => throw new NotSupportedException();
|
||||
|
||||
public string CreateToken(User user, bool oAuth)
|
||||
public string CreateToken(User user, bool serviceLogin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
{
|
||||
Id = restUser.Id,
|
||||
OAuthConnections = sampleOAuthConnections
|
||||
}, cancellationToken), Api.Models.ErrorCode.AdminUserCannotOAuth);
|
||||
}, cancellationToken), Api.Models.ErrorCode.AdminUserCannotHaveServiceConnection);
|
||||
|
||||
var testUser = await client.Users.Create(
|
||||
new UserCreateRequest
|
||||
@@ -334,7 +334,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
Assert.IsFalse(group.PermissionSet.AdministrationRights.CanChangeVersion);
|
||||
Assert.IsFalse(group.PermissionSet.AdministrationRights.CanDownloadLogs);
|
||||
Assert.IsFalse(group.PermissionSet.AdministrationRights.CanEditOwnOAuthConnections);
|
||||
Assert.IsFalse(group.PermissionSet.AdministrationRights.CanEditOwnServiceConnections);
|
||||
Assert.IsFalse(group.PermissionSet.AdministrationRights.CanEditOwnPassword);
|
||||
Assert.IsFalse(group.PermissionSet.AdministrationRights.CanReadUsers);
|
||||
Assert.IsFalse(group.PermissionSet.AdministrationRights.CanRestartHost);
|
||||
@@ -363,7 +363,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanChangeVersion);
|
||||
Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanDownloadLogs);
|
||||
Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanEditOwnOAuthConnections);
|
||||
Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanEditOwnServiceConnections);
|
||||
Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanEditOwnPassword);
|
||||
Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanReadUsers);
|
||||
Assert.IsFalse(group2.PermissionSet.AdministrationRights.CanRestartHost);
|
||||
@@ -416,7 +416,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
|
||||
Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanChangeVersion);
|
||||
Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanDownloadLogs);
|
||||
Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanEditOwnOAuthConnections);
|
||||
Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanEditOwnServiceConnections);
|
||||
Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanEditOwnPassword);
|
||||
Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanReadUsers);
|
||||
Assert.IsTrue(group3.PermissionSet.AdministrationRights.CanRestartHost);
|
||||
@@ -448,7 +448,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
}
|
||||
],
|
||||
cancellationToken),
|
||||
data => data.CreateUserByOAuthAndPermissionSet,
|
||||
data => data.CreateUserByServiceConnectionAndPermissionSet,
|
||||
cancellationToken);
|
||||
|
||||
var testUser2 = oAuthCreateResult.User;
|
||||
@@ -483,7 +483,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
{
|
||||
CanChangeVersion = true,
|
||||
CanDownloadLogs = true,
|
||||
CanEditOwnOAuthConnections = true,
|
||||
CanEditOwnServiceConnections = true,
|
||||
CanEditOwnPassword = true,
|
||||
CanReadUsers = true,
|
||||
CanRestartHost = true,
|
||||
|
||||
Reference in New Issue
Block a user