diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
index cd966e7b6d..e8d8ca75dc 100644
--- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
@@ -71,7 +71,7 @@ namespace Tgstation.Server.Host.Controllers
/// The value of
/// The value of
/// The containing value of
- public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory)
+ public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClient gitHubClient, IServerUpdater serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions) : base(databaseContext, authenticationContextFactory, false)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient));
diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs
index 30d503f871..f835f6d004 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs
@@ -43,6 +43,11 @@ namespace Tgstation.Server.Host.Controllers
///
protected Instance Instance { get; }
+ ///
+ /// If permissions are required to access the
+ ///
+ readonly bool requireInstance;
+
///
/// Runs after a has been validated. Creates the for the
///
@@ -81,6 +86,7 @@ namespace Tgstation.Server.Host.Controllers
await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, context.HttpContext.RequestAborted).ConfigureAwait(false);
+
var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext;
var enumerator = Enum.GetValues(typeof(RightsType));
@@ -103,19 +109,21 @@ namespace Tgstation.Server.Host.Controllers
///
/// The value of
/// The for the
- public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory)
+ /// The value of
+ public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, bool requireInstance)
{
DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
if (authenticationContextFactory == null)
throw new ArgumentNullException(nameof(authenticationContextFactory));
AuthenticationContext = authenticationContextFactory.CurrentAuthenticationContext;
Instance = AuthenticationContext?.InstanceUser?.Instance;
+ this.requireInstance = requireInstance;
}
///
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
- if (AuthenticationContext == null)
+ if (requireInstance && AuthenticationContext.InstanceUser == null)
{
//accessing an instance they don't have access to
await Forbid().ExecuteResultAsync(context).ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs
index 6cda73d094..195745ea11 100644
--- a/src/Tgstation.Server.Host/Controllers/ChatController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs
@@ -34,7 +34,7 @@ namespace Tgstation.Server.Host.Controllers
/// The for the
/// The for the
/// The value of
- public ChatController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory)
+ public ChatController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true)
{
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
}
diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
index fb3abd7ba4..903d08eee9 100644
--- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs
@@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Controllers
/// The for the
/// The value of
/// The value of
- public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory)
+ public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory, true)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
index b5574d49c7..0167d49112 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
@@ -39,7 +39,7 @@ namespace Tgstation.Server.Host.Controllers
/// The for the
/// The value of
/// The value of
- public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory)
+ public DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
index 3437886044..f07b232017 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs
@@ -36,7 +36,7 @@ namespace Tgstation.Server.Host.Controllers
/// The for the
/// The value of
/// The value of
- public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory)
+ public DreamMakerController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager) : base(databaseContext, authenticationContextFactory, true)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs
index 414593875e..9ce8efffae 100644
--- a/src/Tgstation.Server.Host/Controllers/HomeController.cs
+++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs
@@ -35,9 +35,9 @@ namespace Tgstation.Server.Host.Controllers
///
readonly IApplication application;
///
- /// The for the
+ /// The for the
///
- readonly IAuthenticationContextFactory authenticationContextFactory;
+ readonly IIdentityCache identityCache;
///
/// Construct a
@@ -48,14 +48,14 @@ namespace Tgstation.Server.Host.Controllers
/// The value of
/// The value of
/// The value of
- public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application) : base(databaseContext, authenticationContextFactory)
+ /// The value of
+ public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite, IApplication application, IIdentityCache identityCache) : base(databaseContext, authenticationContextFactory, false)
{
this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory));
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
this.application = application ?? throw new ArgumentNullException(nameof(application));
- //base checks not null
- this.authenticationContextFactory = authenticationContextFactory;
+ this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache));
}
///
@@ -109,7 +109,7 @@ namespace Tgstation.Server.Host.Controllers
else
try
{
- identity = await systemIdentityFactory.CreateSystemIdentity(user.Name, ApiHeaders.Password, cancellationToken).ConfigureAwait(false);
+ identity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false);
if (identity == null || identity.Uid != user.SystemIdentifier)
return Unauthorized();
}
@@ -123,7 +123,7 @@ namespace Tgstation.Server.Host.Controllers
var token = tokenFactory.CreateToken(user, out var expiry);
if (identity != null)
- authenticationContextFactory.CacheSystemIdentity(user, identity, expiry.AddSeconds(10)); //expire the identity slightly after the auth token in case of lag
+ identityCache.CacheSystemIdentity(user, identity, expiry.AddSeconds(10)); //expire the identity slightly after the auth token in case of lag
return Json(token);
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index e7f38e8a01..aa4a656911 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -42,7 +42,7 @@ namespace Tgstation.Server.Host.Controllers
/// The value of
/// The value of
/// The value of
- public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager) : base(databaseContext, authenticationContextFactory)
+ public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager) : base(databaseContext, authenticationContextFactory, false)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs
index 9ddc1d43cc..ceb71bd530 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceUserController.cs
@@ -31,7 +31,7 @@ namespace Tgstation.Server.Host.Controllers
/// The for the
/// The for the
/// The value of
- public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory)
+ public InstanceUserController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ILogger logger) : base(databaseContext, authenticationContextFactory, false)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
diff --git a/src/Tgstation.Server.Host/Controllers/InteropController.cs b/src/Tgstation.Server.Host/Controllers/InteropController.cs
index 2f1690884d..d145f99632 100644
--- a/src/Tgstation.Server.Host/Controllers/InteropController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InteropController.cs
@@ -24,7 +24,7 @@ namespace Tgstation.Server.Host.Controllers
/// The for the
/// The for the
/// The value of
- public InteropController(IInstanceManager instanceManager, IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory)
+ public InteropController(IInstanceManager instanceManager, IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory, false)
{
this.instanceManager = instanceManager;
}
diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs
index 8219125d29..c7bab7aee3 100644
--- a/src/Tgstation.Server.Host/Controllers/JobController.cs
+++ b/src/Tgstation.Server.Host/Controllers/JobController.cs
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Controllers
/// The for the
/// The for the
/// The value of
- public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory)
+ public JobController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager) : base(databaseContext, authenticationContextFactory, false)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
}
diff --git a/src/Tgstation.Server.Host/Controllers/ModelController.cs b/src/Tgstation.Server.Host/Controllers/ModelController.cs
index 7943e4ab91..69680526fe 100644
--- a/src/Tgstation.Server.Host/Controllers/ModelController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ModelController.cs
@@ -24,7 +24,8 @@ namespace Tgstation.Server.Host.Controllers
///
/// The for the
/// The for the
- public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory) { }
+ /// If the requires an
+ public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, bool requireInstance) : base(databaseContext, authenticationContextFactory, requireInstance) { }
///
/// Attempt to create a
@@ -49,7 +50,7 @@ namespace Tgstation.Server.Host.Controllers
/// The ID of the model to get
/// The for the operation
/// A resulting in the of the operation
- [HttpGet("/{id}")]
+ [HttpGet("{id}")]
public virtual Task GetId(long id, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
///
@@ -75,7 +76,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// The for the operation
/// A resulting in the of the operation
- [HttpGet("/List")]
+ [HttpGet("List")]
public virtual Task List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs
index 376ccd3b26..25bd803e95 100644
--- a/src/Tgstation.Server.Host/Controllers/UserController.cs
+++ b/src/Tgstation.Server.Host/Controllers/UserController.cs
@@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Controllers
/// The value of
/// The value of
/// The value of
- public UserController(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, false)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index c4f285701f..b70dd81236 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -148,9 +148,9 @@ namespace Tgstation.Server.Host.Core
default:
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}!", nameof(DatabaseType)));
}
-
- //very important this remains a singleton
- services.AddSingleton();
+
+ services.AddScoped();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs
index 3bddfce971..819e6471f3 100644
--- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs
+++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs
@@ -10,7 +10,7 @@ using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Security
{
///
- sealed class AuthenticationContextFactory : IAuthenticationContextFactory, IDisposable
+ sealed class AuthenticationContextFactory : IAuthenticationContextFactory
{
///
public IAuthenticationContext CurrentAuthenticationContext { get; private set; }
@@ -21,53 +21,22 @@ namespace Tgstation.Server.Host.Security
readonly ISystemIdentityFactory systemIdentityFactory;
///
- /// The for the
+ /// The for the
///
- readonly IDatabaseContextFactory databaseContextFactory;
+ readonly IDatabaseContext databaseContext;
- ///
- /// Map of s to s for that user
- ///
- readonly Dictionary identityCache;
+ readonly IIdentityCache identityCache;
///
/// Construct an
///
/// The value of
/// The value of
- public AuthenticationContextFactory(ISystemIdentityFactory systemIdentityFactory, IDatabaseContextFactory databaseContextFactory)
+ public AuthenticationContextFactory(ISystemIdentityFactory systemIdentityFactory, IDatabaseContext databaseContext, IIdentityCache identityCache)
{
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
- this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
-
- identityCache = new Dictionary();
- }
-
- ///
- public void Dispose()
- {
- foreach (var I in identityCache)
- I.Value.Dispose();
- }
-
- ///
- public void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry)
- {
- if (user == null)
- throw new ArgumentNullException(nameof(user));
- if (systemIdentity == null)
- throw new ArgumentNullException(nameof(systemIdentity));
- lock (identityCache)
- {
- if (identityCache.TryGetValue(user.Id, out var identCache))
- identCache.Dispose(); //also clears it out
- identCache = new IdentityCache(systemIdentity.Clone(), expiry, () =>
- {
- lock (identityCache)
- identityCache.Remove(user.Id);
- });
- identityCache.Add(user.Id, identCache);
- }
+ this.databaseContext = databaseContext?? throw new ArgumentNullException(nameof(databaseContext));
+ this.identityCache = identityCache ?? throw new ArgumentNullException(nameof(identityCache));
}
///
@@ -76,32 +45,24 @@ namespace Tgstation.Server.Host.Security
if (CurrentAuthenticationContext != null)
throw new InvalidOperationException("Authentication context has already been loaded");
- User user = null;
- await databaseContextFactory.UseContext(async db =>
- {
- var userQuery = db.Users.Where(x => x.Id == userId);
+ var userQuery = databaseContext.Users.Where(x => x.Id == userId);
- if (instanceId.HasValue)
- userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId));
+ if (instanceId.HasValue)
+ userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId));
- user = await userQuery.Include(x => x.InstanceUsers).FirstAsync(cancellationToken).ConfigureAwait(false);
- }).ConfigureAwait(false);
+ var user = await userQuery.Include(x => x.InstanceUsers).FirstAsync(cancellationToken).ConfigureAwait(false);
InstanceUser instanceUser = null;
if (instanceId.HasValue)
instanceUser = user.InstanceUsers.Where(x => x.InstanceId == instanceId).FirstOrDefault();
- if (instanceUser == default)
- return;
-
ISystemIdentity systemIdentity;
if (user.SystemIdentifier != null)
- lock (identityCache)
- {
- if (!identityCache.TryGetValue(userId, out var identCache))
- throw new InvalidOperationException("Cached system identity has expired!");
- systemIdentity = identCache.SystemIdentity.Clone();
- }
+ {
+ systemIdentity = identityCache.LoadCachedIdentity(user);
+ if (systemIdentity == null)
+ throw new InvalidOperationException("Cached system identity has expired!");
+ }
else
systemIdentity = null;
diff --git a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs
index 2e8552c037..121a9cc46b 100644
--- a/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs
+++ b/src/Tgstation.Server.Host/Security/IAuthenticationContextFactory.cs
@@ -15,14 +15,6 @@ namespace Tgstation.Server.Host.Security
///
IAuthenticationContext CurrentAuthenticationContext { get; }
- ///
- /// Keep a 's alive until an time
- ///
- /// The the belongs to
- /// The to cache
- /// When the should expire
- void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry);
-
///
/// Create an to populate
///
diff --git a/src/Tgstation.Server.Host/Security/IIdentityCache.cs b/src/Tgstation.Server.Host/Security/IIdentityCache.cs
new file mode 100644
index 0000000000..902424f6d3
--- /dev/null
+++ b/src/Tgstation.Server.Host/Security/IIdentityCache.cs
@@ -0,0 +1,23 @@
+using System;
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Security
+{
+ public interface IIdentityCache
+ {
+ ///
+ /// Keep a 's alive until an time
+ ///
+ /// The the belongs to
+ /// The to cache
+ /// When the should expire
+ void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry);
+
+ ///
+ /// Attempt to load a cached
+ ///
+ /// The the belongs to
+ /// The cached or if it doesn't exist or expired
+ ISystemIdentity LoadCachedIdentity(User user);
+ }
+}
diff --git a/src/Tgstation.Server.Host/Security/IdentityCache.cs b/src/Tgstation.Server.Host/Security/IdentityCache.cs
index 931f8f171c..81850ab4f2 100644
--- a/src/Tgstation.Server.Host/Security/IdentityCache.cs
+++ b/src/Tgstation.Server.Host/Security/IdentityCache.cs
@@ -1,63 +1,57 @@
using System;
-using System.Threading;
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Security
{
- ///
- /// For keeping a specific alive for a period of time
- ///
- sealed class IdentityCache : IDisposable
+ ///
+ sealed class IdentityCache : IIdentityCache, IDisposable
{
- ///
- /// The the manages
- ///
- public ISystemIdentity SystemIdentity { get; }
+ readonly Dictionary cachedIdentities;
- ///
- /// The for the
- ///
- readonly CancellationTokenSource cancellationTokenSource;
-
- ///
- /// The to clean up
- ///
- readonly Task task;
-
- ///
- /// Construct an
- ///
- /// The value of
- /// The
- /// An optional to take on expiry
- public IdentityCache(ISystemIdentity systemIdentity, DateTimeOffset expiry, Action onExpiry)
+ public IdentityCache()
{
- SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity));
-
- cancellationTokenSource = new CancellationTokenSource();
-
- async Task DisposeOnExipiry(CancellationToken cancellationToken)
- {
- using (SystemIdentity)
- try
- {
- await Task.Delay(expiry - DateTimeOffset.Now, cancellationToken).ConfigureAwait(false);
- }
- finally
- {
- onExpiry?.Invoke();
- }
- }
-
- task = DisposeOnExipiry(cancellationTokenSource.Token);
+ cachedIdentities = new Dictionary();
}
///
public void Dispose()
{
- cancellationTokenSource.Cancel();
- task.Wait();
- cancellationTokenSource.Dispose();
+ foreach (var I in cachedIdentities)
+ I.Value.Dispose();
+ }
+
+ ///
+ public void CacheSystemIdentity(User user, ISystemIdentity systemIdentity, DateTimeOffset expiry)
+ {
+ if (user == null)
+ throw new ArgumentNullException(nameof(user));
+ if (systemIdentity == null)
+ throw new ArgumentNullException(nameof(systemIdentity));
+ lock (cachedIdentities)
+ {
+ if (cachedIdentities.TryGetValue(user.Id, out var identCache))
+ identCache.Dispose(); //also clears it out
+ identCache = new IdentityCacheObject(systemIdentity.Clone(), () =>
+ {
+ lock (cachedIdentities)
+ cachedIdentities.Remove(user.Id);
+ }, expiry);
+ cachedIdentities.Add(user.Id, identCache);
+ }
+ }
+
+ ///
+ public ISystemIdentity LoadCachedIdentity(User user)
+ {
+ if (user == null)
+ throw new ArgumentNullException(nameof(user));
+ lock (cachedIdentities)
+ {
+ if (cachedIdentities.TryGetValue(user.Id, out var identity))
+ return identity.SystemIdentity;
+ return null;
+ }
}
}
}
diff --git a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs
new file mode 100644
index 0000000000..310c68b34c
--- /dev/null
+++ b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Security
+{
+ ///
+ /// For keeping a specific alive for a period of time
+ ///
+ sealed class IdentityCacheObject : IDisposable
+ {
+ ///
+ /// The the manages
+ ///
+ public ISystemIdentity SystemIdentity { get; }
+
+ ///
+ /// The for the
+ ///
+ readonly CancellationTokenSource cancellationTokenSource;
+
+ ///
+ /// The to clean up
+ ///
+ readonly Task task;
+
+ ///
+ /// Construct an
+ ///
+ /// The value of
+ /// The to take on expiry
+ /// The
+ public IdentityCacheObject(ISystemIdentity systemIdentity, Action onExpiry, DateTimeOffset expiry)
+ {
+ SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity));
+ if (onExpiry == null)
+ throw new ArgumentNullException(nameof(onExpiry));
+ var now = DateTimeOffset.Now;
+ if (expiry < now)
+ throw new ArgumentOutOfRangeException(nameof(expiry), expiry, "expiry must be greater than DateTimeOffset.Now!");
+
+ cancellationTokenSource = new CancellationTokenSource();
+
+ async Task DisposeOnExipiry(CancellationToken cancellationToken)
+ {
+ using (SystemIdentity)
+ try
+ {
+ await Task.Delay(expiry - now, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ onExpiry();
+ }
+ }
+
+ task = DisposeOnExipiry(cancellationTokenSource.Token);
+ }
+
+ ///
+ public void Dispose()
+ {
+ cancellationTokenSource.Cancel();
+ task.Wait();
+ cancellationTokenSource.Dispose();
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs
index 1e0dc1afcf..6f1ab56cba 100644
--- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs
+++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs
@@ -40,7 +40,13 @@ namespace Tgstation.Server.Host.Security
}
///
- public void Dispose() => identity.Dispose();
+ public void Dispose()
+ {
+ if (identity != null)
+ identity.Dispose();
+ else
+ userPrincipal.Dispose();
+ }
///
public string Uid => (userPrincipal?.Sid ?? identity.User).ToString();
diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs
index 781f0b381e..871269f3ae 100644
--- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs
+++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs
@@ -24,14 +24,28 @@ namespace Tgstation.Server.Host.Security
if (user.SystemIdentifier == null)
throw new InvalidOperationException("User's SystemIdentifier must not be null!");
+ PrincipalContext pc = null;
UserPrincipal principal = null;
//machine logon first cause it's faster
- using (var pc = new PrincipalContext(ContextType.Machine, Environment.UserDomainName))
+ try
+ {
+ pc = new PrincipalContext(ContextType.Machine, Environment.UserDomainName);
principal = UserPrincipal.FindByIdentity(pc, user.SystemIdentifier);
- if(principal == null)
- using (var pc = new PrincipalContext(ContextType.Domain, Environment.UserDomainName))
+ if (principal == null)
+ {
+ pc.Dispose();
+ pc = new PrincipalContext(ContextType.Domain, Environment.UserDomainName);
principal = UserPrincipal.FindByIdentity(pc, user.SystemIdentifier);
+ if (principal == null)
+ pc.Dispose();
+ }
+ }
+ catch
+ {
+ pc?.Dispose();
+ throw;
+ }
if (principal == null)
return null;
@@ -42,6 +56,10 @@ namespace Tgstation.Server.Host.Security
///
public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
{
+ if (username == null)
+ throw new ArgumentNullException(nameof(username));
+ if (password == null)
+ throw new ArgumentNullException(nameof(password));
var splits = username.Split('\\');
var res = NativeMethods.LogonUser(splits.Length > 1 ? splits[1] : splits[0], splits.Length > 1 ? splits[0] : null, password, 3 /*LOGON32_LOGON_NETWORK*/, 0 /*LOGON32_PROVIDER_DEFAULT*/, out var token);
diff --git a/tools/TGS.postman_collection.json b/tools/TGS.postman_collection.json
index 59bebb834f..b48b57f3d2 100644
--- a/tools/TGS.postman_collection.json
+++ b/tools/TGS.postman_collection.json
@@ -1,13 +1,13 @@
{
"info": {
- "_postman_id": "f0940866-29fd-4ea3-8682-7e30df5b08c9",
+ "_postman_id": "e62d1230-5b76-45e2-9fb6-002e260daa2a",
"name": "TGS",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Administration",
- "description": "",
+ "description": null,
"item": [
{
"name": "Get Update Versions",
@@ -27,7 +27,10 @@
"value": "Tgstation.Server.Api/4.0.0.0"
}
],
- "body": {},
+ "body": {
+ "mode": "raw",
+ "raw": ""
+ },
"url": {
"raw": "localhost:5000/Administration",
"host": [
@@ -98,7 +101,10 @@
"value": "Tgstation.Server.Api/4.0.0.0"
}
],
- "body": {},
+ "body": {
+ "mode": "raw",
+ "raw": ""
+ },
"url": {
"raw": "localhost:5000/Administration",
"host": [
@@ -136,6 +142,475 @@
}
]
},
+ {
+ "name": "Users",
+ "description": "",
+ "item": [
+ {
+ "name": "Read Current User",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ }
+ ],
+ "body": {},
+ "url": {
+ "raw": "localhost:5000/User",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Read All Users",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ }
+ ],
+ "body": {},
+ "url": {
+ "raw": "localhost:5000/User/List",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User",
+ "List"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create User Bob Pass (asdf)",
+ "request": {
+ "method": "PUT",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"id\": 1,\n \"enabled\": true,\n \"password\": \"asdf\",\n \"name\": \"Bob\"\n}"
+ },
+ "url": {
+ "raw": "localhost:5000/User",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Try To Add A SysId To User ID 2",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"id\": 2,\n \"systemIdentifier\": \"fake\"\n}"
+ },
+ "url": {
+ "raw": "localhost:5000/User",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Enable User ID 3",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"id\": 3,\n \"enabled\": true\n}"
+ },
+ "url": {
+ "raw": "localhost:5000/User",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Try To Add A Password To User ID",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"id\": 3,\n \"password\": \"fake\"\n}"
+ },
+ "url": {
+ "raw": "localhost:5000/User",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Change User ID 2's password and name to bOb and fdsa",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"id\": 2,\n \"password\": \"fdsa\",\n \"name\": \"bOb\"\n}"
+ },
+ "url": {
+ "raw": "localhost:5000/User",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Change User ID 2's name to Bob2",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"id\": 2,\n \"name\": \"Bob2\"\n}"
+ },
+ "url": {
+ "raw": "localhost:5000/User",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Give User ID 3 rights",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"id\": 3,\n \"administrationRights\": -1,\n\t\"instanceManagerRights\": -1,\n}"
+ },
+ "url": {
+ "raw": "localhost:5000/User",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create system user Cyberboss",
+ "request": {
+ "method": "PUT",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"systemIdentifier\": \"Cyberboss\"\n}"
+ },
+ "url": {
+ "raw": "localhost:5000/User",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000",
+ "path": [
+ "User"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Login bob (asdf)",
+ "request": {
+ "auth": {
+ "type": "noauth"
+ },
+ "method": "POST",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Authorization",
+ "value": "Password asdf"
+ },
+ {
+ "key": "Username",
+ "value": "bob"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": ""
+ },
+ "url": {
+ "raw": "localhost:5000",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000"
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Login cyberboss",
+ "request": {
+ "auth": {
+ "type": "noauth"
+ },
+ "method": "POST",
+ "header": [
+ {
+ "key": "Accept",
+ "value": "application/json"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Postman/1.0"
+ },
+ {
+ "key": "User-Agent",
+ "value": "Tgstation.Server.Api/4.0.0.0"
+ },
+ {
+ "key": "Authorization",
+ "value": "Password no"
+ },
+ {
+ "key": "Username",
+ "value": "cyberboss"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": ""
+ },
+ "url": {
+ "raw": "localhost:5000",
+ "host": [
+ "localhost"
+ ],
+ "port": "5000"
+ }
+ },
+ "response": []
+ }
+ ]
+ },
{
"name": "Login Admin Default",
"request": {
@@ -165,7 +640,10 @@
"value": "admin"
}
],
- "body": {},
+ "body": {
+ "mode": "raw",
+ "raw": ""
+ },
"url": {
"raw": "localhost:5000",
"host": [
@@ -200,7 +678,6 @@
{
"key": "name",
"value": "asdfasdf",
- "description": "",
"type": "text"
}
]
@@ -221,7 +698,7 @@
"bearer": [
{
"key": "token",
- "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjU0MjgwMyIsIm5iZiI6IjE1MzI1MzkyMDMiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.f-y5xQ-JbZklD7iMeztZhLGfXBWnSxxlYtzl3TXDt5g",
+ "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoiMTUzMjY1ODI0OSIsIm5iZiI6IjE1MzI2NTQ2NDkiLCJpc3MiOiJUZ3N0YXRpb24uU2VydmVyLkhvc3QiLCJhdWQiOiJUZ3N0YXRpb24uU2VydmVyLkFwaSJ9.nfVqGC501-l1hkXCn6NsLj-rLFBVU65feHKLxXV3X8c",
"type": "string"
}
]