diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs index b6f3bd1e5b..ef40d7f2da 100644 --- a/src/Tgstation.Server.Api/Models/ErrorCode.cs +++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs @@ -531,5 +531,11 @@ namespace Tgstation.Server.Api.Models /// [Description("Could not create dump as gcore exited with a non-zero exit code!")] GCoreFailure, + + /// + /// Attempted to test merge with an invalid remote repository. + /// + [Description("Test merging cannot be performed with this remote!")] + RepoTestMergeInvalidRemote, } } \ No newline at end of file diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index 7356ffc48c..21cc42af56 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -61,6 +61,11 @@ namespace Tgstation.Server.Api.Rights /// /// User can change . /// - SetChatBotLimit = 512 + SetChatBotLimit = 512, + + /// + /// User can give themselves full rights on instances. + /// + GrantPermissions = 1024, } } diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index 69d7d0f79a..cf1041fc86 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -235,6 +235,9 @@ namespace Tgstation.Server.Client /// public Task Update(string route, TBody body, CancellationToken cancellationToken) => RunRequest(route, body, HttpMethod.Post, null, false, cancellationToken); + /// + public Task Patch(string route, CancellationToken cancellationToken) => RunRequest(route, null, HttpMethod.Patch, null, false, cancellationToken); + /// public Task Update(string route, TBody body, CancellationToken cancellationToken) => RunRequest(route, body, HttpMethod.Post, null, false, cancellationToken); diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs index b1c8519892..bee5ea5328 100644 --- a/src/Tgstation.Server.Client/IApiClient.cs +++ b/src/Tgstation.Server.Client/IApiClient.cs @@ -90,6 +90,14 @@ namespace Tgstation.Server.Client /// A representing the running operation Task Update(string route, TBody body, CancellationToken cancellationToken); + /// + /// Run an HTTP PATCH request. + /// + /// The server route to make the request to. + /// The for the operation. + /// A representing the running operation. + Task Patch(string route, CancellationToken cancellationToken); + /// /// Run an HTTP DELETE request /// diff --git a/src/Tgstation.Server.Client/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/IInstanceManagerClient.cs index 57030b1deb..52bc42e769 100644 --- a/src/Tgstation.Server.Client/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/IInstanceManagerClient.cs @@ -50,6 +50,14 @@ namespace Tgstation.Server.Client /// A representing the running operation Task Detach(Instance instance, CancellationToken cancellationToken); + /// + /// Gives the user full permissions on an . + /// + /// The to grant permissions on. + /// The for the operation. + /// A representing the running operation. + Task GrantPermissions(Instance instance, CancellationToken cancellationToken); + /// /// Create an for a given /// diff --git a/src/Tgstation.Server.Client/InstanceManagerClient.cs b/src/Tgstation.Server.Client/InstanceManagerClient.cs index aa5458f0e6..6a00038d42 100644 --- a/src/Tgstation.Server.Client/InstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/InstanceManagerClient.cs @@ -47,6 +47,9 @@ namespace Tgstation.Server.Client /// public Task GetId(Instance instance, CancellationToken cancellationToken) => apiClient.Read(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken); + /// + public Task GrantPermissions(Instance instance, CancellationToken cancellationToken) => apiClient.Patch(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken); + /// public IInstanceClient CreateClient(Instance instance) { diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 35a8928d4c..f9d2ccc9b9 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -274,7 +274,7 @@ namespace Tgstation.Server.Host.Components.Repository committerEmail); if (!IsGitHubRepository) - throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!"); + throw new JobException(ErrorCode.RepoTestMergeInvalidRemote); var commitMessage = String.Format( CultureInfo.InvariantCulture, diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 95207a7f78..5efd4ed1c4 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -114,17 +114,22 @@ namespace Tgstation.Server.Host.Controllers return path; } - Models.InstanceUser InstanceAdminUser() => new Models.InstanceUser + Models.InstanceUser InstanceAdminUser(Models.InstanceUser userToModify) { - ByondRights = (ByondRights)~0U, - ChatBotRights = (ChatBotRights)~0U, - ConfigurationRights = (ConfigurationRights)~0U, - DreamDaemonRights = (DreamDaemonRights)~0U, - DreamMakerRights = (DreamMakerRights)~0U, - RepositoryRights = (RepositoryRights)~0U, - InstanceUserRights = (InstanceUserRights)~0U, - UserId = AuthenticationContext.User.Id - }; + if (userToModify == null) + userToModify = new Models.InstanceUser() + { + UserId = AuthenticationContext.User.Id + }; + userToModify.ByondRights = RightsHelper.AllRights(); + userToModify.ChatBotRights = RightsHelper.AllRights(); + userToModify.ConfigurationRights = RightsHelper.AllRights(); + userToModify.DreamDaemonRights = RightsHelper.AllRights(); + userToModify.DreamMakerRights = RightsHelper.AllRights(); + userToModify.RepositoryRights = RightsHelper.AllRights(); + userToModify.InstanceUserRights = RightsHelper.AllRights(); + return userToModify; + } /// /// Create or attach an . @@ -267,7 +272,7 @@ namespace Tgstation.Server.Host.Controllers }, InstanceUsers = new List // give this user full privileges on the instance { - InstanceAdminUser() + InstanceAdminUser(null) } }; @@ -458,21 +463,6 @@ namespace Tgstation.Server.Host.Controllers return Conflict(new ErrorMessage(ErrorCode.ChatBotMax)); } - // ensure the current user has write privilege on the instance - var usersInstanceUser = await InstanceQuery() - .SelectMany(x => x.InstanceUsers) - .Where(x => x.UserId == AuthenticationContext.User.Id) - .FirstOrDefaultAsync(cancellationToken) - .ConfigureAwait(false); - if (usersInstanceUser == default) - { - var instanceAdminUser = InstanceAdminUser(); - instanceAdminUser.InstanceId = originalModel.Id; - DatabaseContext.InstanceUsers.Add(instanceAdminUser); - } - else - usersInstanceUser.InstanceUserRights |= InstanceUserRights.WriteUsers; - await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); if (renamed) @@ -634,5 +624,40 @@ namespace Tgstation.Server.Host.Controllers api.MoveJob = moveJob?.ToApi(); return Json(api); } + + /// + /// Gives the current user full permissions on a given instance . + /// + /// The instance to give permissions on. + /// The for the operation. + /// A resulting in the of the request. + /// Granted permissions successfully. + [HttpPatch("{id}")] + [TgsAuthorize(InstanceManagerRights.GrantPermissions)] + [ProducesResponseType(204)] + public async Task GrantPermissions(long id, CancellationToken cancellationToken) + { + // ensure the current user has write privilege on the instance + var usersInstanceUser = await DatabaseContext + .Instances + .AsQueryable() + .Where(x => x.Id == id) + .SelectMany(x => x.InstanceUsers) + .Where(x => x.UserId == AuthenticationContext.User.Id) + .FirstOrDefaultAsync(cancellationToken) + .ConfigureAwait(false); + if (usersInstanceUser == default) + { + var instanceAdminUser = InstanceAdminUser(null); + instanceAdminUser.InstanceId = id; + DatabaseContext.InstanceUsers.Add(instanceAdminUser); + } + else + InstanceAdminUser(usersInstanceUser); + + await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + + return NoContent(); + } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 3d75f56a7a..df047e95ad 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -211,7 +211,7 @@ namespace Tgstation.Server.Host.Core // HACK HACK HACK HACK HACK const string ConfigureMethodName = nameof(SqlServerDatabaseContext.ConfigureWith); var configureFunction = typeof(TContext).GetMethod( - nameof(SqlServerDatabaseContext.ConfigureWith), + ConfigureMethodName, BindingFlags.Public | BindingFlags.Static); if (configureFunction == null) diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 2a2d1c3b41..0d5eaf7063 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -1,6 +1,5 @@ { "General": { - "ApiPort": 5000, "MinimumPasswordLength": 15, "GitHubAccessToken": null, "SetupWizardMode": "AutoDetect", @@ -33,6 +32,13 @@ } } }, + "Kestrel": { + "EndPoints": { + "Http": { + "Url": "http://0.0.0.0:80" + } + } + }, "ControlPanel": { "Enable": false, "AllowAnyOrigin": false, diff --git a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs index 1cd109c3e7..0d0f384dc4 100644 --- a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs @@ -80,26 +80,26 @@ namespace Tgstation.Server.Tests.Instance // checkout V3 and back cloned.Reference = "V3"; - var updated = await Checkout(cloned, false, true, true, cancellationToken); + var updated = await Checkout(cloned, false, true, cancellationToken); // Specific SHA updated.CheckoutSha = "f43f5bd"; - await ApiAssert.ThrowsException(() => Checkout(updated, false, false, false, cancellationToken), ErrorCode.RepoMismatchShaAndReference); + await ApiAssert.ThrowsException(() => Checkout(updated, false, false, cancellationToken), ErrorCode.RepoMismatchShaAndReference); updated.Reference = null; - updated = await Checkout(updated, false, false, false, cancellationToken); + updated = await Checkout(updated, false, false, cancellationToken); // Fake SHA updated.Reference = null; updated.CheckoutSha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - updated = await Checkout(updated, true, false, false, cancellationToken); + updated = await Checkout(updated, true, false, cancellationToken); // Fake ref updated.Reference = "Tgs4IntegrationTestFakeBranchNeverNameABranchThis"; - updated = await Checkout(updated, true, true, false, cancellationToken); + updated = await Checkout(updated, true, true, cancellationToken); // Back updated.Reference = workingBranch; - updated = await Checkout(updated, false, true, false, cancellationToken); + updated = await Checkout(updated, false, true, cancellationToken); var testPRString = Environment.GetEnvironmentVariable("TGS4_TEST_PULL_REQUEST_NUMBER"); if (String.IsNullOrWhiteSpace(testPRString)) @@ -118,13 +118,11 @@ namespace Tgstation.Server.Tests.Instance await TestMergeTests(updated, prNumber, cancellationToken); } - async Task Checkout(Repository updated, bool expectFailure, bool isRef, bool checkBusy, CancellationToken cancellationToken) + async Task Checkout(Repository updated, bool expectFailure, bool isRef, CancellationToken cancellationToken) { var newRef = isRef ? updated.Reference : updated.CheckoutSha; var checkingOut = await repositoryClient.Update(updated, cancellationToken); Assert.IsNotNull(checkingOut.ActiveJob); - if(checkBusy) - await ApiAssert.ThrowsException(() => repositoryClient.Read(cancellationToken), ErrorCode.RepoBusy); await WaitForJob(checkingOut.ActiveJob, 30, expectFailure, cancellationToken); var result = await repositoryClient.Read(cancellationToken); diff --git a/tests/Tgstation.Server.Tests/InstanceManagerTest.cs b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs index 8018445d20..208ed2bdb5 100644 --- a/tests/Tgstation.Server.Tests/InstanceManagerTest.cs +++ b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs @@ -143,12 +143,14 @@ namespace Tgstation.Server.Tests await Assert.ThrowsExceptionAsync(() => instanceClient.Users.Read(cancellationToken)).ConfigureAwait(false); - await instanceManagerClient.Update(new Api.Models.Instance + await instanceManagerClient.GrantPermissions(new Api.Models.Instance { Id = firstTest.Id }, cancellationToken).ConfigureAwait(false); ourInstanceUser = await instanceClient.Users.Read(cancellationToken).ConfigureAwait(false); + Assert.AreEqual(RightsHelper.AllRights(), ourInstanceUser.DreamDaemonRights.Value); + //can't detach online instance await ApiAssert.ThrowsException(() => instanceManagerClient.Detach(firstTest, cancellationToken), ErrorCode.InstanceDetachOnline).ConfigureAwait(false);