mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 15:07:03 +01:00
Bunch more user stuff + admin password resetting
This commit is contained in:
@@ -8,13 +8,19 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers, CanList = true, RequiresInstance = true)]
|
||||
public class InstanceUser
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Internal.User.Id"/> of the <see cref="User"/> the <see cref="InstanceUser"/> belongs to
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance.Id"/> of the <see cref="Instance"/> the <see cref="InstanceUser"/> belongs to
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.ByondRights"/> of the <see cref="InstanceUser"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -8,12 +8,6 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
[Model(RightsType.Administration)]
|
||||
public class ServerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the specified Windows/POSIX authentication group to authorize users. Changing this may enable or disable <see cref="User"/>s depending on how they were configured. Setting this to <see langword="null"/> changes the authentication mode to database.
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = AdministrationRights.ChangeAuthenticationGroup, WriteRight = AdministrationRights.ChangeAuthenticationGroup)]
|
||||
public string SystemAuthenticationGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information.
|
||||
/// </summary>
|
||||
@@ -21,7 +15,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
public bool EnableTelemetry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The git repository to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to <see langword="null"/> 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 <see langword="null"/> upstream pulls will be disabled entirely. Should be https://github.com/tgstation/tgstation-server or a fork of it
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)]
|
||||
public string UpstreamRepository { get; set; }
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
/// </summary>
|
||||
public DatabaseType DatabaseType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the admin user should be enabled and have it's password reset
|
||||
/// </summary>
|
||||
public bool ResetAdminPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The connection string for the database
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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<Models.InstanceUser>()
|
||||
};
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize]
|
||||
public override Task<IActionResult> Read(CancellationToken cancellationToken) => Task.FromResult<IActionResult>(Json(AuthenticationContext.User.ToApi()));
|
||||
|
||||
/// <inheritdoc />
|
||||
[TgsAuthorize(AdministrationRights.EditUsers)]
|
||||
public override async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
var users = await DatabaseContext.Users.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
return Json(users);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The default password mode admin password
|
||||
/// The name of the default admin user
|
||||
/// </summary>
|
||||
#pragma warning disable CA1308 // Normalize strings to uppercase
|
||||
static readonly string AdminName = "admin".ToLowerInvariant();
|
||||
#pragma warning restore CA1308 // Normalize strings to uppercase
|
||||
|
||||
/// <summary>
|
||||
/// The default admin password
|
||||
/// </summary>
|
||||
const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory";
|
||||
|
||||
@@ -30,19 +39,28 @@ namespace Tgstation.Server.Host.Models
|
||||
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/></param>
|
||||
public DatabaseSeeder(ICryptographySuite cryptographySuite) => this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
/// <summary>
|
||||
/// Add a default admin <see cref="User"/> to a given <paramref name="databaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to add an admin <see cref="User"/> to</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Tgstation.Server.Host.Models
|
||||
/// For initially seeding a database
|
||||
/// </summary>
|
||||
interface IDatabaseSeeder
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Initially seed a given <paramref name="databaseContext"/>
|
||||
/// </summary>
|
||||
@@ -15,5 +15,13 @@ namespace Tgstation.Server.Host.Models
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the admin password in <see cref="IDatabaseContext"/> back to it's default and enables the account
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to reset the admin password for</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task ResetAdminPassword(IDatabaseContext databaseContext, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Security
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => SystemIdentity.Dispose();
|
||||
public void Dispose() => SystemIdentity?.Dispose();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAuthenticationContext Clone() => new AuthenticationContext(SystemIdentity.Clone(), User, InstanceUser);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <see cref="ISystemIdentity"/> for a given <paramref name="user"/>
|
||||
/// </summary>
|
||||
/// <param name="user">The user to create a <see cref="ISystemIdentity"/> for</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A new <see cref="ISystemIdentity"/> or <see langword="null"/> if the <paramref name="user"/> has no <see cref="ISystemIdentity"/></returns>
|
||||
ISystemIdentity CreateSystemIdentity(User user);
|
||||
Task<ISystemIdentity> CreateSystemIdentity(User user, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ISystemIdentity"/> for a given username and password
|
||||
/// </summary>
|
||||
/// <param name="username">The username of the user</param>
|
||||
/// <param name="password">The password of the user</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A new <see cref="ISystemIdentity"/></returns>
|
||||
ISystemIdentity CreateSystemIdentity(string username, string password);
|
||||
Task<ISystemIdentity> CreateSystemIdentity(string username, string password, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ISystemIdentity CreateSystemIdentity(User user)
|
||||
public Task<ISystemIdentity> CreateSystemIdentity(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISystemIdentity CreateSystemIdentity(string username, string password)
|
||||
public Task<ISystemIdentity> CreateSystemIdentity(string username, string password, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
},
|
||||
"Database": {
|
||||
"DatabaseType": "SqlServer",
|
||||
"ResetAdminPassword": false,
|
||||
"ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user