diff --git a/src/Tgstation.Server.Api/Models/InstanceUser.cs b/src/Tgstation.Server.Api/Models/InstanceUser.cs
index 4e25521934..58ed24033c 100644
--- a/src/Tgstation.Server.Api/Models/InstanceUser.cs
+++ b/src/Tgstation.Server.Api/Models/InstanceUser.cs
@@ -13,13 +13,15 @@ namespace Tgstation.Server.Api.Models
/// The of the the belongs to
///
[Permissions(DenyWrite = true)]
- public long UserId { get; set; }
+ [Required]
+ public long? UserId { get; set; }
///
/// The of the the belongs to
///
[Permissions(DenyWrite = true)]
- public long InstanceId { get; set; }
+ [Required]
+ public long? InstanceId { get; set; }
///
/// The of the
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index d5665f6a4b..fb18f46897 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Net;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Rights;
@@ -63,7 +64,7 @@ namespace Tgstation.Server.Host.Controllers
DreamMakerSettings = new DreamMakerSettings(),
Name = model.Name,
Online = false,
- Path = model.Path,
+ Path = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? model.Path.ToUpperInvariant() : model.Path,
RepositorySettings = new RepositorySettings()
};
@@ -91,15 +92,12 @@ namespace Tgstation.Server.Host.Controllers
throw;
}
}
- catch (Exception e)
+ catch (DbUpdateConcurrencyException e)
{
return Conflict(new { message = e.Message });
}
-
- model.Online = newInstance.Online;
- model.Id = newInstance.Id;
-
- return Json(model);
+
+ return Json(newInstance.ToApi());
}
///
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs
new file mode 100644
index 0000000000..abe94e092c
--- /dev/null
+++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs
@@ -0,0 +1,161 @@
+using Microsoft.AspNetCore.Mvc;
+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;
+using Tgstation.Server.Api.Rights;
+using Tgstation.Server.Host.Models;
+using Tgstation.Server.Host.Security;
+using Z.EntityFramework.Plus;
+
+namespace Tgstation.Server.Host.Controllers
+{
+ ///
+ /// For managing s
+ ///
+ [Route("/" + nameof(Models.InstanceUser))]
+ public sealed class InstanceUserController : ModelController
+ {
+ ///
+ /// The for the
+ ///
+ readonly ILogger logger;
+
+ ///
+ /// Construct a
+ ///
+ /// The for the
+ /// The for the
+ /// The value of
+ public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory)
+ {
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ /// Checks a for errors
+ ///
+ /// The to check
+ /// A explaining any errors, if none
+ BadRequestObjectResult StandardModelChecks(Api.Models.InstanceUser model)
+ {
+ if (model == null)
+ throw new ArgumentNullException(nameof(model));
+
+ if (model.InstanceId.HasValue && model.InstanceId != Instance.Id)
+ return BadRequest(new { message = "InstanceId does not match headers!" });
+
+ if (!model.InstanceId.HasValue)
+ return BadRequest(new { message = "Missing UserId!" });
+
+ return null;
+ }
+
+ ///
+ [TgsAuthorize(AdministrationRights.EditUsers)]
+ public override async Task Create([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken)
+ {
+ var test = StandardModelChecks(model);
+ if (test != null)
+ return test;
+
+ var dbUser = new Models.InstanceUser
+ {
+ ByondRights = model.ByondRights ?? ByondRights.None,
+ ChatSettingsRights = model.ChatSettingsRights ?? ChatSettingsRights.None,
+ ConfigurationRights = model.ConfigurationRights ?? ConfigurationRights.None,
+ DreamDaemonRights = model.DreamDaemonRights ?? DreamDaemonRights.None,
+ DreamMakerRights = model.DreamMakerRights ?? DreamMakerRights.None,
+ RepositoryRights = model.RepositoryRights ?? RepositoryRights.None,
+ UserId = model.UserId
+ };
+
+ var ourInstance = new Models.Instance
+ {
+ Id = Instance.Id
+ };
+
+ DatabaseContext.Instances.Attach(ourInstance);
+
+ ourInstance.InstanceUsers.Add(dbUser);
+
+ try
+ {
+ await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
+ }
+ catch (DbUpdateConcurrencyException e)
+ {
+ return Conflict(new { message = e.Message });
+ }
+ return Json(dbUser.ToApi());
+ }
+
+ ///
+ [TgsAuthorize(AdministrationRights.EditUsers)]
+ public override async Task Update([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken)
+ {
+ var test = StandardModelChecks(model);
+ if (test != null)
+ return test;
+
+ var originalUser = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == model.UserId).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ if (originalUser == null)
+ return StatusCode((int)HttpStatusCode.Gone);
+
+ originalUser.ByondRights = model.ByondRights ?? originalUser.ByondRights;
+ originalUser.ChatSettingsRights = model.ChatSettingsRights ?? originalUser.ChatSettingsRights;
+ originalUser.ConfigurationRights = model.ConfigurationRights ?? originalUser.ConfigurationRights;
+ originalUser.DreamDaemonRights = model.DreamDaemonRights ?? originalUser.DreamDaemonRights;
+ originalUser.DreamMakerRights = model.DreamMakerRights ?? originalUser.DreamMakerRights;
+
+ try
+ {
+ await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
+ }
+ catch (DbUpdateConcurrencyException e)
+ {
+ return Conflict(new { message = e.Message });
+ }
+ return Json(originalUser.ToApi());
+ }
+
+ ///
+ [TgsAuthorize]
+ public override Task Read(CancellationToken cancellationToken) => Task.FromResult(AuthenticationContext.InstanceUser != null ? (IActionResult)Json(AuthenticationContext.InstanceUser.ToApi()) : NotFound());
+
+ ///
+ [TgsAuthorize(InstanceUserRights.ReadUsers)]
+ public override async Task List(CancellationToken cancellationToken)
+ {
+ var users = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).ToListAsync(cancellationToken).ConfigureAwait(false);
+ return Json(users.Select(x => x.ToApi()));
+ }
+
+ ///
+ [TgsAuthorize(InstanceUserRights.ReadUsers)]
+ public override async Task GetId(long id, CancellationToken cancellationToken)
+ {
+ //this functions as userId
+ var user = await DatabaseContext.Instances.Where(x => x.Id == id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
+ if (user == default)
+ return NotFound();
+ return Json(user.ToApi());
+ }
+
+ ///
+ [TgsAuthorize(AdministrationRights.EditUsers)]
+ public override async Task Delete([FromBody] Api.Models.InstanceUser model, CancellationToken cancellationToken)
+ {
+ var test = StandardModelChecks(model);
+ if (test != null)
+ return test;
+
+ await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == model.UserId).DeleteAsync(cancellationToken).ConfigureAwait(false);
+ return Ok();
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs
index a84d50c8f5..cefec12234 100644
--- a/src/Tgstation.Server.Host/Controllers/JobController.cs
+++ b/src/Tgstation.Server.Host/Controllers/JobController.cs
@@ -40,7 +40,7 @@ namespace Tgstation.Server.Host.Controllers
IQueryable query = DatabaseContext.Jobs;
if (Instance != null)
{
- if (!AuthenticationContext.InstanceUser.AnyRights)
+ if (AuthenticationContext.InstanceUser?.AnyRights != true)
return Forbid();
query = query.Where(x => x.Instance.Id == Instance.Id);
}
diff --git a/src/Tgstation.Server.Host/Controllers/UsersController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs
similarity index 89%
rename from src/Tgstation.Server.Host/Controllers/UsersController.cs
rename to src/Tgstation.Server.Host/Controllers/UserController.cs
index c7ccafa5d7..50be28d1dd 100644
--- a/src/Tgstation.Server.Host/Controllers/UsersController.cs
+++ b/src/Tgstation.Server.Host/Controllers/UserController.cs
@@ -18,32 +18,32 @@ namespace Tgstation.Server.Host.Controllers
/// For managing s
///
[Route("/" + nameof(Models.User))]
- public sealed class UsersController : ModelController
+ public sealed class UserController : ModelController
{
///
- /// The for the
+ /// The for the
///
readonly ISystemIdentityFactory systemIdentityFactory;
///
- /// The for the
+ /// The for the
///
readonly ICryptographySuite cryptographySuite;
///
- /// The for the
+ /// The for the
///
- readonly ILogger logger;
+ readonly ILogger logger;
///
- /// Construct a
+ /// Construct a
///
/// The for the
/// The for the
/// The value of
/// The value of
/// The value of
- public UsersController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, ILogger logger) : base(databaseContext, authenticationContextFactory)
+ public UserController(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));
@@ -110,9 +110,9 @@ namespace Tgstation.Server.Host.Controllers
throw;
}
}
- catch (DbUpdateConcurrencyException)
+ catch (DbUpdateConcurrencyException e)
{
- return Conflict();
+ return Conflict(new { message = e.Message });
}
return Json(dbUser.ToApi());
@@ -160,7 +160,7 @@ namespace Tgstation.Server.Host.Controllers
public override async Task List(CancellationToken cancellationToken)
{
var users = await DatabaseContext.Users.ToListAsync(cancellationToken).ConfigureAwait(false);
- return Json(users);
+ return Json(users.Select(x => x.ToApi()));
}
///
@@ -170,7 +170,7 @@ namespace Tgstation.Server.Host.Controllers
var user = await DatabaseContext.Users.Where(x => x.Id == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (user == default)
return NotFound();
- return Json(user);
+ return Json(user.ToApi());
}
}
}
diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs
index 5ca0997c1e..44099444d8 100644
--- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs
+++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs
@@ -106,13 +106,13 @@ namespace Tgstation.Server.Host.Models
// build default model.
LogModelBuilderHelper.Build(modelBuilder.Entity());
modelBuilder.Entity().ToTable(nameof(Logs));
-
+ modelBuilder.Entity().HasIndex(x => x.Path).IsUnique();
modelBuilder.Entity().HasIndex(x => x.Commit).IsUnique();
var user = modelBuilder.Entity();
user.HasIndex(x => x.CanonicalName).IsUnique();
user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict);
- modelBuilder.Entity().HasIndex(x => new { x.UserId, x.Instance }).IsUnique();
+ modelBuilder.Entity().HasIndex(x => new { x.UserId, x.InstanceId }).IsUnique();
var chatChannel = modelBuilder.Entity();
chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique();
diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs
index a9d82058d2..290417b1fe 100644
--- a/src/Tgstation.Server.Host/Models/Instance.cs
+++ b/src/Tgstation.Server.Host/Models/Instance.cs
@@ -1,11 +1,12 @@
using System.Collections.Generic;
+using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Models
{
///
/// Represents an in the database
///
- public sealed class Instance : Api.Models.Instance
+ public sealed class Instance : Api.Models.Instance, IApiConvertable
{
///
/// The for the
@@ -51,5 +52,16 @@ namespace Tgstation.Server.Host.Models
/// The in the
///
public List Jobs { get; set; }
+
+ ///
+ public Api.Models.Instance ToApi() => new Api.Models.Instance
+ {
+ AutoUpdateInterval = AutoUpdateInterval,
+ ConfigurationAllowed = ConfigurationAllowed,
+ Id = Id,
+ Name = Name,
+ Path = Path,
+ Online = Online
+ };
}
}
diff --git a/src/Tgstation.Server.Host/Models/InstanceUser.cs b/src/Tgstation.Server.Host/Models/InstanceUser.cs
index 37fc313e04..43d9fae655 100644
--- a/src/Tgstation.Server.Host/Models/InstanceUser.cs
+++ b/src/Tgstation.Server.Host/Models/InstanceUser.cs
@@ -1,9 +1,10 @@
using System.ComponentModel.DataAnnotations;
+using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Models
{
///
- public sealed class InstanceUser : Api.Models.InstanceUser
+ public sealed class InstanceUser : Api.Models.InstanceUser, IApiConvertable
{
///
/// The row Id
@@ -24,5 +25,18 @@ namespace Tgstation.Server.Host.Models
ConfigurationRights != Api.Rights.ConfigurationRights.None ||
DreamDaemonRights != Api.Rights.DreamDaemonRights.None ||
DreamMakerRights != Api.Rights.DreamMakerRights.None;
+
+ ///
+ public Api.Models.InstanceUser ToApi() => new Api.Models.InstanceUser
+ {
+ ByondRights = ByondRights,
+ ChatSettingsRights = ChatSettingsRights,
+ ConfigurationRights = ConfigurationRights,
+ DreamDaemonRights = DreamDaemonRights,
+ DreamMakerRights = DreamMakerRights,
+ InstanceId = InstanceId,
+ RepositoryRights = RepositoryRights,
+ UserId = UserId
+ };
}
}