diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index b1be91ff6f..6f6bb65a53 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.Linq; using System.Net.Http.Headers; using System.Reflection; +using System.Text; namespace Tgstation.Server.Api { @@ -23,27 +24,32 @@ namespace Tgstation.Server.Api /// /// The header key /// - const string ApiVersionHeader = "Api"; - - /// - /// The header key - /// - const string UsernameHeader = "Username"; + public const string ApiVersionHeader = "api"; /// /// The header key /// - const string InstanceIdHeader = "Instance"; + public const string InstanceIdHeader = "instance"; /// /// The JWT authentication header scheme /// - const string JwtAuthenticationScheme = "Bearer"; + public const string JwtAuthenticationScheme = "bearer"; /// - /// The password authentication header scheme + /// The JWT authentication header scheme /// - const string PasswordAuthenticationScheme = "Password"; + public const string BasicAuthenticationScheme = "basic"; + + /// + /// The header key + /// + const string UsernameHeader = "username"; + + /// + /// The basic authentication header scheme + /// + const string PasswordAuthenticationScheme = "password"; /// /// The current @@ -180,7 +186,9 @@ namespace Tgstation.Server.Api InstanceId = instanceId; } - switch (scheme) +#pragma warning disable CA1308 // Normalize strings to uppercase + switch (scheme.ToLowerInvariant()) +#pragma warning restore CA1308 // Normalize strings to uppercase { case JwtAuthenticationScheme: Token = parameter; @@ -197,6 +205,25 @@ namespace Tgstation.Server.Api if (fail) throw new InvalidOperationException("Missing Username header!"); break; + case BasicAuthenticationScheme: + string joinedString; + try + { + var base64Bytes = Convert.FromBase64String(parameter); + joinedString = Encoding.UTF8.GetString(base64Bytes); + } + catch + { + throw new InvalidOperationException("Invalid basic Authorization header!"); + } + + var basicAuthSplits = joinedString.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + if (basicAuthSplits.Length < 2) + throw new InvalidOperationException("Invalid basic Authorization header!"); + + Username = basicAuthSplits.First(); + Password = String.Concat(basicAuthSplits.Skip(1)); + break; default: throw new InvalidOperationException("Invalid authentication scheme!"); } @@ -241,14 +268,13 @@ namespace Tgstation.Server.Api if (IsTokenAuthentication) headers.Authorization = new AuthenticationHeaderValue(JwtAuthenticationScheme, Token); else - { - headers.Authorization = new AuthenticationHeaderValue(PasswordAuthenticationScheme, Password); - headers.Add(UsernameHeader, Username); - } + headers.Authorization = new AuthenticationHeaderValue( + BasicAuthenticationScheme, + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}"))); headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent)); headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString()); - instanceId = instanceId ?? InstanceId; + instanceId ??= InstanceId; if (instanceId.HasValue) headers.Add(InstanceIdHeader, instanceId.ToString()); } diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index c45b51dac7..249f82f589 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -1,8 +1,11 @@ using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; using System; using System.Linq; using System.Net.Mime; @@ -118,7 +121,22 @@ namespace Tgstation.Server.Host.Controllers public async Task CreateToken(CancellationToken cancellationToken) { if (ApiHeaders == null) - return BadRequest(new Api.Models.ErrorMessage { Message = "Missing API headers!" }); + { + // Get the exact error + var errorMessage = "Missing API headers!"; + try + { + var _ = new ApiHeaders(Request.GetTypedHeaders()); + } + catch (InvalidOperationException ex) + { + errorMessage = ex.Message; + } + + Response.Headers.Add(HeaderNames.WWWAuthenticate, new StringValues("basic realm=\"Create TGS4 bearer token\"")); + + return BadRequest(new Api.Models.ErrorMessage { Message = errorMessage }); + } if (ApiHeaders.IsTokenAuthentication) return BadRequest(new Api.Models.ErrorMessage { Message = "Cannot create a token using another token!" }); diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index e41f4f7380..d0fb649305 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -61,6 +61,18 @@ namespace Tgstation.Server.Host.Controllers generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } + /// + /// Check if a given has a valid specified. + /// + /// The to check. + /// if is valid, a otherwise. + BadRequestObjectResult CheckValidName(UserUpdate model) + { + if (model.Name != null && model.Name.Contains(':', StringComparison.InvariantCulture)) + return BadRequest(new ErrorMessage { Message = "Username must not contain colons!" }); + return null; + } + /// [TgsAuthorize(AdministrationRights.WriteUsers)] public override async Task Create([FromBody] UserUpdate model, CancellationToken cancellationToken) @@ -78,6 +90,10 @@ namespace Tgstation.Server.Host.Controllers if (!(model.Name == null ^ model.SystemIdentifier == null)) return BadRequest(new ErrorMessage { Message = "User must have a name if and only if user has no system identifier!" }); + var fail = CheckValidName(model); + if (fail != null) + return fail; + var dbUser = new Models.User { AdministrationRights = model.AdministrationRights ?? AdministrationRights.None, @@ -154,6 +170,11 @@ namespace Tgstation.Server.Host.Controllers originalUser.InstanceManagerRights = model.InstanceManagerRights ?? originalUser.InstanceManagerRights; originalUser.AdministrationRights = model.AdministrationRights ?? originalUser.AdministrationRights; originalUser.Enabled = model.Enabled ?? originalUser.Enabled; + + var fail = CheckValidName(model); + if (fail != null) + return fail; + originalUser.Name = model.Name ?? originalUser.Name; await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);