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);