diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index 478cfeefec..37f4574866 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -19,6 +19,7 @@ using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Security; using Tgstation.Server.Host.System; @@ -173,7 +174,7 @@ namespace Tgstation.Server.Host.Controllers catch (ApiException e) { Logger.LogWarning(e, OctokitException); - return StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) + return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) { AdditionalData = e.Message, }); @@ -224,9 +225,9 @@ namespace Tgstation.Server.Host.Controllers { NewVersion = model.NewVersion, }), - ServerUpdateResult.ReleaseMissing => Gone(), + ServerUpdateResult.ReleaseMissing => this.Gone(), ServerUpdateResult.UpdateInProgress => BadRequest(new ErrorMessageResponse(ErrorCode.ServerUpdateInProgress)), - ServerUpdateResult.SwarmIntegrityCheckFailed => StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.SwarmIntegrityCheckFailed)), + ServerUpdateResult.SwarmIntegrityCheckFailed => this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.SwarmIntegrityCheckFailed)), _ => throw new InvalidOperationException($"Unexpected ServerUpdateResult: {updateResult}"), }; } @@ -237,7 +238,7 @@ namespace Tgstation.Server.Host.Controllers catch (ApiException e) { Logger.LogWarning(e, OctokitException); - return StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) + return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError) { AdditionalData = e.Message, }); diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index c563525265..c3e64cfdac 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -14,13 +14,16 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Query; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; + using Octokit; + using Serilog.Context; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; using Tgstation.Server.Host.Utils; @@ -119,7 +122,7 @@ namespace Tgstation.Server.Host.Controllers if (!ApiHeaders.Compatible()) { - await StatusCode( + await this.StatusCode( HttpStatusCode.UpgradeRequired, new ErrorMessageResponse(ErrorCode.ApiMismatch)) .ExecuteResultAsync(context) @@ -206,12 +209,6 @@ namespace Tgstation.Server.Host.Controllers } #pragma warning restore CA1506 - /// - /// Generic 410 response. - /// - /// An with . - protected ObjectResult Gone() => StatusCode(HttpStatusCode.Gone, new ErrorMessageResponse(ErrorCode.ResourceNotPresent)); - /// /// Generic 404 response. /// @@ -222,7 +219,7 @@ namespace Tgstation.Server.Host.Controllers /// Generic 501 response. /// /// An with . - protected ObjectResult RequiresPosixSystemIdentity() => StatusCode(HttpStatusCode.NotImplemented, new ErrorMessageResponse(ErrorCode.RequiresPosixSystemIdentity)); + protected ObjectResult RequiresPosixSystemIdentity() => this.StatusCode(HttpStatusCode.NotImplemented, new ErrorMessageResponse(ErrorCode.RequiresPosixSystemIdentity)); /// /// Strongly type calls to . @@ -231,14 +228,6 @@ namespace Tgstation.Server.Host.Controllers /// A with the given . protected StatusCodeResult StatusCode(HttpStatusCode statusCode) => StatusCode((int)statusCode); - /// - /// Strongly type calls to . - /// - /// The . - /// The accompanying payload. - /// A with the given . - protected ObjectResult StatusCode(HttpStatusCode statusCode, object errorMessage) => StatusCode((int)statusCode, errorMessage); - /// /// Generic 201 response with a given . /// @@ -260,7 +249,7 @@ namespace Tgstation.Server.Host.Controllers var secondsString = Math.Ceiling(rateLimitException.GetRetryAfterTimeSpan().TotalSeconds).ToString(CultureInfo.InvariantCulture); Response.Headers.Add(HeaderNames.RetryAfter, secondsString); - return StatusCode(HttpStatusCode.TooManyRequests, new ErrorMessageResponse(ErrorCode.GitHubApiRateLimit)); + return this.StatusCode(HttpStatusCode.TooManyRequests, new ErrorMessageResponse(ErrorCode.GitHubApiRateLimit)); } /// @@ -295,7 +284,7 @@ namespace Tgstation.Server.Host.Controllers }; if (headersException.MissingOrMalformedHeaders.HasFlag(HeaderTypes.Accept)) - return StatusCode(HttpStatusCode.NotAcceptable, errorMessage); + return this.StatusCode(HttpStatusCode.NotAcceptable, errorMessage); return BadRequest(errorMessage); } diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 66057e930c..d3de553456 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -14,6 +14,7 @@ using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -290,7 +291,7 @@ namespace Tgstation.Server.Host.Controllers return Task.FromResult( versionNotInstalled - ? Gone() + ? this.Gone() : null); }); diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index d4651d1877..d54d7c8fdc 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -20,6 +20,7 @@ using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -165,7 +166,7 @@ namespace Tgstation.Server.Host.Controllers return null; }) - ?? StatusCode(HttpStatusCode.Created, dbModel.ToApi()); + ?? this.StatusCode(HttpStatusCode.Created, dbModel.ToApi()); } /// @@ -251,7 +252,7 @@ namespace Tgstation.Server.Host.Controllers var results = await query.FirstOrDefaultAsync(cancellationToken); if (results == default) - return Gone(); + return this.Gone(); var connectionStrings = (AuthenticationContext.GetRight(RightsType.ChatBots) & (ulong)ChatBotRights.ReadConnectionString) != 0; @@ -295,7 +296,7 @@ namespace Tgstation.Server.Host.Controllers var current = await query.FirstOrDefaultAsync(cancellationToken); if (current == default) - return Gone(); + return this.Gone(); if ((model.Channels?.Count ?? current.Channels.Count) > (model.ChannelLimit ?? current.ChannelLimit.Value)) { diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index cc401330f4..4f86feb9a1 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -14,6 +14,7 @@ using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -132,7 +133,7 @@ namespace Tgstation.Server.Host.Controllers .Read(filePath, systemIdentity, cancellationToken) ; if (result == null) - return Gone(); + return this.Gone(); return Json(result); }) @@ -186,7 +187,7 @@ namespace Tgstation.Server.Host.Controllers .ListDirectory(directoryPath, systemIdentity, cancellationToken) ; if (result == null) - return new PaginatableResult(Gone()); + return new PaginatableResult(this.Gone()); return new PaginatableResult( result diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs index 98db93e527..7f82032066 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs @@ -17,6 +17,7 @@ using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Session; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -173,7 +174,7 @@ namespace Tgstation.Server.Host.Controllers .FirstOrDefaultAsync(cancellationToken); if (current == default) - return Gone(); + return this.Gone(); if (model.Port.HasValue && model.Port.Value != current.Port.Value) { @@ -339,7 +340,7 @@ namespace Tgstation.Server.Host.Controllers .FirstOrDefaultAsync(cancellationToken) ; if (settings == default) - return Gone(); + return this.Gone(); } var result = new DreamDaemonResponse(); diff --git a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs index acd862ee72..6709e60929 100644 --- a/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs +++ b/src/Tgstation.Server.Host/Controllers/DreamMakerController.cs @@ -14,6 +14,7 @@ using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -191,7 +192,7 @@ namespace Tgstation.Server.Host.Controllers .FirstOrDefaultAsync(cancellationToken) ; if (hostModel == null) - return Gone(); + return this.Gone(); if (model.ProjectName != null) { diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 958ea12a2b..1774329e64 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -21,6 +21,7 @@ using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; using Tgstation.Server.Host.Security.OAuth; @@ -178,7 +179,7 @@ namespace Tgstation.Server.Host.Controllers // we only allow authorization header issues var headers = new ApiHeaders(Request.GetTypedHeaders(), true); if (!headers.Compatible()) - return StatusCode( + return this.StatusCode( HttpStatusCode.UpgradeRequired, new ErrorMessageResponse(ErrorCode.ApiMismatch)); } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 60be70f7d2..443eec1021 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -20,6 +20,7 @@ using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; @@ -288,7 +289,7 @@ namespace Tgstation.Server.Host.Controllers .Where(x => x.Id == id && x.SwarmIdentifer == swarmConfiguration.Identifier) .FirstOrDefaultAsync(cancellationToken); if (originalModel == default) - return Gone(); + return this.Gone(); if (originalModel.Online.Value) return Conflict(new ErrorMessageResponse(ErrorCode.InstanceDetachOnline)); @@ -359,7 +360,7 @@ namespace Tgstation.Server.Host.Controllers .Include(x => x.DreamDaemonSettings) // need these for onlining .FirstOrDefaultAsync(cancellationToken); if (originalModel == default(Models.Instance)) - return Gone(); + return this.Gone(); if (ValidateInstanceOnlineStatus(originalModel)) await DatabaseContext.Save(cancellationToken); @@ -604,7 +605,7 @@ namespace Tgstation.Server.Host.Controllers var instance = await QueryForUser().FirstOrDefaultAsync(cancellationToken); if (instance == null) - return Gone(); + return this.Gone(); if (ValidateInstanceOnlineStatus(instance)) await DatabaseContext.Save(cancellationToken); @@ -664,7 +665,7 @@ namespace Tgstation.Server.Host.Controllers ; if (!instanceExists) - return Gone(); + return this.Gone(); var instanceAdminUser = InstanceAdminPermissionSet(null); instanceAdminUser.InstanceId = id; diff --git a/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs b/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs index c04d169e3d..b8fafa0466 100644 --- a/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstancePermissionSetController.cs @@ -14,6 +14,7 @@ using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -77,7 +78,7 @@ namespace Tgstation.Server.Host.Controllers ; if (existingPermissionSet == default) - return Gone(); + return this.Gone(); if (existingPermissionSet.UserId.HasValue) { @@ -140,7 +141,7 @@ namespace Tgstation.Server.Host.Controllers .FirstOrDefaultAsync(cancellationToken) ; if (originalPermissionSet == null) - return Gone(); + return this.Gone(); originalPermissionSet.ByondRights = RightsHelper.Clamp(model.ByondRights ?? originalPermissionSet.ByondRights.Value); originalPermissionSet.RepositoryRights = RightsHelper.Clamp(model.RepositoryRights ?? originalPermissionSet.RepositoryRights.Value); @@ -222,7 +223,7 @@ namespace Tgstation.Server.Host.Controllers .FirstOrDefaultAsync(cancellationToken) ; if (permissionSet == default) - return Gone(); + return this.Gone(); return Json(permissionSet.ToApi()); } @@ -248,7 +249,7 @@ namespace Tgstation.Server.Host.Controllers .Where(x => x.PermissionSetId == id) .DeleteAsync(cancellationToken) ; - return numDeleted > 0 ? NoContent() : Gone(); + return numDeleted > 0 ? NoContent() : this.Gone(); } } } diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 577c3113bb..2c92b651dd 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -12,6 +12,7 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -139,7 +140,7 @@ namespace Tgstation.Server.Host.Controllers return Forbid(); var updatedJob = await jobManager.CancelJob(job, AuthenticationContext.User, false, cancellationToken); - return updatedJob != null ? Accepted(updatedJob.ToApi()) : Gone(); + return updatedJob != null ? Accepted(updatedJob.ToApi()) : this.Gone(); } /// diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index 269165b4fd..5e99aff62e 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -18,6 +18,7 @@ using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Repository; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -107,7 +108,7 @@ namespace Tgstation.Server.Host.Controllers ; if (currentModel == default) - return Gone(); + return this.Gone(); currentModel.UpdateSubmodules = model.UpdateSubmodules ?? true; currentModel.AccessToken = model.AccessToken; @@ -214,7 +215,7 @@ namespace Tgstation.Server.Host.Controllers ; if (currentModel == default) - return Gone(); + return this.Gone(); currentModel.AccessToken = null; currentModel.AccessUser = null; @@ -262,7 +263,7 @@ namespace Tgstation.Server.Host.Controllers ; if (currentModel == default) - return Gone(); + return this.Gone(); var api = currentModel.ToApi(); @@ -351,7 +352,7 @@ namespace Tgstation.Server.Host.Controllers ; if (currentModel == default) - return Gone(); + return this.Gone(); bool CheckModified(Expression> expression, RepositoryRights requiredRight) { diff --git a/src/Tgstation.Server.Host/Controllers/TransferController.cs b/src/Tgstation.Server.Host/Controllers/TransferController.cs index 3907d3ecd8..6374b65557 100644 --- a/src/Tgstation.Server.Host/Controllers/TransferController.cs +++ b/src/Tgstation.Server.Host/Controllers/TransferController.cs @@ -1,20 +1,16 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Net; -using System.Net.Mime; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; using Tgstation.Server.Api; using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Security; using Tgstation.Server.Host.Transfer; @@ -65,41 +61,8 @@ namespace Tgstation.Server.Host.Controllers [HttpGet] [ProducesResponseType(200, Type = typeof(LimitedStreamResult))] [ProducesResponseType(410, Type = typeof(ErrorMessageResponse))] - public async Task Download([Required, FromQuery] string ticket, CancellationToken cancellationToken) - { - if (ticket == null) - return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure)); - - var streamAccept = new MediaTypeHeaderValue(MediaTypeNames.Application.Octet); - if (!Request.GetTypedHeaders().Accept.Any(x => streamAccept.IsSubsetOf(x))) - return StatusCode(HttpStatusCode.NotAcceptable, new ErrorMessageResponse(ErrorCode.BadHeaders) - { - AdditionalData = $"File downloads must accept both {MediaTypeNames.Application.Octet} and {MediaTypeNames.Application.Json}!", - }); - - var fileTicketResult = new FileTicketResponse - { - FileTicket = ticket, - }; - - var tuple = await fileTransferService.RetrieveDownloadStream(fileTicketResult, cancellationToken); - var stream = tuple.Item1; - try - { - if (tuple.Item2 != null) - return Conflict(tuple.Item2); - - if (stream == null) - return Gone(); - - return new LimitedStreamResult(stream); - } - catch - { - await stream.DisposeAsync(); - throw; - } - } + public Task Download([Required, FromQuery] string ticket, CancellationToken cancellationToken) + => fileTransferService.GenerateDownloadResponse(this, ticket, cancellationToken); /// /// Uploads a file with a given . @@ -127,7 +90,7 @@ namespace Tgstation.Server.Host.Controllers var result = await fileTransferService.SetUploadStream(fileTicketResult, Request.Body, cancellationToken); if (result != null) return result.ErrorCode == ErrorCode.ResourceNotPresent - ? Gone() + ? this.Gone() : Conflict(result); return NoContent(); diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs index 78ed4839c7..a2dc87f763 100644 --- a/src/Tgstation.Server.Host/Controllers/UserController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserController.cs @@ -16,6 +16,7 @@ using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -116,14 +117,14 @@ namespace Tgstation.Server.Host.Controllers var dbUser = await CreateNewUserFromModel(model, cancellationToken); if (dbUser == null) - return Gone(); + return this.Gone(); if (model.SystemIdentifier != null) try { using var sysIdentity = await systemIdentityFactory.CreateSystemIdentity(dbUser, cancellationToken); if (sysIdentity == null) - return Gone(); + return this.Gone(); dbUser.Name = sysIdentity.Username; dbUser.SystemIdentifier = sysIdentity.Uid; } @@ -269,7 +270,7 @@ namespace Tgstation.Server.Host.Controllers ; if (originalUser.Group == default) - return Gone(); + return this.Gone(); DatabaseContext.Groups.Attach(originalUser.Group); if (originalUser.PermissionSet != null) diff --git a/src/Tgstation.Server.Host/Controllers/UserGroupController.cs b/src/Tgstation.Server.Host/Controllers/UserGroupController.cs index 1e38468534..b3670eb668 100644 --- a/src/Tgstation.Server.Host/Controllers/UserGroupController.cs +++ b/src/Tgstation.Server.Host/Controllers/UserGroupController.cs @@ -15,6 +15,7 @@ using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Api.Rights; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Models; using Tgstation.Server.Host.Security; @@ -125,7 +126,7 @@ namespace Tgstation.Server.Host.Controllers ; if (currentGroup == default) - return Gone(); + return this.Gone(); if (model.PermissionSet != null) { @@ -170,7 +171,7 @@ namespace Tgstation.Server.Host.Controllers .FirstOrDefaultAsync(cancellationToken) ; if (group == default) - return Gone(); + return this.Gone(); return Json(group.ToApi(true)); } @@ -236,7 +237,7 @@ namespace Tgstation.Server.Host.Controllers return groupExists ? Conflict(new ErrorMessageResponse(ErrorCode.UserGroupNotEmpty)) - : Gone(); + : this.Gone(); } } } diff --git a/src/Tgstation.Server.Host/Extensions/ControllerBaseExtensions.cs b/src/Tgstation.Server.Host/Extensions/ControllerBaseExtensions.cs new file mode 100644 index 0000000000..9ab3b1cc87 --- /dev/null +++ b/src/Tgstation.Server.Host/Extensions/ControllerBaseExtensions.cs @@ -0,0 +1,34 @@ +using System; +using System.Net; + +using Microsoft.AspNetCore.Mvc; + +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Response; + +namespace Tgstation.Server.Host.Extensions +{ + /// + /// Extension methods for . + /// + static class ControllerBaseExtensions + { + /// + /// Generic 410 response. + /// + /// The the request is coming from. + /// An with . + public static ObjectResult Gone(this ControllerBase controller) + => controller?.StatusCode(HttpStatusCode.Gone, new ErrorMessageResponse(ErrorCode.ResourceNotPresent)) ?? throw new ArgumentNullException(nameof(controller)); + + /// + /// Strongly type calls to . + /// + /// The the request is coming from. + /// The . + /// The accompanying payload. + /// A with the given . + public static ObjectResult StatusCode(this ControllerBase controller, HttpStatusCode statusCode, object errorMessage) + => controller?.StatusCode((int)statusCode, errorMessage) ?? throw new ArgumentNullException(nameof(controller)); + } +} diff --git a/src/Tgstation.Server.Host/Extensions/FileTransferStreamHandlerExtensions.cs b/src/Tgstation.Server.Host/Extensions/FileTransferStreamHandlerExtensions.cs new file mode 100644 index 0000000000..ee4699d7a7 --- /dev/null +++ b/src/Tgstation.Server.Host/Extensions/FileTransferStreamHandlerExtensions.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Mime; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; + +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Response; +using Tgstation.Server.Host.Controllers; +using Tgstation.Server.Host.Transfer; + +namespace Tgstation.Server.Host.Extensions +{ + /// + /// Extension methods for . + /// + static class FileTransferStreamHandlerExtensions + { + /// + /// Downloads a file with a given . + /// + /// The . + /// The the request is coming from. + /// The for the download. + /// The for the operation. + /// A resulting in the of the method. + public static async Task GenerateDownloadResponse( + this IFileTransferStreamHandler fileTransferService, + ControllerBase controller, + string ticket, + CancellationToken cancellationToken) + { + if (fileTransferService == null) + throw new ArgumentNullException(nameof(fileTransferService)); + + if (controller == null) + throw new ArgumentNullException(nameof(controller)); + + if (ticket == null) + return controller.BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure)); + + var streamAccept = new MediaTypeHeaderValue(MediaTypeNames.Application.Octet); + if (!controller.Request.GetTypedHeaders().Accept.Any(streamAccept.IsSubsetOf)) + return controller.StatusCode((int)HttpStatusCode.NotAcceptable, new ErrorMessageResponse(ErrorCode.BadHeaders) + { + AdditionalData = $"File downloads must accept both {MediaTypeNames.Application.Octet} and {MediaTypeNames.Application.Json}!", + }); + + var fileTicketResult = new FileTicketResponse + { + FileTicket = ticket, + }; + + var tuple = await fileTransferService.RetrieveDownloadStream(fileTicketResult, cancellationToken); + var stream = tuple.Item1; + try + { + if (tuple.Item2 != null) + return controller.Conflict(tuple.Item2); + + if (stream == null) + return controller.Gone(); + + return new LimitedStreamResult(stream); + } + catch + { + await stream.DisposeAsync(); + throw; + } + } + } +}