diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs
index 41a91b5a25..c8774659b6 100644
--- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs
@@ -91,11 +91,6 @@ namespace Tgstation.Server.Host.Components
///
readonly IJobManager jobManager;
- ///
- /// The for the
- ///
- readonly ICredentialsProvider credentialsProvider;
-
///
/// The for the
///
@@ -111,6 +106,11 @@ namespace Tgstation.Server.Host.Components
///
readonly IPlatformIdentifier platformIdentifier;
+ ///
+ /// The for the .
+ ///
+ readonly IRepositoryFactory repositoryFactory;
+
///
/// Construct an
///
@@ -128,10 +128,10 @@ namespace Tgstation.Server.Host.Components
/// The value of
/// The value of
/// The value of
- /// The value of
/// The value of
/// The value of
/// The value of
+ /// The value of .
public InstanceFactory(
IIOManager ioManager,
IDatabaseContextFactory databaseContextFactory,
@@ -147,10 +147,10 @@ namespace Tgstation.Server.Host.Components
IPostWriteHandler postWriteHandler,
IWatchdogFactory watchdogFactory,
IJobManager jobManager,
- ICredentialsProvider credentialsProvider,
INetworkPromptReaper networkPromptReaper,
IGitHubClientFactory gitHubClientFactory,
- IPlatformIdentifier platformIdentifier)
+ IPlatformIdentifier platformIdentifier,
+ IRepositoryFactory repositoryFactory)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
@@ -166,10 +166,10 @@ namespace Tgstation.Server.Host.Components
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
- this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
this.networkPromptReaper = networkPromptReaper ?? throw new ArgumentNullException(nameof(networkPromptReaper));
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
+ this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
}
///
@@ -187,7 +187,13 @@ namespace Tgstation.Server.Host.Components
var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, platformIdentifier, loggerFactory.CreateLogger());
var eventConsumer = new EventConsumer(configuration);
- var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, credentialsProvider, loggerFactory.CreateLogger(), loggerFactory.CreateLogger());
+ var repoManager = new RepositoryManager(
+ repositoryFactory,
+ repoIoManager,
+ eventConsumer,
+ loggerFactory.CreateLogger(),
+ loggerFactory.CreateLogger(),
+ metadata.RepositorySettings);
try
{
var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger());
@@ -244,12 +250,21 @@ namespace Tgstation.Server.Host.Components
throw;
}
}
- #pragma warning restore CA1506
+#pragma warning restore CA1506
///
- public Task StartAsync(CancellationToken cancellationToken) => byondInstaller.CleanCache(cancellationToken);
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ CheckSystemCompatibility();
+ return byondInstaller.CleanCache(cancellationToken);
+ }
///
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
+ ///
+ /// Test that the is functional.
+ ///
+ private void CheckSystemCompatibility() => repositoryFactory.CreateInMemory().Dispose();
}
}
diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs
index 28fbc31558..07bd7aa148 100644
--- a/src/Tgstation.Server.Host/Components/InstanceManager.cs
+++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs
@@ -10,6 +10,8 @@ using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
+using Tgstation.Server.Host.Security;
+using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Components
{
@@ -46,6 +48,16 @@ namespace Tgstation.Server.Host.Components
///
readonly IServerControl serverControl;
+ ///
+ /// The for the
+ ///
+ readonly IPlatformIdentifier platformIdentifier;
+
+ ///
+ /// The for the
+ ///
+ readonly ISystemIdentityFactory systemIdentityFactory;
+
///
/// The for the
///
@@ -75,8 +87,19 @@ namespace Tgstation.Server.Host.Components
/// The value of
/// The value of
/// The value of
+ /// The value of .
+ /// The value of .
/// The value of
- public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger logger)
+ public InstanceManager(
+ IInstanceFactory instanceFactory,
+ IIOManager ioManager,
+ IDatabaseContextFactory databaseContextFactory,
+ IApplication application,
+ IJobManager jobManager,
+ IServerControl serverControl,
+ IPlatformIdentifier platformIdentifier,
+ ISystemIdentityFactory systemIdentityFactory,
+ ILogger logger)
{
this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
@@ -84,6 +107,8 @@ namespace Tgstation.Server.Host.Components
this.application = application ?? throw new ArgumentNullException(nameof(application));
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
+ this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
+ this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
serverControl.RegisterForRestart(this);
@@ -212,6 +237,7 @@ namespace Tgstation.Server.Host.Components
{
try
{
+ CheckSystemCompatibility();
var factoryStartup = instanceFactory.StartAsync(cancellationToken);
await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
await jobManager.StartAsync(cancellationToken).ConfigureAwait(false);
@@ -266,5 +292,15 @@ namespace Tgstation.Server.Host.Components
downgradeVersion = updateVersion != null && updateVersion < application.Version ? updateVersion : null;
return Task.CompletedTask;
}
+
+ ///
+ /// Check we have a valid system identity.
+ ///
+ private void CheckSystemCompatibility()
+ {
+ using (var systemIdentity = systemIdentityFactory.GetCurrent())
+ if (!systemIdentity.CanCreateSymlinks)
+ throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
+ }
}
}
diff --git a/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs b/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs
deleted file mode 100644
index 80112f3e47..0000000000
--- a/src/Tgstation.Server.Host/Components/Repository/CredentialsProvider.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using LibGit2Sharp;
-using LibGit2Sharp.Handlers;
-using Microsoft.Extensions.Logging;
-using System;
-using Tgstation.Server.Host.Jobs;
-
-namespace Tgstation.Server.Host.Components.Repository
-{
- ///
- sealed class CredentialsProvider : ICredentialsProvider
- {
- ///
- /// The for the
- ///
- readonly ILogger logger;
-
- ///
- /// Construct a
- ///
- /// The value of
- public CredentialsProvider(ILogger logger)
- {
- this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
- }
-
- ///
- public CredentialsHandler GenerateHandler(string username, string password) => (a, b, supportedCredentialTypes) =>
- {
- var hasCreds = username != null;
- var supportsUserPass = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.UsernamePassword);
- var supportsAnonymous = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.Default);
-
- logger.LogTrace("Credentials requested. Present: {0}. Supports anonymous: {1}. Supports user/pass: {2}", hasCreds, supportsAnonymous, supportsUserPass);
- if (supportsUserPass)
- {
- if (hasCreds)
- return new UsernamePasswordCredentials
- {
- Username = username,
- Password = password
- };
- }
-
- if (supportsAnonymous)
- return new DefaultCredentials();
-
- if (hasCreds)
- throw new JobException("Remote does not support anonymous authentication!");
-
- throw new JobException("Server does not support anonymous or username/password authentication!");
- };
- }
-}
diff --git a/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs b/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs
index c130b02a78..496a015187 100644
--- a/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/ICredentialsProvider.cs
@@ -13,6 +13,6 @@ namespace Tgstation.Server.Host.Components.Repository
/// The optional username to use in the
/// The optional password to use in the
/// A new
- CredentialsHandler GenerateHandler(string username, string password);
+ CredentialsHandler GenerateCredentialsHandler(string username, string password);
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepositoryFactory.cs b/src/Tgstation.Server.Host/Components/Repository/IRepositoryFactory.cs
new file mode 100644
index 0000000000..1b9d7b8c41
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/Repository/IRepositoryFactory.cs
@@ -0,0 +1,37 @@
+using LibGit2Sharp;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Components.Repository
+{
+ ///
+ /// Factory for creating s.
+ ///
+ interface IRepositoryFactory : ICredentialsProvider
+ {
+ ///
+ /// Create an in-memeory .
+ ///
+ /// A new in-memory .
+ LibGit2Sharp.IRepository CreateInMemory();
+
+ ///
+ /// Load a from a given .
+ ///
+ /// The full path to the .
+ /// The for the operation.
+ /// A resulting in, the loaded .
+ Task CreateFromPath(string path, CancellationToken cancellationToken);
+
+ ///
+ /// Clone a remote .
+ ///
+ /// The of the remote.
+ /// The .
+ /// The full path to the cloned .
+ /// The for the operation.
+ /// A representing the running operation.
+ Task Clone(Uri url, CloneOptions cloneOptions, string path, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs
index dd64bcdb19..d180d541d7 100644
--- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs
@@ -175,7 +175,7 @@ namespace Tgstation.Server.Host.Components.Repository
progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
return !cancellationToken.IsCancellationRequested;
},
- CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
+ CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
};
///
@@ -262,7 +262,7 @@ namespace Tgstation.Server.Host.Components.Repository
return !cancellationToken.IsCancellationRequested;
},
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
- CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
+ CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
}, logMessage);
}
catch (UserCancelledException) { }
@@ -375,7 +375,7 @@ namespace Tgstation.Server.Host.Components.Repository
return !cancellationToken.IsCancellationRequested;
},
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
- CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
+ CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
}, "Fetch origin commits");
}
catch (UserCancelledException)
diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryFactory.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryFactory.cs
new file mode 100644
index 0000000000..69865ce950
--- /dev/null
+++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryFactory.cs
@@ -0,0 +1,87 @@
+using LibGit2Sharp;
+using LibGit2Sharp.Handlers;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Tgstation.Server.Host.Jobs;
+
+namespace Tgstation.Server.Host.Components.Repository
+{
+ ///
+ sealed class RepositoryFactory : IRepositoryFactory
+ {
+ ///
+ /// The for the .
+ ///
+ readonly ILogger logger;
+
+ ///
+ /// Initializes a new instance of the .
+ ///
+ /// The value of .
+ public RepositoryFactory(ILogger logger)
+ {
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ ///
+ public LibGit2Sharp.IRepository CreateInMemory()
+ {
+ logger.LogTrace("Creating in-memory repository...");
+ return new LibGit2Sharp.Repository();
+ }
+
+ ///
+ public Task CreateFromPath(string path, CancellationToken cancellationToken)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+ logger.LogTrace("Creating repostory at {0}...", path);
+ return Task.Factory.StartNew(
+ () => (LibGit2Sharp.IRepository)new LibGit2Sharp.Repository(path),
+ cancellationToken,
+ TaskCreationOptions.LongRunning,
+ TaskScheduler.Current);
+ }
+
+ ///
+ public Task Clone(Uri url, CloneOptions cloneOptions, string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
+ {
+ try
+ {
+ logger.LogTrace("Cloning {0} into {1}...", url, path);
+ LibGit2Sharp.Repository.Clone(url.ToString(), path, cloneOptions);
+ }
+ catch (UserCancelledException ex)
+ {
+ logger.LogTrace("Suppressing clone cancellation exception: {0}", ex);
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+ }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
+
+ ///
+ public CredentialsHandler GenerateCredentialsHandler(string username, string password) => (a, b, supportedCredentialTypes) =>
+ {
+ var hasCreds = username != null;
+ var supportsUserPass = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.UsernamePassword);
+ var supportsAnonymous = supportedCredentialTypes.HasFlag(SupportedCredentialTypes.Default);
+
+ logger.LogTrace("Credentials requested. Present: {0}. Supports anonymous: {1}. Supports user/pass: {2}", hasCreds, supportsAnonymous, supportsUserPass);
+ if (supportsUserPass && hasCreds)
+ return new UsernamePasswordCredentials
+ {
+ Username = username,
+ Password = password
+ };
+
+ if (supportsAnonymous)
+ return new DefaultCredentials();
+
+ if (!hasCreds)
+ throw new JobException("Remote does not support anonymous authentication!");
+
+ throw new JobException("Server does not support anonymous or username/password authentication!");
+ };
+ }
+}
diff --git a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
index 4feeb46897..6bcdaf40bc 100644
--- a/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
+++ b/src/Tgstation.Server.Host/Components/Repository/RepositoryManager.cs
@@ -18,6 +18,11 @@ namespace Tgstation.Server.Host.Components.Repository
///
public bool CloneInProgress { get; private set; }
+ ///
+ /// The for the
+ ///
+ readonly IRepositoryFactory repositoryFactory;
+
///
/// The for the
///
@@ -28,11 +33,6 @@ namespace Tgstation.Server.Host.Components.Repository
///
readonly IEventConsumer eventConsumer;
- ///
- /// The for the
- ///
- readonly ICredentialsProvider credentialsProvider;
-
///
/// The created s
///
@@ -56,20 +56,26 @@ namespace Tgstation.Server.Host.Components.Repository
///
/// Construct a
///
- /// The value of
+ /// The value of .
/// The value of
/// The value of
- /// The value of
/// The value of
/// The value of
- public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, ILogger repositoryLogger, ILogger logger)
+ /// The value of
+ public RepositoryManager(
+ IRepositoryFactory repositoryFactory,
+ IIOManager ioManager,
+ IEventConsumer eventConsumer,
+ ILogger repositoryLogger,
+ ILogger logger,
+ RepositorySettings repositorySettings)
{
- this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings));
+ this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
- this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings));
semaphore = new SemaphoreSlim(1);
}
@@ -89,7 +95,7 @@ namespace Tgstation.Server.Host.Components.Repository
throw new ArgumentNullException(nameof(progressReporter));
logger.LogInformation("Begin clone {0} (Branch: {1})", url, initialBranch);
- lock (this)
+ lock (semaphore)
{
if (CloneInProgress)
throw new InvalidOperationException("The repository is already being cloned!");
@@ -104,30 +110,28 @@ namespace Tgstation.Server.Host.Components.Repository
if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false))
try
{
- await Task.Factory.StartNew(() =>
+ var cloneOptions = new CloneOptions
{
- string path = null;
- try
+ OnProgress = (a) => !cancellationToken.IsCancellationRequested,
+ OnTransferProgress = (a) =>
{
- path = LibGit2Sharp.Repository.Clone(url.ToString(), ioManager.ResolvePath("."), new CloneOptions
- {
- OnProgress = (a) => !cancellationToken.IsCancellationRequested,
- OnTransferProgress = (a) =>
- {
- var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
- progressReporter((int)percentage);
- return !cancellationToken.IsCancellationRequested;
- },
- RecurseSubmodules = true,
- OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
- RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
- BranchName = initialBranch,
- CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
- });
- }
- catch (UserCancelledException) { }
- cancellationToken.ThrowIfCancellationRequested();
- }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
+ var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
+ progressReporter((int)percentage);
+ return !cancellationToken.IsCancellationRequested;
+ },
+ RecurseSubmodules = true,
+ OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
+ RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
+ BranchName = initialBranch,
+ CredentialsProvider = repositoryFactory.GenerateCredentialsHandler(username, password)
+ };
+
+ await repositoryFactory.Clone(
+ url,
+ cloneOptions,
+ ioManager.ResolvePath("."),
+ cancellationToken)
+ .ConfigureAwait(false);
}
catch
{
@@ -164,40 +168,29 @@ namespace Tgstation.Server.Host.Components.Repository
public async Task LoadRepository(CancellationToken cancellationToken)
{
logger.LogTrace("Begin LoadRepository...");
- lock (this)
+ lock (semaphore)
if (CloneInProgress)
throw new InvalidOperationException("The repository is being cloned!");
- await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
- LibGit2Sharp.Repository repo = null;
- await Task.Factory.StartNew(() =>
- {
+ using (var context = await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
try
{
- logger.LogTrace("Creating LibGit2Sharp.Repository...");
- repo = new LibGit2Sharp.Repository(ioManager.ResolvePath("."));
+ var repo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath("."), cancellationToken).ConfigureAwait(false);
+
+ if (repo == null)
+ return null;
+
+ return new Repository(repo, ioManager, eventConsumer, repositoryFactory, repositoryLogger, () =>
+ {
+ logger.LogTrace("Releasing semaphore due to Repository disposal...");
+ semaphore.Release();
+ });
}
catch (RepositoryNotFoundException e)
{
logger.LogDebug("Repository not found!");
logger.LogTrace("Exception: {0}", e);
+ return null;
}
- catch
- {
- semaphore.Release();
- throw;
- }
- }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
- if (repo == null)
- {
- semaphore.Release();
- return null;
- }
-
- return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () =>
- {
- logger.LogTrace("Releasing semaphore due to Repository disposal...");
- semaphore.Release();
- });
}
///
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index bb372eae36..55021ccf9f 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -347,7 +347,7 @@ namespace Tgstation.Server.Host.Core
});
// configure component services
- services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs
index 9dc72788c8..bd880617bc 100644
--- a/src/Tgstation.Server.Host/Security/ISystemIdentity.cs
+++ b/src/Tgstation.Server.Host/Security/ISystemIdentity.cs
@@ -19,6 +19,11 @@ namespace Tgstation.Server.Host.Security
///
string Username { get; }
+ ///
+ /// If this system identity has permissions to create symlinks.
+ ///
+ bool CanCreateSymlinks { get; }
+
///
/// Clone the creating another copy that must have called on it
///
diff --git a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs
index 11ba06ebe7..06371c4fe6 100644
--- a/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs
+++ b/src/Tgstation.Server.Host/Security/ISystemIdentityFactory.cs
@@ -9,6 +9,12 @@ namespace Tgstation.Server.Host.Security
///
public interface ISystemIdentityFactory
{
+ ///
+ /// Retrieves a representing the user executing tgstation-server.
+ ///
+ /// A representing the user executing tgstation-server.
+ ISystemIdentity GetCurrent();
+
///
/// Create a for a given
///
diff --git a/src/Tgstation.Server.Host/Security/PosixSystemIdentity.cs b/src/Tgstation.Server.Host/Security/PosixSystemIdentity.cs
new file mode 100644
index 0000000000..67f0e3ac83
--- /dev/null
+++ b/src/Tgstation.Server.Host/Security/PosixSystemIdentity.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Security
+{
+ ///
+ /// for POSIX systems.
+ ///
+ sealed class PosixSystemIdentity : ISystemIdentity
+ {
+ ///
+ public string Uid => throw new NotImplementedException();
+
+ ///
+ public string Username => throw new NotImplementedException();
+
+ ///
+ public bool CanCreateSymlinks => true;
+
+ ///
+ public ISystemIdentity Clone() => throw new NotImplementedException();
+
+ ///
+ public void Dispose()
+ {
+ }
+
+ ///
+ public Task RunImpersonated(Action action, CancellationToken cancellationToken) => throw new NotSupportedException();
+ }
+}
diff --git a/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs
index 3857b42467..c5a19348a5 100644
--- a/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs
+++ b/src/Tgstation.Server.Host/Security/PosixSystemIdentityFactory.cs
@@ -11,6 +11,9 @@ namespace Tgstation.Server.Host.Security
/// TODO: Blocked by https://github.com/dotnet/corefx/issues/3187
sealed class PosixSystemIdentityFactory : ISystemIdentityFactory
{
+ ///
+ public ISystemIdentity GetCurrent() => new PosixSystemIdentity();
+
///
public Task CreateSystemIdentity(User user, CancellationToken cancellationToken) => throw new NotImplementedException();
diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs
index 5125613971..aad76f2360 100644
--- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs
+++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs
@@ -17,6 +17,9 @@ namespace Tgstation.Server.Host.Security
///
public string Username => userPrincipal?.Name ?? identity.Name;
+ ///
+ public bool CanCreateSymlinks => canCreateSymlinks ?? throw new NotSupportedException();
+
///
/// The for the
///
@@ -27,6 +30,11 @@ namespace Tgstation.Server.Host.Security
///
readonly UserPrincipal userPrincipal;
+ ///
+ /// Backing field for .
+ ///
+ readonly bool? canCreateSymlinks;
+
///
/// Construct a using a
///
@@ -34,6 +42,7 @@ namespace Tgstation.Server.Host.Security
public WindowsSystemIdentity(WindowsIdentity identity)
{
this.identity = identity ?? throw new ArgumentNullException(nameof(identity));
+ canCreateSymlinks = new WindowsPrincipal(identity).IsInRole(WindowsBuiltInRole.Administrator);
}
///
diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs
index 3ce60b3bbd..a3480ddda6 100644
--- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs
+++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs
@@ -41,6 +41,9 @@ namespace Tgstation.Server.Host.Security
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
+ ///
+ public ISystemIdentity GetCurrent() => new WindowsSystemIdentity(WindowsIdentity.GetCurrent());
+
///
public Task CreateSystemIdentity(User user, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
{
diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs
index a2fafe355d..a224e71881 100644
--- a/src/Tgstation.Server.Host/Server.cs
+++ b/src/Tgstation.Server.Host/Server.cs
@@ -15,9 +15,7 @@ using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host
{
///
-#pragma warning disable CA1001 // Types that own disposable fields should be disposable
sealed class Server : IServer, IServerControl
-#pragma warning restore CA1001 // Types that own disposable fields should be disposable
{
///
public bool RestartRequested { get; private set; }
diff --git a/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs b/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs
new file mode 100644
index 0000000000..8d34683556
--- /dev/null
+++ b/tests/Tgstation.Server.Host.Tests/Components/Repository/TestRepositoryFactory.cs
@@ -0,0 +1,78 @@
+using LibGit2Sharp;
+using Microsoft.Extensions.Logging;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Components.Repository.Tests
+{
+ ///
+ /// Tests for .
+ ///
+ [TestClass]
+ public sealed class TestRepositoryFactory
+ {
+ static IRepositoryFactory CreateFactory() => new RepositoryFactory(Mock.Of>());
+
+ static Task TestRepoLoading(
+ string path,
+ IRepositoryFactory repositoryFactory = null) =>
+ (repositoryFactory ?? CreateFactory())
+ .CreateFromPath(path, default);
+
+ [TestMethod]
+ public void TestConstructionThrows() => Assert.ThrowsException(() => new RepositoryFactory(null));
+
+ [TestMethod]
+ public void TestInMemoryRepoCreation()
+ {
+ new RepositoryFactory(Mock.Of>()).CreateInMemory().Dispose();
+ }
+
+ [TestMethod]
+ public async Task TestCloning()
+ {
+ var tempDir = Path.GetTempFileName();
+ File.Delete(tempDir);
+ try
+ {
+ var factory = CreateFactory();
+ await factory.Clone(
+ new Uri("https://github.com/Cyberboss/Test"),
+ new CloneOptions
+ {
+ CredentialsProvider = factory.GenerateCredentialsHandler(null, null)
+ },
+ tempDir,
+ default);
+
+ using (var repo = await TestRepoLoading(tempDir))
+ {
+ var gitObject = repo.Lookup("f636418bf47d238d33b0e4a34f0072b23a8aad0e");
+ Assert.IsNotNull(gitObject);
+ var commit = gitObject.Peel();
+
+ Assert.AreEqual("Update Test.md", commit.Message);
+ }
+ }
+ finally
+ {
+ // Takes a while to release the repo handle sometimes...
+ for (var i = 0; i < 5; ++i)
+ {
+ try
+ {
+ Directory.Delete(tempDir, true);
+ break;
+ }
+ catch (UnauthorizedAccessException)
+ {
+ await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+ }
+}