mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 15:07:03 +01:00
Merge pull request #1052 from tgstation/1009-FixInstanceRestore
Add instance user restore permission
This commit is contained in:
@@ -531,5 +531,11 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Description("Could not create dump as gcore exited with a non-zero exit code!")]
|
||||
GCoreFailure,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to test merge with an invalid remote repository.
|
||||
/// </summary>
|
||||
[Description("Test merging cannot be performed with this remote!")]
|
||||
RepoTestMergeInvalidRemote,
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,11 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Instance.ChatBotLimit"/>.
|
||||
/// </summary>
|
||||
SetChatBotLimit = 512
|
||||
SetChatBotLimit = 512,
|
||||
|
||||
/// <summary>
|
||||
/// User can give themselves full <see cref="Models.InstanceUser"/> rights on instances.
|
||||
/// </summary>
|
||||
GrantPermissions = 1024,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +235,9 @@ namespace Tgstation.Server.Client
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Patch(string route, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Patch, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Post, null, false, cancellationToken);
|
||||
|
||||
|
||||
@@ -90,6 +90,14 @@ namespace Tgstation.Server.Client
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP PATCH request.
|
||||
/// </summary>
|
||||
/// <param name="route">The server route to make the request to.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task Patch(string route, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP DELETE request
|
||||
/// </summary>
|
||||
|
||||
@@ -50,6 +50,14 @@ namespace Tgstation.Server.Client
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Detach(Instance instance, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gives the user full permissions on an <paramref name="instance"/>.
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> to grant permissions on.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task GrantPermissions(Instance instance, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create an <see cref="IInstanceClient"/> for a given <see cref="Instance"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -47,6 +47,9 @@ namespace Tgstation.Server.Client
|
||||
/// <inheritdoc />
|
||||
public Task<Instance> GetId(Instance instance, CancellationToken cancellationToken) => apiClient.Read<Instance>(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task GrantPermissions(Instance instance, CancellationToken cancellationToken) => apiClient.Patch(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IInstanceClient CreateClient(Instance instance)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<ByondRights>();
|
||||
userToModify.ChatBotRights = RightsHelper.AllRights<ChatBotRights>();
|
||||
userToModify.ConfigurationRights = RightsHelper.AllRights<ConfigurationRights>();
|
||||
userToModify.DreamDaemonRights = RightsHelper.AllRights<DreamDaemonRights>();
|
||||
userToModify.DreamMakerRights = RightsHelper.AllRights<DreamMakerRights>();
|
||||
userToModify.RepositoryRights = RightsHelper.AllRights<RepositoryRights>();
|
||||
userToModify.InstanceUserRights = RightsHelper.AllRights<InstanceUserRights>();
|
||||
return userToModify;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create or attach an <see cref="Api.Models.Instance"/>.
|
||||
@@ -267,7 +272,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
},
|
||||
InstanceUsers = new List<Models.InstanceUser> // 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gives the current user full permissions on a given instance <paramref name="id"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The instance <see cref="EntityId.Id"/> to give permissions on.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request.</returns>
|
||||
/// <response code="204">Granted permissions successfully.</response>
|
||||
[HttpPatch("{id}")]
|
||||
[TgsAuthorize(InstanceManagerRights.GrantPermissions)]
|
||||
[ProducesResponseType(204)]
|
||||
public async Task<IActionResult> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<ApiConflictException>(() => Checkout(updated, false, false, false, cancellationToken), ErrorCode.RepoMismatchShaAndReference);
|
||||
await ApiAssert.ThrowsException<ApiConflictException>(() => 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<Repository> Checkout(Repository updated, bool expectFailure, bool isRef, bool checkBusy, CancellationToken cancellationToken)
|
||||
async Task<Repository> 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<ConflictException>(() => repositoryClient.Read(cancellationToken), ErrorCode.RepoBusy);
|
||||
|
||||
await WaitForJob(checkingOut.ActiveJob, 30, expectFailure, cancellationToken);
|
||||
var result = await repositoryClient.Read(cancellationToken);
|
||||
|
||||
@@ -143,12 +143,14 @@ namespace Tgstation.Server.Tests
|
||||
|
||||
await Assert.ThrowsExceptionAsync<InsufficientPermissionsException>(() => 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<DreamDaemonRights>(), ourInstanceUser.DreamDaemonRights.Value);
|
||||
|
||||
//can't detach online instance
|
||||
await ApiAssert.ThrowsException<ConflictException>(() => instanceManagerClient.Detach(firstTest, cancellationToken), ErrorCode.InstanceDetachOnline).ConfigureAwait(false);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user