diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs
index d0a10ed87b..0235c5d401 100644
--- a/src/Tgstation.Server.Api/ApiHeaders.cs
+++ b/src/Tgstation.Server.Api/ApiHeaders.cs
@@ -110,7 +110,7 @@ namespace Tgstation.Server.Api
}
///
- /// Construct and validates from a
+ /// Construct and validates from a set of
///
/// The containing the
public ApiHeaders(RequestHeaders requestHeaders)
diff --git a/src/Tgstation.Server.Api/Rights/RightsHelper.cs b/src/Tgstation.Server.Api/Rights/RightsHelper.cs
index 9ccbf29e22..2b3e0ddbc0 100644
--- a/src/Tgstation.Server.Api/Rights/RightsHelper.cs
+++ b/src/Tgstation.Server.Api/Rights/RightsHelper.cs
@@ -25,16 +25,6 @@ namespace Tgstation.Server.Api.Rights
{ RightsType.InstanceUser, typeof(InstanceUserRights) }
};
- static readonly IReadOnlyDictionary rightMap = CreateRightsMap();
-
- static IReadOnlyDictionary CreateRightsMap()
- {
- var dic = new Dictionary();
- foreach (var I in typeMap)
- dic.Add(I.Value, I.Key);
- return dic;
- }
-
///
/// Map a given to its respective
///
@@ -42,7 +32,20 @@ namespace Tgstation.Server.Api.Rights
/// The of the given
public static Type RightToType(RightsType rightsType) => typeMap[rightsType];
+ ///
+ /// Gets the role claim name used for a given
+ ///
+ /// The
+ /// The
+ /// A representing the claim role name
public static string RoleName(TRight right) => String.Concat(typeof(TRight).Name, '.', right.ToString());
+
+ ///
+ /// Gets the role claim name used for a given and
+ ///
+ /// The
+ /// The right value
+ /// A representing the claim role name
public static string RoleName(RightsType rightsType, int right)
{
var enumType = typeMap[rightsType];
diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs
deleted file mode 100644
index e914de955f..0000000000
--- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace Tgstation.Server.Host.Configuration
-{
- ///
- /// General configuration options
- ///
- sealed class GeneralConfiguration
- {
- ///
- /// The key for the the resides in
- ///
- public const string Section = "General";
-
- ///
- /// The string used to validate JWTs
- ///
- public string TokenSigningKey { get; set; }
- }
-}
diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs
index be7d20f51f..2df14a9d56 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs
@@ -17,18 +17,38 @@ using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Controllers
{
+ ///
+ /// A for API functions
+ ///
[Produces(ApiHeaders.ApplicationJson)]
[Consumes(ApiHeaders.ApplicationJson)]
public abstract class ApiController : Controller
{
+ ///
+ /// The for the operation
+ ///
protected ApiHeaders ApiHeaders { get; private set; }
+ ///
+ /// The for the operation
+ ///
protected IDatabaseContext DatabaseContext { get; }
+ ///
+ /// The for the operation
+ ///
protected IAuthenticationContext AuthenticationContext { get; }
+ ///
+ /// The for the operation
+ ///
protected Instance Instance { get; }
+ ///
+ /// Runs after a has been validated. Creates the for the
+ ///
+ /// The for the operation
+ /// A representing the running operation
public static async Task OnTokenValidated(TokenValidatedContext context)
{
var databaseContext = context.HttpContext.RequestServices.GetRequiredService();
@@ -75,6 +95,11 @@ namespace Tgstation.Server.Host.Controllers
context.Principal.AddIdentity(new ClaimsIdentity(claims));
}
+ ///
+ /// Construct an
+ ///
+ /// The value of
+ /// The for the
public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory)
{
DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
@@ -84,6 +109,7 @@ namespace Tgstation.Server.Host.Controllers
Instance = AuthenticationContext?.InstanceUser?.Instance;
}
+ ///
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
//validate the headers
diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs
index 58dec0d80d..02947c7272 100644
--- a/src/Tgstation.Server.Host/Controllers/HomeController.cs
+++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs
@@ -4,15 +4,18 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
-using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
+using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Controllers
{
+ ///
+ /// Main for the
+ ///
[Route("/")]
public sealed class HomeController : ApiController
{
@@ -25,21 +28,38 @@ namespace Tgstation.Server.Host.Controllers
///
readonly ISystemIdentityFactory systemIdentityFactory;
///
- /// The for the
+ /// The for the
///
- readonly IPasswordHasher passwordHasher;
+ readonly ICryptographySuite cryptographySuite;
- public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, IPasswordHasher passwordHasher) : base(databaseContext, authenticationContextFactory)
+ ///
+ /// Construct a
+ ///
+ /// The for the
+ /// The for the
+ /// The value of
+ /// The value of
+ /// The value of
+ public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite) : base(databaseContext, authenticationContextFactory)
{
this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory));
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
- this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher));
+ this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
}
+ ///
+ /// Returns the version of the
+ ///
+ ///
[Authorize]
[HttpGet]
- public JsonResult Home() => Json(Assembly.GetExecutingAssembly().GetName().Version);
+ public JsonResult Home() => Json(Application.Version);
+ ///
+ /// Attempt to authenticate a using
+ ///
+ /// The for the operation
+ /// A resulting in the of the operation
[HttpPost]
public async Task CreateToken(CancellationToken cancellationToken)
{
@@ -56,17 +76,15 @@ namespace Tgstation.Server.Host.Controllers
if (user == null)
return Unauthorized();
- if(user.PasswordHash != null)
+ if (user.PasswordHash != null)
{
- var hashResult = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, ApiHeaders.Password);
- switch (hashResult)
+ var originalHash = user.PasswordHash;
+ if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
+ return Unauthorized();
+ if (user.PasswordHash != originalHash)
{
- case PasswordVerificationResult.Failed:
- return Unauthorized();
- case PasswordVerificationResult.SuccessRehashNeeded:
- user.PasswordHash = passwordHasher.HashPassword(user, ApiHeaders.Password);
- await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
- break;
+ DatabaseContext.Users.Attach(user);
+ await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
}
}
else
diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs
index c97e498c3c..59d2b5c19a 100644
--- a/src/Tgstation.Server.Host/Controllers/ModelController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs
@@ -8,24 +8,64 @@ using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Controllers
{
- public abstract class ModelController : ApiController
+ ///
+ /// An representing a
+ ///
+ /// The model being represented
+ public abstract class ModelController : ApiController where TModel : class
{
+ ///
+ /// The of the
+ ///
protected static readonly ModelAttribute ModelAttribute = (ModelAttribute)typeof(TModel).GetCustomAttributes(typeof(ModelAttribute), true).First();
+ ///
+ /// Construct a
+ ///
+ /// The for the
+ /// The for the
public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory) { }
+ ///
+ /// Attempt to create a
+ ///
+ /// The being created
+ /// The for the operation
+ /// A resulting in the of the operation
[HttpPut]
public virtual Task Create([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
+ ///
+ /// Attempt to read a
+ ///
+ /// The for the operation
+ /// A resulting in the of the operation
[HttpGet]
public virtual Task Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
+ ///
+ /// Attempt to update a
+ ///
+ /// The being updated
+ /// The for the operation
+ /// A resulting in the of the operation
[HttpPost]
public virtual Task Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
+ ///
+ /// Attempt to delete a
+ ///
+ /// The being deleted
+ /// The for the operation
+ /// A resulting in the of the operation
[HttpDelete]
public virtual Task Delete([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
+ ///
+ /// Attempt to list entries of the
+ ///
+ /// The for the operation
+ /// A resulting in the of the operation
[HttpGet("/List")]
public virtual Task List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
}
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index 53a3e1639b..2cedce3c5e 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -2,22 +2,15 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
-using System.Linq;
using System.Reflection;
-using System.Security.Claims;
-using System.Text;
-using Tgstation.Server.Api;
-using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Controllers;
using Tgstation.Server.Host.Models;
@@ -30,6 +23,11 @@ namespace Tgstation.Server.Host.Core
///
sealed class Application
{
+ ///
+ /// The version of the
+ ///
+ public static readonly Version Version = Assembly.GetExecutingAssembly().GetName().Version;
+
///
/// The for the
///
@@ -64,17 +62,9 @@ namespace Tgstation.Server.Host.Core
var workingDir = Environment.CurrentDirectory;
var databaseConfigurationSection = configuration.GetSection(DatabaseConfiguration.Section);
services.Configure(databaseConfigurationSection);
- var generalConfigSection = configuration.GetSection(GeneralConfiguration.Section);
- services.Configure(generalConfigSection);
-
- services.AddMvc();
- services.AddOptions();
-
- var signingKey = generalConfigSection.Get().TokenSigningKey;
-
- if (signingKey == "default")
- throw new InvalidOperationException("Do not use the default signing key!");
+ services.AddOptions();
+
const string scheme = "JwtBearer";
services.AddAuthentication((options) =>
{
@@ -85,7 +75,7 @@ namespace Tgstation.Server.Host.Core
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)),
+ IssuerSigningKey = new SymmetricSecurityKey(TokenFactory.TokenSigningKey),
ValidateIssuer = true,
ValidIssuer = TokenFactory.TokenIssuer,
@@ -105,7 +95,9 @@ namespace Tgstation.Server.Host.Core
OnTokenValidated = ApiController.OnTokenValidated
};
});
- JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs
+ JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs
+
+ services.AddMvc();
var databaseConfiguration = databaseConfigurationSection.Get();
void ConfigureDatabase(DbContextOptionsBuilder builder)
diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs
index 7994ea69b6..7c7e3465c7 100644
--- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs
+++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs
@@ -1,5 +1,4 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading;
@@ -18,6 +17,7 @@ namespace Tgstation.Server.Host.Security
/// The for the
///
readonly ISystemIdentityFactory systemIdentityFactory;
+
///
/// The for the
///
diff --git a/src/Tgstation.Server.Host/Security/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs
index 7fa35d77ee..a3c5553030 100644
--- a/src/Tgstation.Server.Host/Security/CryptographySuite.cs
+++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs
@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Identity;
using System;
using System.Security.Cryptography;
-using System.Text;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Security
@@ -10,31 +9,16 @@ namespace Tgstation.Server.Host.Security
sealed class CryptographySuite : ICryptographySuite
{
///
- /// The length of secure strings used in the application
+ /// Generates a secure set of s
///
- public const int SecureStringLength = 40;
-
- ///
- /// Generates a secure ascii of length
- ///
- /// A secure ascii of length
- static string GenerateSecureString()
+ /// A secure set of s
+ public static byte[] GetSecureBytes(int amount)
{
using (var rng = new RNGCryptoServiceProvider())
{
- var byt = new byte[1];
- var result = new StringBuilder
- {
- Capacity = SecureStringLength
- };
- while (result.Length < SecureStringLength)
- {
- rng.GetBytes(byt);
- var chr = (char)byt[0];
- if (Char.IsLetterOrDigit(chr))
- result.Append(chr);
- }
- return result.ToString();
+ var byt = new byte[amount];
+ rng.GetBytes(byt);
+ return byt;
}
}
@@ -58,5 +42,19 @@ namespace Tgstation.Server.Host.Security
throw new ArgumentNullException(nameof(newPassword));
user.PasswordHash = passwordHasher.HashPassword(user, newPassword);
}
+
+ ///
+ public bool CheckUserPassword(User user, string password)
+ {
+ switch(passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password))
+ {
+ case PasswordVerificationResult.Failed:
+ return false;
+ case PasswordVerificationResult.SuccessRehashNeeded:
+ user.PasswordHash = passwordHasher.HashPassword(user, password);
+ break;
+ }
+ return true;
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs
index a5af46a902..2b1ee41a25 100644
--- a/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs
+++ b/src/Tgstation.Server.Host/Security/IAuthenticationContext.cs
@@ -19,6 +19,11 @@ namespace Tgstation.Server.Host.Security
///
InstanceUser InstanceUser { get; }
+ ///
+ /// Get the value of a given
+ ///
+ /// The of the right to get
+ /// The value of . Note that if is all based rights will return 0
int GetRight(RightsType rightsType);
///
diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs
index 4051035c0c..e0f3a225f2 100644
--- a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs
+++ b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs
@@ -8,8 +8,18 @@ namespace Tgstation.Server.Host.Security
///
public interface IAuthenticationContextFactory
{
+ ///
+ /// The the created
+ ///
IAuthenticationContext CurrentAuthenticationContext { get; }
+ ///
+ /// Create an to populate
+ ///
+ /// The of the
+ /// The of the operation
+ /// The for the operation
+ /// A representing the running operation
Task CreateAuthenticationContext(long userId, long? instanceId, CancellationToken cancellationToken);
}
}
diff --git a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs
index 21b3de1a7a..b19f805871 100644
--- a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs
+++ b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs
@@ -5,7 +5,7 @@ namespace Tgstation.Server.Host.Security
///
/// Contains various cryptographic functions
///
- interface ICryptographySuite
+ public interface ICryptographySuite
{
///
/// Sets a for a given
@@ -13,5 +13,13 @@ namespace Tgstation.Server.Host.Security
/// The whos is to be set
/// The new password for the
void SetUserPassword(User user, string newPassword);
+
+ ///
+ /// Checks a given matches a given 's . This may result in being modified and this should be persisted
+ ///
+ /// The to check
+ /// The password to check
+ /// if matches the hash, otherwise
+ bool CheckUserPassword(User user, string password);
}
}
diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs
index 174f5858ee..e0e4c9af7b 100644
--- a/src/Tgstation.Server.Host/Security/TokenFactory.cs
+++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs
@@ -1,5 +1,4 @@
-using Microsoft.Extensions.Options;
-using Microsoft.IdentityModel.Tokens;
+using Microsoft.IdentityModel.Tokens;
using System;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
@@ -7,7 +6,6 @@ using System.Reflection;
using System.Security.Claims;
using System.Text;
using Tgstation.Server.Api.Models;
-using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Security
{
@@ -16,17 +14,7 @@ namespace Tgstation.Server.Host.Security
{
public static readonly string TokenAudience = typeof(Token).Assembly.GetName().Name;
public static readonly string TokenIssuer = Assembly.GetExecutingAssembly().GetName().Name;
-
- ///
- /// The for the
- ///
- readonly GeneralConfiguration generalConfiguration;
-
- ///
- /// Construct a
- ///
- /// The containing the value of
- public TokenFactory(IOptions generalConfigurationOptions) => generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
+ public static readonly byte[] TokenSigningKey = CryptographySuite.GetSecureBytes(256);
///
public Token CreateToken(Models.User user)
@@ -43,7 +31,7 @@ namespace Tgstation.Server.Host.Security
new Claim(JwtRegisteredClaimNames.Aud, TokenAudience)
};
- var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(generalConfiguration.TokenSigningKey));
+ var key = new SymmetricSecurityKey(TokenSigningKey);
var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims));
return new Token { Bearer = new JwtSecurityTokenHandler().WriteToken(token) };
diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
index a2125b38fb..2370aabf52 100644
--- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
+++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
@@ -19,15 +19,11 @@
-
-
- PreserveNewest
-
PreserveNewest
diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json
index c2960461f8..bfcbeffa32 100644
--- a/src/Tgstation.Server.Host/appsettings.json
+++ b/src/Tgstation.Server.Host/appsettings.json
@@ -17,9 +17,6 @@
}
}
},
- "General": {
- "TokenSigningKey": "Default"
- },
"Database": {
"DatabaseType": "Sqlite",
"ConnectionString": "Fake"