diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs
index 5cbcd6cbbb..b198ceff64 100644
--- a/src/Tgstation.Server.Api/Models/InstanceUser.cs
+++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs
@@ -1,4 +1,5 @@
-using Tgstation.Server.Api.Rights;
+using System.ComponentModel.DataAnnotations;
+using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Api.Models
{
@@ -17,31 +18,37 @@ namespace Tgstation.Server.Api.Models
///
/// The of the
///
- public ByondRights ByondRights { get; set; }
+ [Required]
+ public ByondRights? ByondRights { get; set; }
///
/// The of the
///
- public DreamDaemonRights DreamDaemonRights { get; set; }
+ [Required]
+ public DreamDaemonRights? DreamDaemonRights { get; set; }
///
/// The of the
///
- public DreamMakerRights DreamMakerRights { get; set; }
+ [Required]
+ public DreamMakerRights? DreamMakerRights { get; set; }
///
/// The of the
///
- public RepositoryRights RepositoryRights { get; set; }
+ [Required]
+ public RepositoryRights? RepositoryRights { get; set; }
///
/// The of the
///
- public ChatSettingsRights ChatSettingsRights { get; set; }
+ [Required]
+ public ChatSettingsRights? ChatSettingsRights { get; set; }
///
/// The of the
///
- public ConfigurationRights ConfigurationRights { get; set; }
+ [Required]
+ public ConfigurationRights? ConfigurationRights { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Internal/User.cs b/src/Tgstation.Server.Api/Models/Internal/User.cs
index d71512dbbe..b70e868079 100644
--- a/src/Tgstation.Server.Api/Models/Internal/User.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/User.cs
@@ -7,7 +7,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// Represents a server
///
- [Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers, CanCrud = true)]
+ [Model(RightsType.Administration, WriteRight = Rights.AdministrationRights.EditUsers, CanCrud = true)]
public class User
{
///
@@ -19,7 +19,8 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// If the is enabled since users cannot be deleted. System users cannot be disabled
///
- public bool Enabled { get; set; }
+ [Required]
+ public bool? Enabled { get; set; }
///
/// When the was created
@@ -37,20 +38,19 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// The name of the
///
- [Permissions(WriteRight = AdministrationRights.EditUsers)]
[Required]
public string Name { get; set; }
///
/// The for the
///
- [Permissions(WriteRight = AdministrationRights.EditUsers)]
- public AdministrationRights AdministrationRights { get; set; }
+ [Required]
+ public AdministrationRights? AdministrationRights { get; set; }
///
/// The for the
///
- [Permissions(WriteRight = AdministrationRights.EditUsers)]
- public InstanceManagerRights InstanceManagerRights { get; set; }
+ [Required]
+ public InstanceManagerRights? InstanceManagerRights { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Api/Models/UserUpdate.cs b/src/Tgstation.Server.Api/Models/UserUpdate.cs
index f5f7eda6fc..4203beed1b 100644
--- a/src/Tgstation.Server.Api/Models/UserUpdate.cs
+++ b/src/Tgstation.Server.Api/Models/UserUpdate.cs
@@ -1,6 +1,4 @@
-using Tgstation.Server.Api.Rights;
-
-namespace Tgstation.Server.Api.Models
+namespace Tgstation.Server.Api.Models
{
///
/// For editing a given . Will never be returned by the API
@@ -10,7 +8,6 @@ namespace Tgstation.Server.Api.Models
///
/// Cleartext password of the
///
- [Permissions(WriteRight = AdministrationRights.EditUsers)]
public string Password { get; set; }
}
}
diff --git a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs
index 436e051429..8e2ac94dce 100644
--- a/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs
+++ b/src/Tgstation.Server.Host.Watchdog/WatchdogFactory.cs
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
+using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
@@ -9,6 +10,6 @@ namespace Tgstation.Server.Host.Watchdog
{
///
[ExcludeFromCodeCoverage]
- public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory(), loggerFactory.CreateLogger());
+ public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(new ServerFactory(), RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? (IActiveAssemblyDeleter)new WindowsActiveAssemblyDeleter() : new PosixActiveAssemblyDeleter(), new IsolatedAssemblyContextFactory(), loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory)));
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs
index 280e99cb08..e04fbd52db 100644
--- a/src/Tgstation.Server.Host/Controllers/ChatController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs
@@ -83,7 +83,15 @@ namespace Tgstation.Server.Host.Controllers
};
DatabaseContext.ChatSettings.Add(dbModel);
DatabaseContext.ChatChannels.AddRange(dbModel.Channels);
- await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ return Conflict();
+ }
try
{
diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs
index 8abb2ca69f..40ea8a1e91 100644
--- a/src/Tgstation.Server.Host/Controllers/HomeController.cs
+++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs
@@ -107,7 +107,7 @@ namespace Tgstation.Server.Host.Controllers
return Unauthorized();
}
- if (!user.Enabled)
+ if (!user.Enabled.Value)
return Forbid();
return Json(tokenFactory.CreateToken(user));
diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UsersController.cs
index 4e71f62465..f66ffe1e5c 100644
--- a/src/Tgstation.Server.Host/Controllers/UsersController.cs
+++ b/src/Tgstation.Server.Host/Controllers/UsersController.cs
@@ -2,6 +2,8 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
+using System.Linq;
+using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
@@ -43,6 +45,8 @@ namespace Tgstation.Server.Host.Controllers
public UsersController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
+ this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
}
///
@@ -60,11 +64,11 @@ namespace Tgstation.Server.Host.Controllers
var dbUser = new Models.User
{
- AdministrationRights = model.AdministrationRights,
+ AdministrationRights = model.AdministrationRights ?? AdministrationRights.None,
CreatedAt = DateTimeOffset.Now,
CreatedBy = AuthenticationContext.User,
- Enabled = model.Enabled,
- InstanceManagerRights = model.InstanceManagerRights,
+ Enabled = model.Enabled ?? false,
+ InstanceManagerRights = model.InstanceManagerRights ?? InstanceManagerRights.None,
Name = model.Name,
SystemIdentifier = model.SystemIdentifier
};
@@ -97,7 +101,37 @@ namespace Tgstation.Server.Host.Controllers
return Conflict();
}
- return Ok();
+ return Json(dbUser.ToApi());
+ }
+
+ ///
+ [TgsAuthorize(AdministrationRights.EditUsers)]
+ public override async Task Update([FromBody] UserUpdate model, CancellationToken cancellationToken)
+ {
+ if (model == null)
+ throw new ArgumentNullException(nameof(model));
+
+ var originalUser = await DatabaseContext.Users.Where(x => x.Id == model.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ if (originalUser == default)
+ return StatusCode((int)HttpStatusCode.Gone);
+
+ if (model.Password != null)
+ {
+ if (originalUser.PasswordHash == null)
+ return BadRequest(new { message = "Cannot convert a system user to a password user!" });
+ cryptographySuite.SetUserPassword(originalUser, model.Password);
+ }
+ else if(model.SystemIdentifier != originalUser.SystemIdentifier)
+ return BadRequest(new { message = "Cannot change a user's system identifier!" });
+
+ originalUser.Name = model.Name ?? originalUser.Name;
+ originalUser.InstanceManagerRights = model.InstanceManagerRights ?? originalUser.InstanceManagerRights;
+ originalUser.AdministrationRights = model.AdministrationRights ?? originalUser.AdministrationRights;
+ originalUser.Enabled = model.Enabled ?? originalUser.Enabled;
+
+ await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
+
+ return Json(originalUser.ToApi());
}
}
}
diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs
index 14d049bb59..25caf31b19 100644
--- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs
+++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs
@@ -112,6 +112,8 @@ namespace Tgstation.Server.Host.Models
user.HasIndex(x => new { x.Name, x.SystemIdentifier }).IsUnique();
user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict);
+ modelBuilder.Entity().HasIndex(x => new { x.UserId, x.Instance }).IsUnique();
+
var chatChannel = modelBuilder.Entity();
chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique();
chatChannel.HasIndex(x => new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique();
diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs
index 8e2f3b9ec5..37fc313e04 100644
--- a/src/Tgstation.Server.Host/Models/InstanceUser.cs
+++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs
@@ -1,5 +1,4 @@
using System.ComponentModel.DataAnnotations;
-using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Host.Models
{
@@ -20,10 +19,10 @@ namespace Tgstation.Server.Host.Models
///
/// If the has any instance rights
///
- public bool AnyRights => ByondRights != ByondRights.None ||
- ChatSettingsRights != ChatSettingsRights.None ||
- ConfigurationRights != ConfigurationRights.None ||
- DreamDaemonRights != DreamDaemonRights.None ||
- DreamMakerRights != DreamMakerRights.None;
+ public bool AnyRights => ByondRights != Api.Rights.ByondRights.None ||
+ ChatSettingsRights != Api.Rights.ChatSettingsRights.None ||
+ ConfigurationRights != Api.Rights.ConfigurationRights.None ||
+ DreamDaemonRights != Api.Rights.DreamDaemonRights.None ||
+ DreamMakerRights != Api.Rights.DreamMakerRights.None;
}
}
diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs
index dab8b2e60a..5e03fbff4b 100644
--- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs
+++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs
@@ -51,10 +51,15 @@ namespace Tgstation.Server.Host.Security
// use the api versions because they're the ones that contain the actual properties
var typeToCheck = isInstance ? typeof(InstanceUser) : typeof(User);
- var prop = typeToCheck.GetProperties().Where(x => x.PropertyType == rightsEnum).First();
+ var nullableType = typeof(Nullable<>);
+ var nullableRightsType = nullableType.MakeGenericType(rightsEnum);
+
+ var prop = typeToCheck.GetProperties().Where(x => x.PropertyType == nullableRightsType).First();
var right = prop.GetMethod.Invoke(isInstance ? (object)InstanceUser : User, Array.Empty