Adds the accessible property to InstanceResponse

This commit is contained in:
Jordan Brown
2021-08-11 04:30:21 -04:00
parent d8d8850705
commit 06c1c8d827
7 changed files with 62 additions and 27 deletions
+3 -3
View File
@@ -5,9 +5,9 @@
<PropertyGroup>
<TgsCoreVersion>4.12.1</TgsCoreVersion>
<TgsConfigVersion>3.1.0</TgsConfigVersion>
<TgsApiVersion>9.0.1</TgsApiVersion>
<TgsApiLibraryVersion>9.0.0</TgsApiLibraryVersion>
<TgsClientVersion>10.0.0</TgsClientVersion>
<TgsApiVersion>9.1.0</TgsApiVersion>
<TgsApiLibraryVersion>9.1.0</TgsApiLibraryVersion>
<TgsClientVersion>10.1.0</TgsClientVersion>
<TgsDmapiVersion>6.0.4</TgsDmapiVersion>
<TgsInteropVersion>5.3.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.1.1</TgsHostWatchdogVersion>
@@ -11,5 +11,10 @@
/// <remarks>Due to how <see cref="JobResponse"/>s are children of <see cref="Instance"/>s but moving one requires the <see cref="Instance"/> to be offline, interactions with this <see cref="JobResponse"/> are performed in a non-standard fashion. The <see cref="JobResponse"/> is read by querying the <see cref="Instance"/> again (either via list or ID lookup) and cancelled by making any sort of update to the <see cref="Instance"/>. Once the <see cref="Instance"/> comes back <see cref="Instance.Online"/> it can be queried like a normal job.</remarks>
[ResponseOptions]
public JobResponse? MoveJob { get; set; }
/// <summary>
/// If the querying user may access this instance.
/// </summary>
public bool Accessible { get; set; }
}
}
@@ -296,14 +296,14 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
/// <typeparam name="TModel">The <see cref="Type"/> of model being generated and returned.</typeparam>
/// <param name="queryGenerator">A <see cref="Func{TResult}"/> resulting in a <see cref="Task{TResult}"/> resulting in the generated <see cref="PaginatableResult{TModel}"/>.</param>
/// <param name="resultTransformer">An <see cref="Action{T1}"/> to transform the <typeparamref name="TModel"/>s after being queried.</param>
/// <param name="resultTransformer">A <see cref="Func{T, TResult}"/> to transform the <typeparamref name="TModel"/>s after being queried.</param>
/// <param name="pageQuery">The requested page from the query.</param>
/// <param name="pageSizeQuery">The requested page size from the query.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
protected Task<IActionResult> Paginated<TModel>(
Func<Task<PaginatableResult<TModel>>> queryGenerator,
Action<TModel> resultTransformer,
Func<TModel, Task> resultTransformer,
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken) => PaginatedImpl(
@@ -319,14 +319,14 @@ namespace Tgstation.Server.Host.Controllers
/// <typeparam name="TModel">The <see cref="Type"/> of model being generated.</typeparam>
/// <typeparam name="TApiModel">The <see cref="Type"/> of model being returned.</typeparam>
/// <param name="queryGenerator">A <see cref="Func{TResult}"/> resulting in a <see cref="Task{TResult}"/> resulting in the generated <see cref="PaginatableResult{TModel}"/>.</param>
/// <param name="resultTransformer">An <see cref="Action{T1}"/> to transform the <typeparamref name="TApiModel"/>s after being queried.</param>
/// <param name="resultTransformer">A <see cref="Func{T, TResult}"/> to transform the <typeparamref name="TApiModel"/>s after being queried.</param>
/// <param name="pageQuery">The requested page from the query.</param>
/// <param name="pageSizeQuery">The requested page size from the query.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
protected Task<IActionResult> Paginated<TModel, TApiModel>(
Func<Task<PaginatableResult<TModel>>> queryGenerator,
Action<TApiModel> resultTransformer,
Func<TApiModel, Task> resultTransformer,
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken)
@@ -344,14 +344,14 @@ namespace Tgstation.Server.Host.Controllers
/// <typeparam name="TModel">The <see cref="Type"/> of model being generated. If different from <typeparamref name="TResultModel"/>, must implement <see cref="IApiTransformable{TApiModel}"/> for <typeparamref name="TResultModel"/>.</typeparam>
/// <typeparam name="TResultModel">The <see cref="Type"/> of model being returned.</typeparam>
/// <param name="queryGenerator">A <see cref="Func{TResult}"/> resulting in a <see cref="Task{TResult}"/> resulting in the generated <see cref="PaginatableResult{TModel}"/>.</param>
/// <param name="resultTransformer">An <see cref="Action{T1}"/> to transform the <typeparamref name="TResultModel"/>s after being queried.</param>
/// <param name="resultTransformer">A <see cref="Func{T, TResult}"/> to transform the <typeparamref name="TResultModel"/>s after being queried.</param>
/// <param name="pageQuery">The requested page from the query.</param>
/// <param name="pageSizeQuery">The requested page size from the query.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
async Task<IActionResult> PaginatedImpl<TModel, TResultModel>(
Func<Task<PaginatableResult<TModel>>> queryGenerator,
Action<TResultModel> resultTransformer,
Func<TResultModel, Task> resultTransformer,
int? pageQuery,
int? pageSizeQuery,
CancellationToken cancellationToken)
@@ -406,7 +406,7 @@ namespace Tgstation.Server.Host.Controllers
if (resultTransformer != null)
foreach (var finalResult in finalResults)
resultTransformer(finalResult);
await resultTransformer(finalResult).ConfigureAwait(false);
var carryTheOne = totalResults % pageSize != 0
? 1
@@ -198,6 +198,8 @@ namespace Tgstation.Server.Host.Controllers
{
if (connectionStrings)
chatBot.ConnectionString = null;
return Task.CompletedTask;
},
page,
pageSize,
@@ -264,6 +264,7 @@ namespace Tgstation.Server.Host.Controllers
Logger.LogInformation("{0} {1} instance {2}: {3} ({4})", AuthenticationContext.User.Name, attached ? "attached" : "created", newInstance.Name, newInstance.Id, newInstance.Path);
var api = newInstance.ToApi();
api.Accessible = true; // instances are always accessible by their creator
return attached ? (IActionResult)Json(api) : Created(api);
}
@@ -495,6 +496,7 @@ namespace Tgstation.Server.Host.Controllers
await componentInstance.SetAutoUpdateInterval(model.AutoUpdateInterval.Value).ConfigureAwait(false);
}
await CheckAccessible(api, cancellationToken).ConfigureAwait(false);
return moving ? (IActionResult)Accepted(api) : Json(api);
}
#pragma warning restore CA1502
@@ -552,10 +554,11 @@ namespace Tgstation.Server.Host.Controllers
new PaginatableResult<Models.Instance>(
GetBaseQuery()
.OrderBy(x => x.Id))),
instance =>
async instance =>
{
needsUpdate |= InstanceRequiredController.ValidateInstanceOnlineStatus(instanceManager, Logger, instance);
instance.MoveJob = moveJobs.FirstOrDefault(x => x.Instance.Id == instance.Id)?.ToApi();
await CheckAccessible(instance, cancellationToken).ConfigureAwait(false);
},
page,
pageSize,
@@ -623,6 +626,7 @@ namespace Tgstation.Server.Host.Controllers
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
api.MoveJob = moveJob?.ToApi();
await CheckAccessible(api, cancellationToken).ConfigureAwait(false);
return Json(api);
}
@@ -785,5 +789,20 @@ namespace Tgstation.Server.Host.Controllers
return path;
}
/// <summary>
/// Populate the <see cref="InstanceResponse.Accessible"/> property of a given <paramref name="instanceResponse"/>.
/// </summary>
/// <param name="instanceResponse">The <see cref="InstanceResponse"/> to populate.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task CheckAccessible(InstanceResponse instanceResponse, CancellationToken cancellationToken)
{
instanceResponse.Accessible = await DatabaseContext
.InstancePermissionSets
.AsQueryable()
.Where(x => x.InstanceId == instanceResponse.Id && x.PermissionSetId == AuthenticationContext.PermissionSet.Id)
.AnyAsync(cancellationToken);
}
}
}
@@ -175,9 +175,11 @@ namespace Tgstation.Server.Host.Controllers
/// Supplements <see cref="JobResponse"/> <see cref="PaginatedResponse{TModel}"/>s with their <see cref="JobResponse.Progress"/>.
/// </summary>
/// <param name="jobResponse">The <see cref="JobResponse"/> to augment.</param>
private void AddJobProgressResponseTransformer(JobResponse jobResponse)
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
private Task AddJobProgressResponseTransformer(JobResponse jobResponse)
{
jobResponse.Progress = jobManager.JobProgress(jobResponse);
return Task.CompletedTask;
}
}
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Net;
@@ -181,22 +181,20 @@ namespace Tgstation.Server.Tests
var token = serverClient.Token.Bearer;
// check that 400s are returned appropriately
using var httpClient = new HttpClient();
using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ListRoute(Routes.InstanceManager).Substring(1) + "?pageSize=2"))
{
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RegressionTest1256", "1.0.0"));
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
request.Headers.Add(ApiHeaders.ApiVersionHeader, "Tgstation.Server.Api/" + ApiHeaders.Version);
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme, token);
using var response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
using var request = new HttpRequestMessage(HttpMethod.Get, url.ToString() + Routes.ListRoute(Routes.InstanceManager).Substring(1) + "?pageSize=2");
request.Headers.Accept.Clear();
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("RegressionTest1256", "1.0.0"));
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
request.Headers.Add(ApiHeaders.ApiVersionHeader, "Tgstation.Server.Api/" + ApiHeaders.Version);
request.Headers.Authorization = new AuthenticationHeaderValue(ApiHeaders.BearerAuthenticationScheme, token);
using var response = await httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var paginated = JsonConvert.DeserializeObject<PaginatedResponse<InstanceResponse>>(json);
var json = await response.Content.ReadAsStringAsync();
var paginated = JsonConvert.DeserializeObject<PaginatedResponse<InstanceResponse>>(json);
Assert.AreEqual(2, paginated.PageSize);
Assert.AreEqual(3, paginated.TotalPages);
}
Assert.AreEqual(2, paginated.PageSize);
Assert.AreEqual(3, paginated.TotalPages);
}
public async Task RunPostTest(CancellationToken cancellationToken)
@@ -205,16 +203,25 @@ namespace Tgstation.Server.Tests
var firstTest = instances.Single(x => x.Name == TestInstanceName);
var instanceClient = instanceManagerClient.CreateClient(firstTest);
Assert.IsTrue(firstTest.Accessible);
//can regain permissions on instance without instance user
var ourInstanceUser = await instanceClient.PermissionSets.Read(cancellationToken).ConfigureAwait(false);
await instanceClient.PermissionSets.Delete(ourInstanceUser, cancellationToken).ConfigureAwait(false);
firstTest = await instanceManagerClient.GetId(firstTest, cancellationToken);
Assert.IsFalse(firstTest.Accessible);
await Assert.ThrowsExceptionAsync<InsufficientPermissionsException>(() => instanceClient.PermissionSets.Read(cancellationToken)).ConfigureAwait(false);
await instanceManagerClient.GrantPermissions(new InstanceUpdateRequest
{
Id = firstTest.Id
}, cancellationToken).ConfigureAwait(false);
firstTest = await instanceManagerClient.GetId(firstTest, cancellationToken);
Assert.IsTrue(firstTest.Accessible);
ourInstanceUser = await instanceClient.PermissionSets.Read(cancellationToken).ConfigureAwait(false);
Assert.AreEqual(RightsHelper.AllRights<DreamDaemonRights>(), ourInstanceUser.DreamDaemonRights.Value);