From b4f712d00ba034fd7aef90abaaee7fad3ab72656 Mon Sep 17 00:00:00 2001 From: Cyberboss Date: Fri, 20 Jul 2018 17:21:08 -0400 Subject: [PATCH] Bunch more user stuff + admin password resetting --- .../Models/InstanceUser.cs | 8 +++- .../Models/Internal/ServerSettings.cs | 8 +--- .../Configuration/DatabaseConfiguration.cs | 5 +++ .../Controllers/HomeController.cs | 2 +- .../Controllers/UsersController.cs | 40 +++++++++++++---- .../Models/DatabaseContext.cs | 2 + .../Models/DatabaseSeeder.cs | 43 ++++++++++++++++--- .../Models/IDatabaseSeeder.cs | 12 +++++- .../Security/AuthenticationContext.cs | 2 +- .../Security/AuthenticationContextFactory.cs | 7 +-- .../Security/ISystemIdentityFactory.cs | 10 +++-- .../Security/SystemIdentityFactory.cs | 6 ++- src/Tgstation.Server.Host/appsettings.json | 1 + 13 files changed, 112 insertions(+), 34 deletions(-) diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs index b198ceff64..4e25521934 100644 --- a/src/Tgstation.Server.Api/Models/InstanceUser.cs +++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs @@ -8,13 +8,19 @@ namespace Tgstation.Server.Api.Models /// [Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers, CanList = true, RequiresInstance = true)] public class InstanceUser - { + { /// /// The of the the belongs to /// [Permissions(DenyWrite = true)] public long UserId { get; set; } + /// + /// The of the the belongs to + /// + [Permissions(DenyWrite = true)] + public long InstanceId { get; set; } + /// /// The of the /// diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs index f044801219..3ab52adef5 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ServerSettings.cs @@ -8,12 +8,6 @@ namespace Tgstation.Server.Api.Models.Internal [Model(RightsType.Administration)] public class ServerSettings { - /// - /// Use the specified Windows/POSIX authentication group to authorize users. Changing this may enable or disable s depending on how they were configured. Setting this to changes the authentication mode to database. - /// - [Permissions(ReadRight = AdministrationRights.ChangeAuthenticationGroup, WriteRight = AdministrationRights.ChangeAuthenticationGroup)] - public string SystemAuthenticationGroup { get; set; } - /// /// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information. /// @@ -21,7 +15,7 @@ namespace Tgstation.Server.Api.Models.Internal public bool EnableTelemetry { get; set; } /// - /// The git repository to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to upstream pulls will be disabled entirely + /// The git repository URL to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to upstream pulls will be disabled entirely. Should be https://github.com/tgstation/tgstation-server or a fork of it /// [Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)] public string UpstreamRepository { get; set; } diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index 4afebe14a5..a75d463a48 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -15,6 +15,11 @@ /// public DatabaseType DatabaseType { get; set; } + /// + /// If the admin user should be enabled and have it's password reset + /// + public bool ResetAdminPassword { get; set; } + /// /// The connection string for the database /// diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 40ea8a1e91..79d6f528c3 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -100,7 +100,7 @@ namespace Tgstation.Server.Host.Controllers else try { - var systemIdentity = systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password); + using (await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false)) { } } catch { diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UsersController.cs index f66ffe1e5c..4eaaa01e6b 100644 --- a/src/Tgstation.Server.Host/Controllers/UsersController.cs +++ b/src/Tgstation.Server.Host/Controllers/UsersController.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading; @@ -59,8 +60,8 @@ namespace Tgstation.Server.Host.Controllers if (model.Name == null) return BadRequest(new { message = "Missing user name!" }); - if (model.Password == null && model.SystemIdentifier == null) - return BadRequest(new { message = "User must have either a password or system identifier!" }); + if (!(model.Password == null ^ model.SystemIdentifier == null)) + return BadRequest(new { message = "User must have exactly one of either a password or system identifier!" }); var dbUser = new Models.User { @@ -69,15 +70,22 @@ namespace Tgstation.Server.Host.Controllers CreatedBy = AuthenticationContext.User, Enabled = model.Enabled ?? false, InstanceManagerRights = model.InstanceManagerRights ?? InstanceManagerRights.None, - Name = model.Name, - SystemIdentifier = model.SystemIdentifier +#pragma warning disable CA1308 // Normalize strings to uppercase + Name = model.Name.ToLowerInvariant(), +#pragma warning restore CA1308 // Normalize strings to uppercase + SystemIdentifier = model.SystemIdentifier, + InstanceUsers = new List() }; if (model.SystemIdentifier != null) - using (var systemIdentity = systemIdentityFactory.CreateSystemIdentity(dbUser)) + try { - if (systemIdentity == null) - return Forbid(); + using (await systemIdentityFactory.CreateSystemIdentity(dbUser, cancellationToken).ConfigureAwait(false)) { } + } + catch(Exception e) + { + logger.LogInformation("System identifier user creation failure for {0}. Exception: {1}", model.SystemIdentifier, e); + return Forbid(); } else cryptographySuite.SetUserPassword(dbUser, model.Password); @@ -121,10 +129,12 @@ namespace Tgstation.Server.Host.Controllers return BadRequest(new { message = "Cannot convert a system user to a password user!" }); cryptographySuite.SetUserPassword(originalUser, model.Password); } - else if(model.SystemIdentifier != originalUser.SystemIdentifier) + else if(model.SystemIdentifier != null && model.SystemIdentifier != originalUser.SystemIdentifier) return BadRequest(new { message = "Cannot change a user's system identifier!" }); - originalUser.Name = model.Name ?? originalUser.Name; + if (model.Name != null && model.Name != originalUser.SystemIdentifier) + return BadRequest(new { message = "Cannot change a user's name!" }); + originalUser.InstanceManagerRights = model.InstanceManagerRights ?? originalUser.InstanceManagerRights; originalUser.AdministrationRights = model.AdministrationRights ?? originalUser.AdministrationRights; originalUser.Enabled = model.Enabled ?? originalUser.Enabled; @@ -133,5 +143,17 @@ namespace Tgstation.Server.Host.Controllers return Json(originalUser.ToApi()); } + + /// + [TgsAuthorize] + public override Task Read(CancellationToken cancellationToken) => Task.FromResult(Json(AuthenticationContext.User.ToApi())); + + /// + [TgsAuthorize(AdministrationRights.EditUsers)] + public override async Task List(CancellationToken cancellationToken) + { + var users = await DatabaseContext.Users.ToListAsync(cancellationToken).ConfigureAwait(false); + return Json(users); + } } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index 25caf31b19..04befa41fa 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -151,6 +151,8 @@ namespace Tgstation.Server.Host.Models #endif if (wasEmpty) await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false); + else if(databaseConfiguration.ResetAdminPassword) + await databaseSeeder.ResetAdminPassword(this, cancellationToken).ConfigureAwait(false); } /// diff --git a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs index 7ccf4f33ca..ef57b6f6b6 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseSeeder.cs @@ -1,4 +1,6 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Rights; @@ -10,7 +12,14 @@ namespace Tgstation.Server.Host.Models sealed class DatabaseSeeder : IDatabaseSeeder { /// - /// The default password mode admin password + /// The name of the default admin user + /// +#pragma warning disable CA1308 // Normalize strings to uppercase + static readonly string AdminName = "admin".ToLowerInvariant(); +#pragma warning restore CA1308 // Normalize strings to uppercase + + /// + /// The default admin password /// const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"; @@ -30,19 +39,28 @@ namespace Tgstation.Server.Host.Models /// The value of public DatabaseSeeder(ICryptographySuite cryptographySuite) => this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite)); - /// - public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken) + /// + /// Add a default admin to a given + /// + /// The to add an admin to + void SeedAdminUser(IDatabaseContext databaseContext) { var admin = new User { AdministrationRights = (AdministrationRights)~0, CreatedAt = DateTimeOffset.Now, InstanceManagerRights = (InstanceManagerRights)~0, - Name = "Admin", + Name = AdminName, Enabled = true, }; cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); databaseContext.Users.Add(admin); + } + + /// + public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken) + { + SeedAdminUser(databaseContext); var serverSettings = await databaseContext.GetServerSettings(cancellationToken).ConfigureAwait(false); @@ -51,5 +69,20 @@ namespace Tgstation.Server.Host.Models await databaseContext.Save(cancellationToken).ConfigureAwait(false); } + + /// + public async Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken) + { + var admin = await databaseContext.Users.Where(x => x.Name == AdminName).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); + if (admin == default) + SeedAdminUser(databaseContext); + else + { + admin.Enabled = true; + cryptographySuite.SetUserPassword(admin, DefaultAdminPassword); + } + + await databaseContext.Save(cancellationToken).ConfigureAwait(false); + } } } diff --git a/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs b/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs index f57620fd0a..354b3eee7b 100644 --- a/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs +++ b/src/Tgstation.Server.Host/Models/IDatabaseSeeder.cs @@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Models /// For initially seeding a database /// interface IDatabaseSeeder - { + { /// /// Initially seed a given /// @@ -15,5 +15,13 @@ namespace Tgstation.Server.Host.Models /// The for the operation /// A representing the running operation Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken); - } + + /// + /// Changes the admin password in back to it's default and enables the account + /// + /// The to reset the admin password for + /// The for the operation + /// A representing the running operation + Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken); + } } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs index 5e03fbff4b..47d3cac17e 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Security } /// - public void Dispose() => SystemIdentity.Dispose(); + public void Dispose() => SystemIdentity?.Dispose(); /// public IAuthenticationContext Clone() => new AuthenticationContext(SystemIdentity.Clone(), User, InstanceUser); diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 7c7e3465c7..1b73c7afc5 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -45,13 +45,14 @@ namespace Tgstation.Server.Host.Security if (instanceId.HasValue) userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId)); - var user = await userQuery.FirstAsync(cancellationToken).ConfigureAwait(false); + var user = await userQuery.Include(x => x.InstanceUsers).FirstAsync(cancellationToken).ConfigureAwait(false); InstanceUser instanceUser = null; if (instanceId.HasValue) - instanceUser = user.InstanceUsers.First(); + instanceUser = user.InstanceUsers.Where(x => x.InstanceId == instanceId).First(); - CurrentAuthenticationContext = new AuthenticationContext(user.SystemIdentifier != null ? systemIdentityFactory.CreateSystemIdentity(user) : null, user, instanceUser); + var systemIdentity = user.SystemIdentifier != null ? await systemIdentityFactory.CreateSystemIdentity(user, cancellationToken).ConfigureAwait(false) : null; + CurrentAuthenticationContext = new AuthenticationContext(systemIdentity, user, instanceUser); } } } diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs index 86f4f9b7e2..11ba06ebe7 100644 --- a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs @@ -1,4 +1,6 @@ -using Tgstation.Server.Host.Models; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security { @@ -11,15 +13,17 @@ namespace Tgstation.Server.Host.Security /// Create a for a given /// /// The user to create a for + /// The for the operation /// A new or if the has no - ISystemIdentity CreateSystemIdentity(User user); + Task CreateSystemIdentity(User user, CancellationToken cancellationToken); /// /// Create a for a given username and password /// /// The username of the user /// The password of the user + /// The for the operation /// A new - ISystemIdentity CreateSystemIdentity(string username, string password); + Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs index f9e9c15a87..4f384ff4b4 100644 --- a/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/SystemIdentityFactory.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security @@ -7,13 +9,13 @@ namespace Tgstation.Server.Host.Security sealed class SystemIdentityFactory : ISystemIdentityFactory { /// - public ISystemIdentity CreateSystemIdentity(User user) + public Task CreateSystemIdentity(User user, CancellationToken cancellationToken) { throw new NotImplementedException(); } /// - public ISystemIdentity CreateSystemIdentity(string username, string password) + public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 233a6dd6fa..8abbd1eb6a 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -20,6 +20,7 @@ }, "Database": { "DatabaseType": "SqlServer", + "ResetAdminPassword": false, "ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True" } }