diff --git a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs index d95d7d58a1..b0a67681cd 100644 --- a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs +++ b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs @@ -55,10 +55,14 @@ namespace Tgstation.Server.Host.Authority /// Creates a . /// /// The . + /// If a zero-length indicates and OAuth only user. /// The for the operation. /// A resulting in am for the created . [TgsAuthorize(AdministrationRights.WriteUsers)] - ValueTask> Create(UserCreateRequest createRequest, CancellationToken cancellationToken); + ValueTask> Create( + UserCreateRequest createRequest, + bool? needZeroLengthPasswordWithOAuthConnections, + CancellationToken cancellationToken); /// /// Updates a . diff --git a/src/Tgstation.Server.Host/Authority/UserAuthority.cs b/src/Tgstation.Server.Host/Authority/UserAuthority.cs index ed7dba0997..b670c01243 100644 --- a/src/Tgstation.Server.Host/Authority/UserAuthority.cs +++ b/src/Tgstation.Server.Host/Authority/UserAuthority.cs @@ -202,6 +202,7 @@ namespace Tgstation.Server.Host.Authority /// public async ValueTask> Create( UserCreateRequest createRequest, + bool? needZeroLengthPasswordWithOAuthConnections, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(createRequest); @@ -209,10 +210,28 @@ namespace Tgstation.Server.Host.Authority if (createRequest.OAuthConnections?.Any(x => x == null) == true) return BadRequest(ErrorCode.ModelValidationFailure); - if ((createRequest.Password != null && createRequest.SystemIdentifier != null) - || (createRequest.Password == null && createRequest.SystemIdentifier == null && (createRequest.OAuthConnections?.Count > 0) != true)) + var hasNonNullPassword = createRequest.Password != null; + var hasNonNullSystemIdentifier = createRequest.SystemIdentifier != null; + var hasOAuthConnections = (createRequest.OAuthConnections?.Count > 0) == true; + if ((hasNonNullPassword && hasNonNullSystemIdentifier) + || (!hasNonNullPassword && !hasNonNullSystemIdentifier && !hasOAuthConnections)) return BadRequest(ErrorCode.UserMismatchPasswordSid); + var hasZeroLengthPassword = createRequest.Password?.Length == 0; + if (needZeroLengthPasswordWithOAuthConnections.HasValue) + { + if (needZeroLengthPasswordWithOAuthConnections.Value) + { + if (createRequest.OAuthConnections == null) + throw new InvalidOperationException($"Expected {nameof(UserCreateRequest.OAuthConnections)} to be set here!"); + + if (createRequest.OAuthConnections.Count == 0) + return BadRequest(ErrorCode.ModelValidationFailure); + } + else if (hasZeroLengthPassword) + return BadRequest(ErrorCode.ModelValidationFailure); + } + if (createRequest.Group != null && createRequest.PermissionSet != null) return BadRequest(ErrorCode.UserGroupAndPermissionSet); @@ -254,11 +273,14 @@ namespace Tgstation.Server.Host.Authority new ErrorMessageResponse(ErrorCode.RequiresPosixSystemIdentity), HttpFailureResponse.NotImplemented); } - else if (!(createRequest.Password?.Length == 0 && (createRequest.OAuthConnections?.Count > 0) == true)) + else { - var result = TrySetPassword(dbUser, createRequest.Password!, true); - if (result != null) - return result; + if (!(needZeroLengthPasswordWithOAuthConnections != false && hasZeroLengthPassword && hasOAuthConnections)) // special case allow PasswordHash to be null by setting Password to "" if OAuthConnections are set + { + var result = TrySetPassword(dbUser, createRequest.Password!, true); + if (result != null) + return result; + } } dbUser.CanonicalName = User.CanonicalizeName(dbUser.Name!); diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index be6866f928..f55218ef64 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -67,7 +67,7 @@ namespace Tgstation.Server.Host.Controllers [TgsRestAuthorize(nameof(IUserAuthority.Create))] [ProducesResponseType(typeof(UserResponse), 201)] public ValueTask Create([FromBody] UserCreateRequest model, CancellationToken cancellationToken) - => userAuthority.InvokeTransformable(this, authority => authority.Create(model, cancellationToken)); + => userAuthority.InvokeTransformable(this, authority => authority.Create(model, null, cancellationToken)); /// /// Update a . diff --git a/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs b/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs index 177ddbd1f1..50c8587e0a 100644 --- a/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs +++ b/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs @@ -72,53 +72,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations }) .ToList(), }, - cancellationToken)); - } - - /// - /// Creates a system user specifying a personal . - /// - /// The of the . - /// If the is . - /// The s for the user. - /// The owned of the user. - /// The for the . - /// The for the operation. - /// The created . - [TgsGraphQLAuthorize(nameof(IUserAuthority.Create))] - [Error(typeof(ErrorMessageException))] - public ValueTask CreateUserBySystemIDAndPermissionSet( - string systemIdentifier, - bool? enabled, - IEnumerable? oAuthConnections, - PermissionSetInput permissionSet, - [Service] IGraphQLAuthorityInvoker userAuthority, - CancellationToken cancellationToken) - { - ArgumentException.ThrowIfNullOrWhiteSpace(systemIdentifier); - ArgumentNullException.ThrowIfNull(userAuthority); - - return userAuthority.InvokeTransformable( - authority => authority.Create( - new UserCreateRequest - { - SystemIdentifier = systemIdentifier, - Enabled = enabled, - PermissionSet = permissionSet != null - ? new Api.Models.PermissionSet - { - AdministrationRights = permissionSet.AdministrationRights, - InstanceManagerRights = permissionSet.InstanceManagerRights, - } - : null, - OAuthConnections = oAuthConnections - ?.Select(oAuthConnection => new Api.Models.OAuthConnection - { - ExternalUserId = oAuthConnection.ExternalUserId, - Provider = oAuthConnection.Provider, - }) - .ToList(), - }, + false, cancellationToken)); } @@ -167,6 +121,152 @@ namespace Tgstation.Server.Host.GraphQL.Mutations }) .ToList(), }, + false, + cancellationToken)); + } + + /// + /// Creates a TGS user authenticated with one or mor s specifying a personal . + /// + /// The of the . + /// The s for the user. + /// If the is . + /// The owned of the user. + /// The for the . + /// The for the operation. + /// The created . + [TgsGraphQLAuthorize(nameof(IUserAuthority.Create))] + [Error(typeof(ErrorMessageException))] + public ValueTask CreateUserByOAuthAndPermissionSet( + string name, + IEnumerable oAuthConnections, + bool? enabled, + PermissionSetInput? permissionSet, + [Service] IGraphQLAuthorityInvoker userAuthority, + CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + ArgumentNullException.ThrowIfNull(oAuthConnections); + ArgumentNullException.ThrowIfNull(userAuthority); + + return userAuthority.InvokeTransformable( + authority => authority.Create( + new UserCreateRequest + { + Name = name, + Password = String.Empty, + Enabled = enabled, + PermissionSet = permissionSet != null + ? new Api.Models.PermissionSet + { + AdministrationRights = permissionSet.AdministrationRights, + InstanceManagerRights = permissionSet.InstanceManagerRights, + } + : null, + OAuthConnections = oAuthConnections + .Select(oAuthConnection => new Api.Models.OAuthConnection + { + ExternalUserId = oAuthConnection.ExternalUserId, + Provider = oAuthConnection.Provider, + }) + .ToList(), + }, + true, + cancellationToken)); + } + + /// + /// Creates a TGS user specifying the they will belong to. + /// + /// The of the . + /// The s for the user. + /// The of the the will belong to. + /// If the is . + /// The for the . + /// The for the operation. + /// The created . + [TgsGraphQLAuthorize(nameof(IUserAuthority.Create))] + [Error(typeof(ErrorMessageException))] + public ValueTask CreateUserByOAuthAndGroup( + string name, + IEnumerable oAuthConnections, + [ID(nameof(UserGroup))] long groupId, + bool? enabled, + [Service] IGraphQLAuthorityInvoker userAuthority, + CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + ArgumentNullException.ThrowIfNull(oAuthConnections); + ArgumentNullException.ThrowIfNull(userAuthority); + + return userAuthority.InvokeTransformable( + authority => authority.Create( + new UserCreateRequest + { + Name = name, + Password = String.Empty, + Enabled = enabled, + Group = new Api.Models.Internal.UserGroup + { + Id = groupId, + }, + OAuthConnections = oAuthConnections + .Select(oAuthConnection => new Api.Models.OAuthConnection + { + ExternalUserId = oAuthConnection.ExternalUserId, + Provider = oAuthConnection.Provider, + }) + .ToList(), + }, + true, + cancellationToken)); + } + + /// + /// Creates a system user specifying a personal . + /// + /// The of the . + /// If the is . + /// The s for the user. + /// The owned of the user. + /// The for the . + /// The for the operation. + /// The created . + [TgsGraphQLAuthorize(nameof(IUserAuthority.Create))] + [Error(typeof(ErrorMessageException))] + public ValueTask CreateUserBySystemIDAndPermissionSet( + string systemIdentifier, + bool? enabled, + IEnumerable? oAuthConnections, + PermissionSetInput permissionSet, + [Service] IGraphQLAuthorityInvoker userAuthority, + CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(systemIdentifier); + ArgumentNullException.ThrowIfNull(userAuthority); + + return userAuthority.InvokeTransformable( + authority => authority.Create( + new UserCreateRequest + { + SystemIdentifier = systemIdentifier, + Enabled = enabled, + PermissionSet = permissionSet != null + ? new Api.Models.PermissionSet + { + AdministrationRights = permissionSet.AdministrationRights, + InstanceManagerRights = permissionSet.InstanceManagerRights, + } + : null, + OAuthConnections = oAuthConnections + ?.Select(oAuthConnection => new Api.Models.OAuthConnection + { + ExternalUserId = oAuthConnection.ExternalUserId, + Provider = oAuthConnection.Provider, + }) + .ToList(), + }, + false, cancellationToken)); } @@ -175,8 +275,8 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// /// The of the . /// If the is . - /// The s for the user. /// The of the the will belong to. + /// The s for the user. /// The for the . /// The for the operation. /// The created . @@ -185,8 +285,8 @@ namespace Tgstation.Server.Host.GraphQL.Mutations public ValueTask CreateUserBySystemIDAndGroup( string systemIdentifier, bool? enabled, - IEnumerable? oAuthConnections, [ID(nameof(UserGroup))] long groupId, + IEnumerable? oAuthConnections, [Service] IGraphQLAuthorityInvoker userAuthority, CancellationToken cancellationToken) { @@ -211,6 +311,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations }) .ToList(), }, + false, cancellationToken)); }