From 54b7a171338588bb4a6bbe4ec20e7220ccd3b709 Mon Sep 17 00:00:00 2001 From: Terrails Date: Mon, 12 Aug 2024 19:37:36 +0200 Subject: [PATCH] restrict IDs using Range annotation --- .../API/v3/Controllers/EpisodeController.cs | 65 ++------ .../API/v3/Controllers/FilterController.cs | 14 +- .../API/v3/Controllers/GroupController.cs | 20 +-- .../API/v3/Controllers/ImageController.cs | 9 +- .../v3/Controllers/ImportFolderController.cs | 15 +- .../ReleaseManagementController.cs | 6 +- .../API/v3/Controllers/RenamerController.cs | 5 +- .../v3/Controllers/ReverseTreeController.cs | 13 +- .../API/v3/Controllers/SeriesController.cs | 147 +++++++----------- .../API/v3/Controllers/TagController.cs | 16 +- .../API/v3/Controllers/TreeController.cs | 36 ++--- .../API/v3/Controllers/UserController.cs | 11 +- .../API/v3/Controllers/WebUIController.cs | 7 +- 13 files changed, 130 insertions(+), 234 deletions(-) diff --git a/Shoko.Server/API/v3/Controllers/EpisodeController.cs b/Shoko.Server/API/v3/Controllers/EpisodeController.cs index 7f7cb9e16..bd991f765 100644 --- a/Shoko.Server/API/v3/Controllers/EpisodeController.cs +++ b/Shoko.Server/API/v3/Controllers/EpisodeController.cs @@ -42,8 +42,6 @@ public class EpisodeController : BaseController private readonly WatchedStatusService _watchedService; - internal const string EpisodeWithZeroID = "episodeID must be greater than 0"; - internal const string EpisodeNotFoundWithEpisodeID = "No Episode entry for the given episodeID"; internal const string EpisodeNotFoundForAnidbEpisodeID = "No Episode entry for the given anidbEpisodeID"; @@ -308,16 +306,13 @@ public ActionResult> GetAllEpisodes( /// [HttpGet("{episodeID}")] public ActionResult GetEpisodeByEpisodeID( - [FromRoute] int episodeID, + [FromRoute, Range(1, int.MaxValue)] int episodeID, [FromQuery] bool includeFiles = false, [FromQuery] bool includeMediaInfo = false, [FromQuery] bool includeAbsolutePaths = false, [FromQuery] bool includeXRefs = false, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null) { - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); @@ -340,11 +335,8 @@ public ActionResult GetEpisodeByEpisodeID( /// [Authorize("admin")] [HttpPost("{episodeID}/OverrideTitle")] - public ActionResult OverrideEpisodeTitle([FromRoute] int episodeID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Episode.Input.EpisodeTitleOverrideBody body) + public ActionResult OverrideEpisodeTitle([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Episode.Input.EpisodeTitleOverrideBody body) { - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) @@ -378,11 +370,8 @@ public ActionResult OverrideEpisodeTitle([FromRoute] int episodeID, [FromBody(Em /// [Authorize("admin")] [HttpPost("{episodeID}/SetHidden")] - public ActionResult PostEpisodeSetHidden([FromRoute] int episodeID, [FromQuery] bool value = true, [FromQuery] bool updateStats = true) + public ActionResult PostEpisodeSetHidden([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromQuery] bool value = true, [FromQuery] bool updateStats = true) { - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); @@ -422,11 +411,8 @@ public ActionResult PostEpisodeSetHidden([FromRoute] int episodeID, [FromQuery] /// Shoko ID /// [HttpGet("{episodeID}/AniDB")] - public ActionResult GetEpisodeAnidbByEpisodeID([FromRoute] int episodeID) + public ActionResult GetEpisodeAnidbByEpisodeID([FromRoute, Range(1, int.MaxValue)] int episodeID) { - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); @@ -490,11 +476,8 @@ public ActionResult GetEpisode( /// /// [HttpPost("{episodeID}/Vote")] - public ActionResult PostEpisodeVote([FromRoute] int episodeID, [FromBody] Vote vote) + public ActionResult PostEpisodeVote([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromBody] Vote vote) { - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); @@ -527,7 +510,7 @@ public ActionResult PostEpisodeVote([FromRoute] int episodeID, [FromBody] Vote v /// All TMDB Movies linked directly to the Shoko Episode. [HttpGet("{episodeID}/TMDB/Movie")] public ActionResult> GetTmdbMoviesByEpisodeID( - [FromRoute] int episodeID, + [FromRoute, Range(1, int.MaxValue)] int episodeID, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet include = null, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet language = null ) @@ -550,7 +533,7 @@ public ActionResult> GetTmdbMoviesByEpisodeID( /// All TMDB Movie cross-references for the Shoko Episode. [HttpGet("{seriesID}/TMDB/Movie/CrossReferences")] public ActionResult> GetTMDBMovieCrossReferenceByEpisodeID( - [FromRoute] int seriesID + [FromRoute, Range(1, int.MaxValue)] int seriesID ) { var episode = RepoFactory.AnimeEpisode.GetByID(seriesID); @@ -572,7 +555,7 @@ [FromRoute] int seriesID /// All TMDB Episodes linked to the Shoko Episode. [HttpGet("{episodeID}/TMDB/Episode")] public ActionResult> GetTmdbEpisodesByEpisodeID( - [FromRoute] int episodeID, + [FromRoute, Range(1, int.MaxValue)] int episodeID, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet include = null, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet language = null ) @@ -595,7 +578,7 @@ public ActionResult> GetTmdbEpisodesByEpisodeID( /// All TMDB Episode cross-references for the Shoko Episode. [HttpGet("{seriesID}/TMDB/Episode/CrossReferences")] public ActionResult> GetTMDBEpisodeCrossReferenceByEpisodeID( - [FromRoute] int seriesID + [FromRoute, Range(1, int.MaxValue)] int seriesID ) { var episode = RepoFactory.AnimeEpisode.GetByID(seriesID); @@ -618,11 +601,8 @@ [FromRoute] int seriesID /// Shoko ID /// [HttpGet("{episodeID}/TvDB")] - public ActionResult> GetEpisodeTvDBDetails([FromRoute] int episodeID) + public ActionResult> GetEpisodeTvDBDetails([FromRoute, Range(1, int.MaxValue)] int episodeID) { - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); @@ -658,11 +638,8 @@ [FromRoute] int seriesID /// /// [HttpGet("{episodeID}/Images")] - public ActionResult GetSeriesImages([FromRoute] int episodeID, [FromQuery] bool includeDisabled) + public ActionResult GetSeriesImages([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromQuery] bool includeDisabled) { - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); @@ -688,15 +665,12 @@ public ActionResult GetSeriesImages([FromRoute] int episodeID, [FromQuer /// Poster, Banner, Fanart /// [HttpGet("{episodeID}/Images/{imageType}")] - public ActionResult GetSeriesDefaultImageForType([FromRoute] int episodeID, + public ActionResult GetSeriesDefaultImageForType([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromRoute] Image.ImageType imageType) { if (!_allowedImageTypes.Contains(imageType)) return NotFound(); - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); @@ -733,15 +707,12 @@ public ActionResult GetSeriesDefaultImageForType([FromRoute] int episodeI /// The body containing the source and id used to set. /// [HttpPut("{episodeID}/Images/{imageType}")] - public ActionResult SetSeriesDefaultImageForType([FromRoute] int episodeID, + public ActionResult SetSeriesDefaultImageForType([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromRoute] Image.ImageType imageType, [FromBody] Image.Input.DefaultImageBody body) { if (!_allowedImageTypes.Contains(imageType)) return NotFound(); - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); @@ -779,14 +750,11 @@ public ActionResult SetSeriesDefaultImageForType([FromRoute] int episodeI /// Poster, Banner, Fanart /// [HttpDelete("{episodeID}/Images/{imageType}")] - public ActionResult DeleteSeriesDefaultImageForType([FromRoute] int episodeID, [FromRoute] Image.ImageType imageType) + public ActionResult DeleteSeriesDefaultImageForType([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromRoute] Image.ImageType imageType) { if (!_allowedImageTypes.Contains(imageType)) return NotFound(); - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); @@ -822,11 +790,8 @@ public ActionResult DeleteSeriesDefaultImageForType([FromRoute] int episodeID, [ /// /// [HttpPost("{episodeID}/Watched/{watched}")] - public async Task SetWatchedStatusOnEpisode([FromRoute] int episodeID, [FromRoute] bool watched) + public async Task SetWatchedStatusOnEpisode([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromRoute] bool watched) { - if (episodeID == 0) - return BadRequest(EpisodeWithZeroID); - var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); if (episode == null) return NotFound(EpisodeNotFoundWithEpisodeID); diff --git a/Shoko.Server/API/v3/Controllers/FilterController.cs b/Shoko.Server/API/v3/Controllers/FilterController.cs index 0adb83b92..11b19caea 100644 --- a/Shoko.Server/API/v3/Controllers/FilterController.cs +++ b/Shoko.Server/API/v3/Controllers/FilterController.cs @@ -213,7 +213,7 @@ public ActionResult AddNewFilter(Filter.Input.CreateOrUpdateFilterBody b /// Include conditions and sort criteria in the response. /// The filter [HttpGet("{filterID}")] - public ActionResult GetFilter([FromRoute] int filterID, [FromQuery] bool withConditions = false) + public ActionResult GetFilter([FromRoute, Range(1, int.MaxValue)] int filterID, [FromQuery] bool withConditions = false) { var filterPreset = RepoFactory.FilterPreset.GetByID(filterID); if (filterPreset == null) @@ -231,7 +231,7 @@ public ActionResult GetFilter([FromRoute] int filterID, [FromQuery] bool /// The updated filter. [Authorize("admin")] [HttpPatch("{filterID}")] - public ActionResult PatchFilter([FromRoute] int filterID, JsonPatchDocument document) + public ActionResult PatchFilter([FromRoute, Range(1, int.MaxValue)] int filterID, JsonPatchDocument document) { var filterPreset = RepoFactory.FilterPreset.GetByID(filterID); if (filterPreset == null) @@ -265,7 +265,7 @@ public ActionResult PatchFilter([FromRoute] int filterID, JsonPatchDocum /// The updated filter. [Authorize("admin")] [HttpPut("{filterID}")] - public ActionResult PutFilter([FromRoute] int filterID, Filter.Input.CreateOrUpdateFilterBody body) + public ActionResult PutFilter([FromRoute, Range(1, int.MaxValue)] int filterID, Filter.Input.CreateOrUpdateFilterBody body) { var filterPreset = RepoFactory.FilterPreset.GetByID(filterID); if (filterPreset == null) @@ -293,7 +293,7 @@ public ActionResult PutFilter([FromRoute] int filterID, Filter.Input.Cre /// Void. [Authorize("admin")] [HttpDelete("{filterID}")] - public ActionResult DeleteFilter(int filterID) + public ActionResult DeleteFilter([FromRoute, Range(1, int.MaxValue)] int filterID) { var filterPreset = RepoFactory.FilterPreset.GetByID(filterID); if (filterPreset == null) @@ -433,14 +433,13 @@ public ActionResult> GetPreviewSeriesInFilteredGroup([FromBod /// Include with missing s in the search. /// [HttpPost("Preview/Group/{groupID}/Group")] - public ActionResult> GetPreviewFilteredSubGroups([FromBody] Filter.Input.CreateOrUpdateFilterBody filter, [FromRoute] int groupID, + public ActionResult> GetPreviewFilteredSubGroups([FromBody] Filter.Input.CreateOrUpdateFilterBody filter, [FromRoute, Range(1, int.MaxValue)] int groupID, [FromQuery] bool randomImages = false, [FromQuery] bool includeEmpty = false) { var filterPreset = _factory.GetFilterPreset(filter, ModelState); if (!ModelState.IsValid) return ValidationProblem(ModelState); // Check if the group exists. - if (groupID == 0) return BadRequest(GroupController.GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) return NotFound(GroupController.GroupNotFound); @@ -493,7 +492,7 @@ public ActionResult> GetPreviewFilteredSubGroups([FromBody] Filter.I /// Include data from selected s. /// /// [HttpPost("Preview/Group/{groupID}/Series")] - public ActionResult> GetPreviewSeriesInFilteredGroup([FromBody] Filter.Input.CreateOrUpdateFilterBody filter, [FromRoute] int groupID, + public ActionResult> GetPreviewSeriesInFilteredGroup([FromBody] Filter.Input.CreateOrUpdateFilterBody filter, [FromRoute, Range(1, int.MaxValue)] int groupID, [FromQuery] bool recursive = false, [FromQuery] bool includeMissing = false, [FromQuery] bool randomImages = false, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null) { @@ -501,7 +500,6 @@ public ActionResult> GetPreviewSeriesInFilteredGroup([FromBody] Fil if (!ModelState.IsValid) return ValidationProblem(ModelState); // Check if the group exists. - if (groupID == 0) return BadRequest(GroupController.GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) return NotFound(GroupController.GroupNotFound); diff --git a/Shoko.Server/API/v3/Controllers/GroupController.cs b/Shoko.Server/API/v3/Controllers/GroupController.cs index 9fa2d2c7b..cc8142668 100644 --- a/Shoko.Server/API/v3/Controllers/GroupController.cs +++ b/Shoko.Server/API/v3/Controllers/GroupController.cs @@ -30,8 +30,6 @@ public class GroupController : BaseController #region Return messages - internal const string GroupWithZeroID = "GroupID must be greater than 0"; - internal const string GroupNotFound = "No Group entry for the given groupID"; internal const string GroupForbiddenForUser = "Accessing Group is not allowed for the current user"; @@ -162,9 +160,8 @@ public ActionResult CreateGroup([FromBody] Group.Input.CreateOrUpdateGrou /// /// [HttpGet("{groupID}")] - public ActionResult GetGroup([FromRoute] int groupID) + public ActionResult GetGroup([FromRoute, Range(1, int.MaxValue)] int groupID) { - if (groupID == 0) return BadRequest(GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) { @@ -190,9 +187,8 @@ public ActionResult GetGroup([FromRoute] int groupID) /// The new details for the group. /// The updated group. [HttpPut("{groupID}")] - public ActionResult PutGroup([FromRoute] int groupID, [FromBody] Group.Input.CreateOrUpdateGroupBody body) + public ActionResult PutGroup([FromRoute, Range(1, int.MaxValue)] int groupID, [FromBody] Group.Input.CreateOrUpdateGroupBody body) { - if (groupID == 0) return BadRequest(GroupWithZeroID); var animeGroup = RepoFactory.AnimeGroup.GetByID(groupID); if (animeGroup == null) { @@ -226,9 +222,8 @@ public ActionResult PutGroup([FromRoute] int groupID, [FromBody] Group.In /// The JSON Patch document containing the changes to be applied to the group. /// The updated group. [HttpPatch("{groupID}")] - public ActionResult PatchGroup([FromRoute] int groupID, [FromBody] JsonPatchDocument patchDocument) + public ActionResult PatchGroup([FromRoute, Range(1, int.MaxValue)] int groupID, [FromBody] JsonPatchDocument patchDocument) { - if (groupID == 0) return BadRequest(GroupWithZeroID); var animeGroup = RepoFactory.AnimeGroup.GetByID(groupID); if (animeGroup == null) { @@ -268,10 +263,9 @@ public ActionResult PatchGroup([FromRoute] int groupID, [FromBody] JsonPa /// Show relations for all series within the group, even for series within sub-groups. /// [HttpGet("{groupID}/Relations")] - public ActionResult> GetShokoRelationsBySeriesID([FromRoute] int groupID, + public ActionResult> GetShokoRelationsBySeriesID([FromRoute, Range(1, int.MaxValue)] int groupID, [FromQuery] bool recursive = false) { - if (groupID == 0) return BadRequest(GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) { @@ -315,9 +309,8 @@ public ActionResult> GetShokoRelationsBySeriesID([FromRoute /// [Authorize("admin")] [HttpDelete("{groupID}")] - public async Task DeleteGroup(int groupID, bool deleteSeries = false, bool deleteFiles = false) + public async Task DeleteGroup([FromRoute, Range(1, int.MaxValue)] int groupID, bool deleteSeries = false, bool deleteFiles = false) { - if (groupID == 0) return BadRequest(GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) { @@ -351,9 +344,8 @@ public async Task DeleteGroup(int groupID, bool deleteSeries = fal /// /// [HttpPost("{groupID}/Recalculate")] - public async Task RecalculateStats(int groupID) + public async Task RecalculateStats([FromRoute, Range(1, int.MaxValue)] int groupID) { - if (groupID == 0) return BadRequest(GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) { diff --git a/Shoko.Server/API/v3/Controllers/ImageController.cs b/Shoko.Server/API/v3/Controllers/ImageController.cs index 5edcab1aa..8f9d076c6 100644 --- a/Shoko.Server/API/v3/Controllers/ImageController.cs +++ b/Shoko.Server/API/v3/Controllers/ImageController.cs @@ -1,3 +1,4 @@ +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -29,12 +30,12 @@ public class ImageController : BaseController [ProducesResponseType(typeof(FileStreamResult), 200)] [ProducesResponseType(404)] public ActionResult GetImage([FromRoute] Image.ImageSource source, [FromRoute] Image.ImageType type, - [FromRoute] int value) + [FromRoute, Range(1, int.MaxValue)] int value) { // Unrecognized combination of source, type and/or value. var dataSource = source.ToServer(); var imageEntityType = type.ToServer(); - if (imageEntityType == ImageEntityType.None || dataSource == DataSourceType.None || value <= 0) + if (imageEntityType == ImageEntityType.None || dataSource == DataSourceType.None) return NotFound(ImageNotFound); // User avatars are stored in the database. @@ -65,12 +66,12 @@ public ActionResult GetImage([FromRoute] Image.ImageSource source, [FromRoute] I /// [Authorize("admin")] [HttpPost("{source}/{type}/{value}/Enabled")] - public ActionResult EnableOrDisableImage([FromRoute] Image.ImageSource source, [FromRoute] Image.ImageType type, [FromRoute] int value, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Image.Input.EnableImageBody body) + public ActionResult EnableOrDisableImage([FromRoute] Image.ImageSource source, [FromRoute] Image.ImageType type, [FromRoute, Range(1, int.MaxValue)] int value, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Image.Input.EnableImageBody body) { // Unrecognized combination of source, type and/or value. var dataSource = source.ToServer(); var imageEntityType = type.ToServer(); - if (imageEntityType == ImageEntityType.None || dataSource == DataSourceType.None || value <= 0) + if (imageEntityType == ImageEntityType.None || dataSource == DataSourceType.None) return NotFound(ImageNotFound); // User avatars are stored in the database. diff --git a/Shoko.Server/API/v3/Controllers/ImportFolderController.cs b/Shoko.Server/API/v3/Controllers/ImportFolderController.cs index ced0dd219..92b0ac21b 100644 --- a/Shoko.Server/API/v3/Controllers/ImportFolderController.cs +++ b/Shoko.Server/API/v3/Controllers/ImportFolderController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -75,9 +76,8 @@ public ActionResult AddImportFolder([FromBody] ImportFolder folder /// Import Folder ID /// [HttpGet("{folderID}")] - public ActionResult GetImportFolderByFolderID([FromRoute] int folderID) + public ActionResult GetImportFolderByFolderID([FromRoute, Range(1, int.MaxValue)] int folderID) { - if (folderID == 0) return BadRequest("ID must be greater than 0"); var folder = RepoFactory.ImportFolder.GetByID(folderID); if (folder == null) { @@ -95,7 +95,7 @@ public ActionResult GetImportFolderByFolderID([FromRoute] int fold /// [Authorize("admin")] [HttpPatch("{folderID}")] - public ActionResult PatchImportFolderByFolderID([FromRoute] int folderID, + public ActionResult PatchImportFolderByFolderID([FromRoute, Range(1, int.MaxValue)] int folderID, [FromBody] JsonPatchDocument patch) { if (patch == null) @@ -163,14 +163,9 @@ public ActionResult EditImportFolder([FromBody] ImportFolder folder) /// [Authorize("admin")] [HttpDelete("{folderID}")] - public async Task DeleteImportFolderByFolderID([FromRoute] int folderID, [FromQuery] bool removeRecords = true, + public async Task DeleteImportFolderByFolderID([FromRoute, Range(1, int.MaxValue)] int folderID, [FromQuery] bool removeRecords = true, [FromQuery] bool updateMyList = true) { - if (folderID == 0) - { - return NotFound("Folder not found."); - } - if (!removeRecords) { // These are annoying to clean up later, so do it now. We can easily recreate them. @@ -195,7 +190,7 @@ public async Task DeleteImportFolderByFolderID([FromRoute] int fol /// Import Folder ID /// [HttpGet("{folderID}/Scan")] - public async Task ScanImportFolderByFolderID([FromRoute] int folderID) + public async Task ScanImportFolderByFolderID([FromRoute, Range(1, int.MaxValue)] int folderID) { var folder = RepoFactory.ImportFolder.GetByID(folderID); if (folder == null) diff --git a/Shoko.Server/API/v3/Controllers/ReleaseManagementController.cs b/Shoko.Server/API/v3/Controllers/ReleaseManagementController.cs index e080209bc..acc7609d9 100644 --- a/Shoko.Server/API/v3/Controllers/ReleaseManagementController.cs +++ b/Shoko.Server/API/v3/Controllers/ReleaseManagementController.cs @@ -62,7 +62,7 @@ public class ReleaseManagementController : BaseController /// [HttpGet("Series/{seriesID}")] public ActionResult> GetEpisodesForSeries( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null, [FromQuery] bool includeFiles = true, [FromQuery] bool includeMediaInfo = true, @@ -72,7 +72,6 @@ public ActionResult> GetEpisodesForSeries( [FromQuery, Range(0, 1000)] int pageSize = 100, [FromQuery, Range(1, int.MaxValue)] int page = 1) { - if (seriesID == 0) return BadRequest(SeriesController.SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return new ListResult(); @@ -124,11 +123,10 @@ public ActionResult> GetEpisodes( /// [HttpGet("Series/{seriesID}/Episode/FilesToDelete")] public ActionResult> GetFileIdsWithPreference( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool ignoreVariations = true ) { - if (seriesID == 0) return BadRequest(SeriesController.SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return new List(); diff --git a/Shoko.Server/API/v3/Controllers/RenamerController.cs b/Shoko.Server/API/v3/Controllers/RenamerController.cs index 2e8af00ba..2240c67c0 100644 --- a/Shoko.Server/API/v3/Controllers/RenamerController.cs +++ b/Shoko.Server/API/v3/Controllers/RenamerController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Reflection; @@ -514,10 +515,8 @@ private IEnumerable GetNewLocationsForFiles(IEnumerable f /// A result object containing information about the relocation process. [Authorize("admin")] [HttpPost("Relocate/Location/{locationID}")] - public async Task> DirectlyRelocateFileLocation([FromRoute] int locationID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] RelocateArgs body) + public async Task> DirectlyRelocateFileLocation([FromRoute, Range(1, int.MaxValue)] int locationID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] RelocateArgs body) { - if (locationID <= 0) - return NotFound(FileController.FileLocationNotFoundWithLocationID); var fileLocation = _vlpRepository.GetByID(locationID); if (fileLocation == null) return NotFound(FileController.FileLocationNotFoundWithLocationID); diff --git a/Shoko.Server/API/v3/Controllers/ReverseTreeController.cs b/Shoko.Server/API/v3/Controllers/ReverseTreeController.cs index 72464b8f9..5f7d47d69 100644 --- a/Shoko.Server/API/v3/Controllers/ReverseTreeController.cs +++ b/Shoko.Server/API/v3/Controllers/ReverseTreeController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -38,7 +39,7 @@ public class ReverseTreeController : BaseController /// Always get the top-level /// [HttpGet("Filter/{filterID}/Parent")] - public ActionResult GetParentFromFilter([FromRoute] int filterID, [FromQuery] bool topLevel = false) + public ActionResult GetParentFromFilter([FromRoute, Range(1, int.MaxValue)] int filterID, [FromQuery] bool topLevel = false) { var filter = RepoFactory.FilterPreset.GetByID(filterID); if (filter == null) @@ -74,9 +75,8 @@ public ActionResult GetParentFromFilter([FromRoute] int filterID, [FromQ /// Always get the top-level /// [HttpGet("Group/{groupID}/Parent")] - public ActionResult GetParentFromGroup([FromRoute] int groupID, [FromQuery] bool topLevel = false) + public ActionResult GetParentFromGroup([FromRoute, Range(1, int.MaxValue)] int groupID, [FromQuery] bool topLevel = false) { - if (groupID == 0) return BadRequest(GroupController.GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) { @@ -114,9 +114,8 @@ public ActionResult GetParentFromGroup([FromRoute] int groupID, [FromQuer /// Always get the top-level /// [HttpGet("Series/{seriesID}/Group")] - public ActionResult GetGroupFromSeries([FromRoute] int seriesID, [FromQuery] bool topLevel = false) + public ActionResult GetGroupFromSeries([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool topLevel = false) { - if (seriesID == 0) return BadRequest(SeriesController.SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -145,7 +144,7 @@ public ActionResult GetGroupFromSeries([FromRoute] int seriesID, [FromQue /// Include data from selected s. /// [HttpGet("Episode/{episodeID}/Series")] - public ActionResult GetSeriesFromEpisode([FromRoute] int episodeID, [FromQuery] bool randomImages = false, + public ActionResult GetSeriesFromEpisode([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromQuery] bool randomImages = false, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null) { var episode = RepoFactory.AnimeEpisode.GetByID(episodeID); @@ -180,7 +179,7 @@ public ActionResult GetSeriesFromEpisode([FromRoute] int episodeID, [Fro /// [HttpGet("File/{fileID}/Episode")] public ActionResult> GetEpisodeFromFile( - [FromRoute] int fileID, + [FromRoute, Range(1, int.MaxValue)] int fileID, [FromQuery] bool includeFiles = false, [FromQuery] bool includeMediaInfo = false, [FromQuery] bool includeAbsolutePaths = false, diff --git a/Shoko.Server/API/v3/Controllers/SeriesController.cs b/Shoko.Server/API/v3/Controllers/SeriesController.cs index 2d86c8bc8..e02d3c9ca 100644 --- a/Shoko.Server/API/v3/Controllers/SeriesController.cs +++ b/Shoko.Server/API/v3/Controllers/SeriesController.cs @@ -77,8 +77,6 @@ public SeriesController(ISettingsProvider settingsProvider, AnimeSeriesService s #region Return messages - internal const string SeriesWithZeroID = "SeriesID must be greater than 0"; - internal const string SeriesNotFoundWithSeriesID = "No Series entry for the given seriesID"; internal const string SeriesNotFoundWithAnidbID = "No Series entry for the given anidbID"; @@ -142,10 +140,9 @@ public ActionResult> GetAllSeries( /// Include data from selected s. /// [HttpGet("{seriesID}")] - public ActionResult GetSeries([FromRoute] int seriesID, [FromQuery] bool randomImages = false, + public ActionResult GetSeries([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool randomImages = false, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -169,11 +166,8 @@ public ActionResult GetSeries([FromRoute] int seriesID, [FromQuery] bool /// [Authorize("admin")] [HttpDelete("{seriesID}")] - public async Task DeleteSeries([FromRoute] int seriesID, [FromQuery] bool deleteFiles = false, [FromQuery] bool completelyRemove = false) + public async Task DeleteSeries([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool deleteFiles = false, [FromQuery] bool completelyRemove = false) { - if (seriesID == 0) - return BadRequest(SeriesWithZeroID); - var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -191,11 +185,8 @@ public async Task DeleteSeries([FromRoute] int seriesID, [FromQuer /// [Authorize("admin")] [HttpPost("{seriesID}/OverrideTitle")] - public ActionResult OverrideSeriesTitle([FromRoute] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Series.Input.TitleOverrideBody body) + public ActionResult OverrideSeriesTitle([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Series.Input.TitleOverrideBody body) { - if (seriesID == 0) - return BadRequest(SeriesWithZeroID); - var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -222,9 +213,8 @@ public ActionResult OverrideSeriesTitle([FromRoute] int seriesID, [FromBody(Empt /// [Authorize("admin")] [HttpGet("{seriesID}/AutoMatchSettings")] - public ActionResult GetAutoMatchSettingsBySeriesID([FromRoute] int seriesID) + public ActionResult GetAutoMatchSettingsBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -244,9 +234,8 @@ public ActionResult OverrideSeriesTitle([FromRoute] int seriesID, [FromBody(Empt /// [Authorize("admin")] [HttpPatch("{seriesID}/AutoMatchSettings")] - public ActionResult PatchAutoMatchSettingsBySeriesID([FromRoute] int seriesID, [FromBody] JsonPatchDocument patchDocument) + public ActionResult PatchAutoMatchSettingsBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody] JsonPatchDocument patchDocument) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -273,9 +262,8 @@ public ActionResult OverrideSeriesTitle([FromRoute] int seriesID, [FromBody(Empt /// [Authorize("admin")] [HttpPut("{seriesID}/AutoMatchSettings")] - public ActionResult PutAutoMatchSettingsBySeriesID([FromRoute] int seriesID, [FromBody] Series.AutoMatchSettings autoMatchSettings) + public ActionResult PutAutoMatchSettingsBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody] Series.AutoMatchSettings autoMatchSettings) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -292,9 +280,8 @@ public ActionResult OverrideSeriesTitle([FromRoute] int seriesID, [FromBody(Empt /// Shoko ID /// [HttpGet("{seriesID}/Relations")] - public ActionResult> GetShokoRelationsBySeriesID([FromRoute] int seriesID) + public ActionResult> GetShokoRelationsBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -455,9 +442,8 @@ public ActionResult> GetAnidbRelations([FromQuery, Ra /// Shoko ID /// [HttpGet("{seriesID}/AniDB")] - public ActionResult GetSeriesAnidbBySeriesID([FromRoute] int seriesID) + public ActionResult GetSeriesAnidbBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -484,9 +470,8 @@ public ActionResult> GetAnidbRelations([FromQuery, Ra /// Shoko ID /// [HttpGet("{seriesID}/AniDB/Similar")] - public ActionResult> GetAnidbSimilarBySeriesID([FromRoute] int seriesID) + public ActionResult> GetAnidbSimilarBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -515,9 +500,8 @@ public ActionResult> GetAnidbRelations([FromQuery, Ra /// Shoko ID /// [HttpGet("{seriesID}/AniDB/Related")] - public ActionResult> GetAnidbRelatedBySeriesID([FromRoute] int seriesID) + public ActionResult> GetAnidbRelatedBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -546,9 +530,8 @@ public ActionResult> GetAnidbRelations([FromQuery, Ra /// Shoko ID /// [HttpGet("{seriesID}/AniDB/Relations")] - public ActionResult> GetAnidbRelationsBySeriesID([FromRoute] int seriesID) + public ActionResult> GetAnidbRelationsBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -891,11 +874,10 @@ public async Task> RefreshAniDBByAniDBID([FromRoute] int anid /// Only used data from the cache when performing the refresh. takes precedence over this option. /// True if the refresh is done, otherwise false if it was queued. [HttpPost("{seriesID}/AniDB/Refresh")] - public async Task> RefreshAniDBBySeriesID([FromRoute] int seriesID, [FromQuery] bool force = false, + public async Task> RefreshAniDBBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool force = false, [FromQuery] bool downloadRelations = false, [FromQuery] bool? createSeriesEntry = null, [FromQuery] bool immediate = false, [FromQuery] bool cacheOnly = false) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); if (!createSeriesEntry.HasValue) { var settings = SettingsProvider.GetSettings(); @@ -931,7 +913,7 @@ public async Task> RefreshAniDBBySeriesID([FromRoute] int ser /// True if the refresh is done, otherwise false if it failed. [HttpPost("{seriesID}/AniDB/Refresh/ForceFromXML")] [Obsolete("Use Refresh with cacheOnly set to true")] - public async Task> RefreshAniDBFromXML([FromRoute] int seriesID) + public async Task> RefreshAniDBFromXML([FromRoute, Range(1, int.MaxValue)] int seriesID) => await RefreshAniDBBySeriesID(seriesID, false, false, true, true, true); #endregion @@ -944,9 +926,8 @@ public async Task> RefreshAniDBFromXML([FromRoute] int series /// Shoko ID /// [HttpGet("{seriesID}/TvDB")] - public ActionResult> GetSeriesTvdb([FromRoute] int seriesID) + public ActionResult> GetSeriesTvdb([FromRoute, Range(1, int.MaxValue)] int seriesID) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -972,9 +953,8 @@ public async Task> RefreshAniDBFromXML([FromRoute] int series /// [Authorize("admin")] [HttpPost("{seriesID}/TvDB")] - public async Task LinkTvDB([FromRoute] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Series.Input.LinkCommonBody body) + public async Task LinkTvDB([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Series.Input.LinkCommonBody body) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(TvdbNotFoundForSeriesID); @@ -996,9 +976,8 @@ public async Task LinkTvDB([FromRoute] int seriesID, [FromBody(Emp /// [Authorize("admin")] [HttpDelete("{seriesID}/TvDB")] - public ActionResult UnlinkTvDB([FromRoute] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Series.Input.UnlinkCommonBody body) + public ActionResult UnlinkTvDB([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Series.Input.UnlinkCommonBody body) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(TvdbNotFoundForSeriesID); @@ -1024,9 +1003,8 @@ public ActionResult UnlinkTvDB([FromRoute] int seriesID, [FromBody(EmptyBodyBeha /// Forcefully retrieve updated data from TvDB /// [HttpPost("{seriesID}/TvDB/Refresh")] - public async Task RefreshSeriesTvdbBySeriesID([FromRoute] int seriesID, [FromQuery] bool force = false) + public async Task RefreshSeriesTvdbBySeriesID([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool force = false) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -1136,7 +1114,7 @@ public ActionResult> GetSeriesByTvdbID([FromRoute] int tvdbID) /// Void. [HttpGet("{seriesID}/TMDB/Action/AutoSearch")] public async Task>> PreviewAutoMatchTMDBMoviesBySeriesID( - [FromRoute] int seriesID + [FromRoute, Range(1, int.MaxValue)] int seriesID ) { var series = RepoFactory.AnimeSeries.GetByID(seriesID); @@ -1164,7 +1142,7 @@ [FromRoute] int seriesID /// Void. [HttpPost("{seriesID}/TMDB/Action/AutoSearch")] public async Task ScheduleAutoMatchTMDBMoviesBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool force = false ) { @@ -1191,7 +1169,7 @@ public async Task ScheduleAutoMatchTMDBMoviesBySeriesID( /// All TMDB Movies linked to the Shoko Series. [HttpGet("{seriesID}/TMDB/Movie")] public ActionResult> GetTMDBMoviesBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet include = null, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet language = null ) @@ -1219,7 +1197,7 @@ public ActionResult> GetTMDBMoviesBySeriesID( [Authorize("admin")] [HttpPost("{seriesID}/TMDB/Movie")] public async Task AddLinkToTMDBMoviesBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Series.Input.LinkMovieBody body ) { @@ -1254,7 +1232,7 @@ public async Task AddLinkToTMDBMoviesBySeriesID( [Authorize("admin")] [HttpDelete("{seriesID}/TMDB/Movie")] public async Task RemoveLinkToTMDBMoviesBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Series.Input.UnlinkCommonBody body ) { @@ -1285,7 +1263,7 @@ public async Task RemoveLinkToTMDBMoviesBySeriesID( [Authorize("admin")] [HttpPost("{seriesID}/TMDB/Movie/Action/Refresh")] public async Task RefreshTMDBMoviesBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool force = false, [FromQuery] bool downloadImages = true, [FromQuery] bool? downloadCrewAndCast = null, @@ -1312,7 +1290,7 @@ public async Task RefreshTMDBMoviesBySeriesID( /// All TMDB Movie cross-references for the Shoko Series. [HttpGet("{seriesID}/TMDB/Movie/CrossReferences")] public ActionResult> GetTMDBMovieCrossReferenceBySeriesID( - [FromRoute] int seriesID + [FromRoute, Range(1, int.MaxValue)] int seriesID ) { var series = RepoFactory.AnimeSeries.GetByID(seriesID); @@ -1341,7 +1319,7 @@ [FromRoute] int seriesID /// [HttpGet("{seriesID}/TMDB/Show")] public ActionResult> GetTMDBShowsBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet include = null, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet language = null ) @@ -1369,7 +1347,7 @@ public ActionResult> GetTMDBShowsBySeriesID( [Authorize("admin")] [HttpPost("{seriesID}/TMDB/Show")] public async Task AddLinkToTMDBShowsBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Series.Input.LinkShowBody body ) { @@ -1404,7 +1382,7 @@ public async Task AddLinkToTMDBShowsBySeriesID( [Authorize("admin")] [HttpDelete("{seriesID}/TMDB/Show")] public async Task RemoveLinkToTMDBShowsBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Series.Input.UnlinkCommonBody body ) { @@ -1435,7 +1413,7 @@ public async Task RemoveLinkToTMDBShowsBySeriesID( [Authorize("admin")] [HttpPost("{seriesID}/TMDB/Show/Action/Refresh")] public async Task RefreshTMDBShowsBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool force = false, [FromQuery] bool downloadImages = true, [FromQuery] bool? downloadCrewAndCast = null, @@ -1462,7 +1440,7 @@ public async Task RefreshTMDBShowsBySeriesID( /// All TMDB Show cross-references for the Shoko Series. [HttpGet("{seriesID}/TMDB/Show/CrossReferences")] public ActionResult> GetTMDBShowCrossReferenceBySeriesID( - [FromRoute] int seriesID + [FromRoute, Range(1, int.MaxValue)] int seriesID ) { var series = RepoFactory.AnimeSeries.GetByID(seriesID); @@ -1491,7 +1469,7 @@ [FromRoute] int seriesID /// A list of TMDB episode cross-references as part of the preview result, based on the provided filtering and pagination settings. [HttpGet("{seriesID}/TMDB/Show/CrossReferences/Episode")] public ActionResult> GetTMDBEpisodeMappingsBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] int? tmdbShowID, [FromQuery, Range(0, 1000)] int pageSize = 50, [FromQuery, Range(1, int.MaxValue)] int page = 1 @@ -1527,7 +1505,7 @@ [FromRoute] int seriesID [Authorize("admin")] [HttpPost("{seriesID}/TMDB/Show/CrossReferences/Episode")] public async Task OverrideTMDBEpisodeMappingsBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Series.Input.OverrideEpisodeMappingBody body ) { @@ -1607,7 +1585,7 @@ public async Task OverrideTMDBEpisodeMappingsBySeriesID( [Authorize("admin")] [HttpGet("{seriesID}/TMDB/Show/CrossReferences/Episode/Auto")] public ActionResult> PreviewAutoTMDBEpisodeMappingsBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] int? tmdbShowID, [FromQuery] int? tmdbSeasonID, [FromQuery] bool keepExisting = true, @@ -1661,7 +1639,7 @@ public async Task OverrideTMDBEpisodeMappingsBySeriesID( [Authorize("admin")] [HttpPost("{seriesID}/TMDB/Show/CrossReferences/Episode/Auto")] public async Task AutoTMDBEpisodeMappingsBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] int? tmdbShowID, [FromQuery] int? tmdbSeasonID, [FromQuery] bool keepExisting = true @@ -1720,7 +1698,7 @@ public async Task AutoTMDBEpisodeMappingsBySeriesID( [Authorize("admin")] [HttpDelete("{seriesID}/TMDB/Show/CrossReferences/Episode")] public ActionResult RemoveTMDBEpisodeMappingsBySeriesID( - [FromRoute] int seriesID + [FromRoute, Range(1, int.MaxValue)] int seriesID ) { var series = RepoFactory.AnimeSeries.GetByID(seriesID); @@ -1750,7 +1728,7 @@ [FromRoute] int seriesID /// All TMDB Seasons indirectly linked to the Shoko Series. [HttpGet("{seriesID}/TMDB/Season")] public ActionResult> GetTMDBSeasonsBySeriesID( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet include = null, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet language = null ) @@ -1805,7 +1783,7 @@ public ActionResult> GetTMDBSeasonsBySeriesID( /// A list of episodes based on the specified filters. [HttpGet("{seriesID}/Episode")] public ActionResult> GetEpisodes( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery, Range(0, 1000)] int pageSize = 20, [FromQuery, Range(1, int.MaxValue)] int page = 1, [FromQuery] IncludeOnlyFilter includeMissing = IncludeOnlyFilter.False, @@ -1821,7 +1799,6 @@ public ActionResult> GetEpisodes( [FromQuery] bool fuzzy = true ) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -1847,7 +1824,7 @@ public ActionResult> GetEpisodes( /// [HttpPost("{seriesID}/Episode/Watched")] public async Task MarkSeriesWatched( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool value = true, [FromQuery] IncludeOnlyFilter includeMissing = IncludeOnlyFilter.False, [FromQuery] IncludeOnlyFilter includeHidden = IncludeOnlyFilter.False, @@ -1856,7 +1833,6 @@ public async Task MarkSeriesWatched( [FromQuery] string search = null, [FromQuery] bool fuzzy = true) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -2110,7 +2086,7 @@ public ParallelQuery GetEpisodesInternal( /// Include data from selected s. /// [HttpGet("{seriesID}/NextUpEpisode")] - public ActionResult GetNextUnwatchedEpisode([FromRoute] int seriesID, + public ActionResult GetNextUnwatchedEpisode([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool onlyUnwatched = true, [FromQuery] bool includeSpecials = true, [FromQuery] bool includeMissing = true, @@ -2123,7 +2099,6 @@ public ActionResult GetNextUnwatchedEpisode([FromRoute] int seriesID, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null) { var user = User; - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -2156,9 +2131,8 @@ public ActionResult GetNextUnwatchedEpisode([FromRoute] int seriesID, /// [Authorize("admin")] [HttpPost("{seriesID}/File/Rescan")] - public async Task RescanSeriesFiles([FromRoute] int seriesID, [FromQuery] bool priority = false) + public async Task RescanSeriesFiles([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool priority = false) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -2195,9 +2169,8 @@ await scheduler.StartJob(c => /// [Authorize("admin")] [HttpPost("{seriesID}/File/Rehash")] - public async Task RehashSeriesFiles([FromRoute] int seriesID) + public async Task RehashSeriesFiles([FromRoute, Range(1, int.MaxValue)] int seriesID) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -2234,9 +2207,8 @@ await scheduler.StartJobNow(c => /// /// [HttpPost("{seriesID}/Vote")] - public async Task PostSeriesUserVote([FromRoute] int seriesID, [FromBody] Vote vote) + public async Task PostSeriesUserVote([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody] Vote vote) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -2277,9 +2249,8 @@ public async Task PostSeriesUserVote([FromRoute] int seriesID, [Fr /// /// [HttpGet("{seriesID}/Images")] - public ActionResult GetSeriesImages([FromRoute] int seriesID, [FromQuery] bool includeDisabled) + public ActionResult GetSeriesImages([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool includeDisabled) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -2305,13 +2276,12 @@ public ActionResult GetSeriesImages([FromRoute] int seriesID, [FromQuery /// Poster, Banner, Fanart /// [HttpGet("{seriesID}/Images/{imageType}")] - public ActionResult GetSeriesDefaultImageForType([FromRoute] int seriesID, + public ActionResult GetSeriesDefaultImageForType([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromRoute] Image.ImageType imageType) { if (!_allowedImageTypes.Contains(imageType)) return NotFound(); - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -2344,13 +2314,12 @@ public ActionResult GetSeriesDefaultImageForType([FromRoute] int seriesID /// The body containing the source and id used to set. /// [HttpPut("{seriesID}/Images/{imageType}")] - public ActionResult SetSeriesDefaultImageForType([FromRoute] int seriesID, + public ActionResult SetSeriesDefaultImageForType([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromRoute] Image.ImageType imageType, [FromBody] Image.Input.DefaultImageBody body) { if (!_allowedImageTypes.Contains(imageType)) return NotFound(); - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -2387,13 +2356,12 @@ public ActionResult SetSeriesDefaultImageForType([FromRoute] int seriesID /// Poster, Banner, Fanart /// [HttpDelete("{seriesID}/Images/{imageType}")] - public ActionResult DeleteSeriesDefaultImageForType([FromRoute] int seriesID, [FromRoute] Image.ImageType imageType) + public ActionResult DeleteSeriesDefaultImageForType([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromRoute] Image.ImageType imageType) { if (!_allowedImageTypes.Contains(imageType)) return NotFound(); // Check if the series exists and if the user can access the series. - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); @@ -2433,11 +2401,10 @@ public ActionResult DeleteSeriesDefaultImageForType([FromRoute] int seriesID, [F /// Only show verified tags. /// [HttpGet("{seriesID}/Tags")] - public ActionResult> GetSeriesTags([FromRoute] int seriesID, [FromQuery] TagFilter.Filter filter = 0, + public ActionResult> GetSeriesTags([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] TagFilter.Filter filter = 0, [FromQuery] bool excludeDescriptions = false, [FromQuery] bool orderByName = false, [FromQuery] bool onlyVerified = true) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -2466,7 +2433,7 @@ public ActionResult> GetSeriesTags([FromRoute] int seriesID, [FromQuer /// [HttpGet("{seriesID}/Tags/User")] public ActionResult> GetSeriesUserTags( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery] bool excludeDescriptions = false) => GetSeriesTags(seriesID, TagFilter.Filter.User | TagFilter.Filter.Invert, excludeDescriptions, true, true); @@ -2479,11 +2446,10 @@ public ActionResult> GetSeriesUserTags( [HttpPost("{seriesID}/Tags/User")] [Authorize("admin")] public ActionResult AddSeriesUserTags( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Series.Input.AddOrRemoveUserTagsBody body ) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -2522,11 +2488,10 @@ public ActionResult AddSeriesUserTags( [HttpDelete("{seriesID}/Tags/User")] [Authorize("admin")] public ActionResult RemoveSeriesUserTags( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Series.Input.AddOrRemoveUserTagsBody body ) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -2559,10 +2524,9 @@ public ActionResult RemoveSeriesUserTags( /// [HttpGet("{seriesID}/Tags/{filter}")] [Obsolete("Use Tags with query parameter instead.")] - public ActionResult> GetSeriesTagsFromPath([FromRoute] int seriesID, [FromRoute] TagFilter.Filter filter, + public ActionResult> GetSeriesTagsFromPath([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromRoute] TagFilter.Filter filter, [FromQuery] bool excludeDescriptions = false) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); return GetSeriesTags(seriesID, filter, excludeDescriptions); } @@ -2577,10 +2541,9 @@ public ActionResult> GetSeriesTagsFromPath([FromRoute] int seriesID, [ /// Filter by role type /// [HttpGet("{seriesID}/Cast")] - public ActionResult> GetSeriesCast([FromRoute] int seriesID, + public ActionResult> GetSeriesCast([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet roleType = null) { - if (seriesID == 0) return BadRequest(SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) { @@ -2607,14 +2570,8 @@ public ActionResult> GetSeriesCast([FromRoute] int seriesID, /// [Authorize("admin")] [HttpPatch("{seriesID}/Move/{groupID}")] - public ActionResult MoveSeries([FromRoute] int seriesID, [FromRoute] int groupID) + public ActionResult MoveSeries([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromRoute, Range(1, int.MaxValue)] int groupID) { - if (seriesID == 0) - return BadRequest(SeriesWithZeroID); - - if (groupID == 0) - return BadRequest(GroupController.GroupWithZeroID); - var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) return NotFound(SeriesNotFoundWithSeriesID); diff --git a/Shoko.Server/API/v3/Controllers/TagController.cs b/Shoko.Server/API/v3/Controllers/TagController.cs index 9b417a5ae..41a1071c1 100644 --- a/Shoko.Server/API/v3/Controllers/TagController.cs +++ b/Shoko.Server/API/v3/Controllers/TagController.cs @@ -119,9 +119,9 @@ public ActionResult AddUserTag([FromBody(EmptyBodyBehavior = EmptyBodyBehav /// Exclude tag description from response. /// [HttpGet("User/{tagID}")] - public ActionResult GetUserTag([FromRoute] int tagID, [FromQuery] bool excludeDescription = false) + public ActionResult GetUserTag([FromRoute, Range(1, int.MaxValue)] int tagID, [FromQuery] bool excludeDescription = false) { - var tag = tagID <= 0 ? null : RepoFactory.CustomTag.GetByID(tagID); + var tag = RepoFactory.CustomTag.GetByID(tagID); if (tag == null) return NotFound("No User Tag entry for the given tagID"); @@ -136,9 +136,9 @@ public ActionResult GetUserTag([FromRoute] int tagID, [FromQuery] bool excl /// The updated user tag, or an error action result. [HttpPut("User/{tagID}")] [Authorize("admin")] - public ActionResult EditUserTag([FromRoute] int tagID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Tag.Input.CreateOrUpdateCustomTagBody body) + public ActionResult EditUserTag([FromRoute, Range(1, int.MaxValue)] int tagID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] Tag.Input.CreateOrUpdateCustomTagBody body) { - var tag = tagID <= 0 ? null : RepoFactory.CustomTag.GetByID(tagID); + var tag = RepoFactory.CustomTag.GetByID(tagID); if (tag == null) return NotFound("No User Tag entry for the given tagID"); @@ -158,9 +158,9 @@ public ActionResult EditUserTag([FromRoute] int tagID, [FromBody(EmptyBodyB /// The updated user tag, or an error action result. [HttpPatch("User/{tagID}")] [Authorize("admin")] - public ActionResult PatchUserTag([FromRoute] int tagID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] JsonPatchDocument patchDocument) + public ActionResult PatchUserTag([FromRoute, Range(1, int.MaxValue)] int tagID, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] JsonPatchDocument patchDocument) { - var tag = tagID <= 0 ? null : RepoFactory.CustomTag.GetByID(tagID); + var tag = RepoFactory.CustomTag.GetByID(tagID); if (tag == null) return NotFound("No User Tag entry for the given tagID"); var body = new Tag.Input.CreateOrUpdateCustomTagBody(); @@ -182,9 +182,9 @@ public ActionResult PatchUserTag([FromRoute] int tagID, [FromBody(EmptyBody /// No content or an error action result. [HttpDelete("User/{tagID}")] [Authorize("admin")] - public ActionResult RemoveUserTag([FromRoute] int tagID) + public ActionResult RemoveUserTag([FromRoute, Range(1, int.MaxValue)] int tagID) { - var tag = tagID <= 0 ? null : RepoFactory.CustomTag.GetByID(tagID); + var tag = RepoFactory.CustomTag.GetByID(tagID); if (tag == null) return NotFound("No User Tag entry for the given tagID"); diff --git a/Shoko.Server/API/v3/Controllers/TreeController.cs b/Shoko.Server/API/v3/Controllers/TreeController.cs index 63a99f5ff..3c906dc45 100644 --- a/Shoko.Server/API/v3/Controllers/TreeController.cs +++ b/Shoko.Server/API/v3/Controllers/TreeController.cs @@ -43,7 +43,7 @@ public class TreeController : BaseController /// Include data from selected s. /// [HttpGet("ImportFolder/{folderID}/File")] - public ActionResult> GetFilesInImportFolder([FromRoute] int folderID, + public ActionResult> GetFilesInImportFolder([FromRoute, Range(1, int.MaxValue)] int folderID, [FromQuery, Range(0, 10000)] int pageSize = 200, [FromQuery, Range(1, int.MaxValue)] int page = 1, [FromQuery] string folderPath = null, @@ -52,7 +52,7 @@ public ActionResult> GetFilesInImportFolder([FromRoute] int fol { include ??= []; - var importFolder = folderID > 0 ? RepoFactory.ImportFolder.GetByID(folderID) : null; + var importFolder = RepoFactory.ImportFolder.GetByID(folderID); if (importFolder == null) return NotFound("Import folder not found: " + folderID); @@ -104,7 +104,7 @@ public ActionResult> GetFilesInImportFolder([FromRoute] int fol /// Show hidden filters /// [HttpGet("Filter/{filterID}/Filter")] - public ActionResult> GetSubFilters([FromRoute] int filterID, + public ActionResult> GetSubFilters([FromRoute, Range(1, int.MaxValue)] int filterID, [FromQuery, Range(0, 100)] int pageSize = 50, [FromQuery, Range(1, int.MaxValue)] int page = 1, [FromQuery] bool showHidden = false) { @@ -137,7 +137,7 @@ public ActionResult> GetSubFilters([FromRoute] int filterID, /// Ignore the group filter sort criteria and always order the returned list by name. /// [HttpGet("Filter/{filterID}/Group")] - public ActionResult> GetFilteredGroups([FromRoute] int filterID, + public ActionResult> GetFilteredGroups([FromRoute, Range(0, int.MaxValue)] int filterID, [FromQuery, Range(0, 100)] int pageSize = 50, [FromQuery, Range(1, int.MaxValue)] int page = 1, [FromQuery] bool includeEmpty = false, [FromQuery] bool randomImages = false, [FromQuery] bool orderByName = false) { @@ -208,12 +208,12 @@ public ActionResult> GetFilteredGroups([FromRoute] int filterI /// s in the count. /// [HttpGet("Filter/{filterID}/Group/Letters")] - public ActionResult> GetGroupNameLettersInFilter([FromRoute] int? filterID = null, [FromQuery] bool includeEmpty = false) + public ActionResult> GetGroupNameLettersInFilter([FromRoute, Range(0, int.MaxValue)] int filterID, [FromQuery] bool includeEmpty = false) { var user = User; - if (filterID.HasValue && filterID > 0) + if (filterID > 0) { - var filterPreset = RepoFactory.FilterPreset.GetByID(filterID.Value); + var filterPreset = RepoFactory.FilterPreset.GetByID(filterID); if (filterPreset == null) return NotFound(FilterController.FilterNotFound); @@ -275,7 +275,7 @@ public ActionResult> GetGroupNameLettersInFilter([FromRout /// s in the count. /// [HttpGet("Filter/{filterID}/Series")] - public ActionResult> GetSeriesInFilteredGroup([FromRoute] int filterID, + public ActionResult> GetSeriesInFilteredGroup([FromRoute, Range(0, int.MaxValue)] int filterID, [FromQuery, Range(0, 100)] int pageSize = 50, [FromQuery, Range(1, int.MaxValue)] int page = 1, [FromQuery] bool randomImages = false, [FromQuery] bool includeMissing = false) { @@ -320,7 +320,7 @@ public ActionResult> GetSeriesInFilteredGroup([FromRoute] int /// Include with missing s in the search. /// [HttpGet("Filter/{filterID}/Group/{groupID}/Group")] - public ActionResult> GetFilteredSubGroups([FromRoute] int filterID, [FromRoute] int groupID, + public ActionResult> GetFilteredSubGroups([FromRoute, Range(0, int.MaxValue)] int filterID, [FromRoute, Range(1, int.MaxValue)] int groupID, [FromQuery] bool randomImages = false, [FromQuery] bool includeEmpty = false) { // Return sub-groups with no group filter applied. @@ -332,7 +332,6 @@ public ActionResult> GetFilteredSubGroups([FromRoute] int filterID, return NotFound(FilterController.FilterNotFound); // Check if the group exists. - if (groupID == 0) return BadRequest(GroupController.GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) return NotFound(GroupController.GroupNotFound); @@ -392,11 +391,10 @@ public ActionResult> GetFilteredSubGroups([FromRoute] int filterID, /// Include data from selected s. /// /// [HttpGet("Filter/{filterID}/Group/{groupID}/Series")] - public ActionResult> GetSeriesInFilteredGroup([FromRoute] int filterID, [FromRoute] int groupID, + public ActionResult> GetSeriesInFilteredGroup([FromRoute, Range(0, int.MaxValue)] int filterID, [FromRoute, Range(1, int.MaxValue)] int groupID, [FromQuery] bool recursive = false, [FromQuery] bool includeMissing = false, [FromQuery] bool randomImages = false, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null) { - if (groupID == 0) return BadRequest(GroupController.GroupWithZeroID); // Return the groups with no group filter applied. if (filterID == 0) return GetSeriesInGroup(groupID, recursive, includeMissing, randomImages, includeDataFrom); @@ -451,11 +449,10 @@ public ActionResult> GetSeriesInFilteredGroup([FromRoute] int filte /// Include with missing s in the search. /// [HttpGet("Group/{groupID}/Group")] - public ActionResult> GetSubGroups([FromRoute] int groupID, [FromQuery] bool randomImages = false, + public ActionResult> GetSubGroups([FromRoute, Range(1, int.MaxValue)] int groupID, [FromQuery] bool randomImages = false, [FromQuery] bool includeEmpty = false) { // Check if the group exists. - if (groupID == 0) return BadRequest(GroupController.GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) return NotFound(GroupController.GroupNotFound); @@ -495,12 +492,11 @@ public ActionResult> GetSubGroups([FromRoute] int groupID, [FromQuer /// Include data from selected s. /// [HttpGet("Group/{groupID}/Series")] - public ActionResult> GetSeriesInGroup([FromRoute] int groupID, [FromQuery] bool recursive = false, + public ActionResult> GetSeriesInGroup([FromRoute, Range(1, int.MaxValue)] int groupID, [FromQuery] bool recursive = false, [FromQuery] bool includeMissing = false, [FromQuery] bool randomImages = false, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null) { // Check if the group exists. - if (groupID == 0) return BadRequest(GroupController.GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) return NotFound(GroupController.GroupNotFound); @@ -527,11 +523,10 @@ public ActionResult> GetSeriesInGroup([FromRoute] int groupID, [Fro /// Include data from selected s. /// [HttpGet("Group/{groupID}/MainSeries")] - public ActionResult GetMainSeriesInGroup([FromRoute] int groupID, [FromQuery] bool randomImages = false, + public ActionResult GetMainSeriesInGroup([FromRoute, Range(1, int.MaxValue)] int groupID, [FromQuery] bool randomImages = false, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null) { // Check if the group exists. - if (groupID == 0) return BadRequest(GroupController.GroupWithZeroID); var group = RepoFactory.AnimeGroup.GetByID(groupID); if (group == null) return NotFound(GroupController.GroupNotFound); @@ -563,7 +558,7 @@ public ActionResult GetMainSeriesInGroup([FromRoute] int groupID, [FromQ /// Include data from selected s. /// [HttpGet("Series/{seriesID}/File")] - public ActionResult> GetFilesForSeries([FromRoute] int seriesID, + public ActionResult> GetFilesForSeries([FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery, Range(0, 1000)] int pageSize = 100, [FromQuery, Range(1, int.MaxValue)] int page = 1, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] FileNonDefaultIncludeType[] include = default, @@ -572,7 +567,6 @@ public ActionResult> GetFilesForSeries([FromRoute] int seriesID [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] List sortOrder = null, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet includeDataFrom = null) { - if (seriesID == 0) return BadRequest(SeriesController.SeriesWithZeroID); var user = User; var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series == null) @@ -599,7 +593,7 @@ public ActionResult> GetFilesForSeries([FromRoute] int seriesID /// Include data from selected s. /// [HttpGet("Episode/{episodeID}/File")] - public ActionResult> GetFilesForEpisode([FromRoute] int episodeID, + public ActionResult> GetFilesForEpisode([FromRoute, Range(1, int.MaxValue)] int episodeID, [FromQuery, Range(0, 1000)] int pageSize = 100, [FromQuery, Range(1, int.MaxValue)] int page = 1, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] FileNonDefaultIncludeType[] include = default, diff --git a/Shoko.Server/API/v3/Controllers/UserController.cs b/Shoko.Server/API/v3/Controllers/UserController.cs index b37ee930c..a406ea232 100644 --- a/Shoko.Server/API/v3/Controllers/UserController.cs +++ b/Shoko.Server/API/v3/Controllers/UserController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.JsonPatch; @@ -116,7 +117,7 @@ public ActionResult ChangePasswordForCurrentUser([FromBody] User.Input.ChangePas /// The user. [Authorize("admin")] [HttpGet("{userID}")] - public ActionResult GetUserByUserID([FromRoute] int userID) + public ActionResult GetUserByUserID([FromRoute, Range(1, int.MaxValue)] int userID) { var user = RepoFactory.JMMUser.GetByID(userID); if (user == null) @@ -136,7 +137,7 @@ public ActionResult GetUserByUserID([FromRoute] int userID) /// The updated user. [Authorize("admin")] [HttpPatch("{userID}")] - public ActionResult PatchUserByUserID([FromRoute] int userID, [FromBody] JsonPatchDocument document) + public ActionResult PatchUserByUserID([FromRoute, Range(1, int.MaxValue)] int userID, [FromBody] JsonPatchDocument document) { var user = RepoFactory.JMMUser.GetByID(userID); if (user == null) @@ -166,7 +167,7 @@ public ActionResult PatchUserByUserID([FromRoute] int userID, [FromBody] J /// The updated user. [Authorize("admin")] [HttpPut("{userID}")] - public ActionResult PutUserByUserID([FromRoute] int userID, [FromBody] User.Input.CreateOrUpdateUserBody body) + public ActionResult PutUserByUserID([FromRoute, Range(1, int.MaxValue)] int userID, [FromBody] User.Input.CreateOrUpdateUserBody body) { var user = RepoFactory.JMMUser.GetByID(userID); if (user == null) @@ -189,7 +190,7 @@ public ActionResult PutUserByUserID([FromRoute] int userID, [FromBody] Use /// Void. [Authorize("admin")] [HttpDelete("{userID}")] - public ActionResult DeleteUser([FromRoute] int userID) + public ActionResult DeleteUser([FromRoute, Range(1, int.MaxValue)] int userID) { var user = RepoFactory.JMMUser.GetByID(userID); if (user == null) @@ -215,7 +216,7 @@ public ActionResult DeleteUser([FromRoute] int userID) /// [Authorize("admin")] [HttpPost("{userID}/ChangePassword")] - public ActionResult ChangePasswordForUserByUserID([FromRoute] int userID, [FromBody] User.Input.ChangePasswordBody body) => + public ActionResult ChangePasswordForUserByUserID([FromRoute, Range(1, int.MaxValue)] int userID, [FromBody] User.Input.ChangePasswordBody body) => ChangePassword(RepoFactory.JMMUser.GetByID(userID), body); [NonAction] diff --git a/Shoko.Server/API/v3/Controllers/WebUIController.cs b/Shoko.Server/API/v3/Controllers/WebUIController.cs index 012335da6..d272cbb59 100644 --- a/Shoko.Server/API/v3/Controllers/WebUIController.cs +++ b/Shoko.Server/API/v3/Controllers/WebUIController.cs @@ -254,10 +254,8 @@ public ActionResult> GetGroupView([FromBody] Input.WebUIGr /// The ID of the series to retrieve information for. /// A WebUISeriesExtra object containing extra information for the series. [HttpGet("Series/{seriesID}")] - public ActionResult GetSeries([FromRoute] int seriesID) + public ActionResult GetSeries([FromRoute, Range(1, int.MaxValue)] int seriesID) { - if (seriesID == 0) return BadRequest(SeriesController.SeriesWithZeroID); - // Retrieve extra information for the specified series if it exists and the user has permissions. var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series is null) @@ -285,7 +283,7 @@ public ActionResult GetSeries([FromRoute] int seriesID) /// A WebUISeriesFileSummary object containing a summary of file information for the series. [HttpGet("Series/{seriesID}/FileSummary")] public ActionResult GetSeriesFileSummary( - [FromRoute] int seriesID, + [FromRoute, Range(1, int.MaxValue)] int seriesID, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet type = null, [FromQuery, ModelBinder(typeof(CommaDelimitedModelBinder))] HashSet groupBy = null, [FromQuery] bool includeEpisodeDetails = false, @@ -293,7 +291,6 @@ public ActionResult GetSeriesFileSummary( [FromQuery] bool includeMissingFutureEpisodes = false) { // Retrieve a summary of file information for the specified series if it exists and the user has permissions. - if (seriesID == 0) return BadRequest(SeriesController.SeriesWithZeroID); var series = RepoFactory.AnimeSeries.GetByID(seriesID); if (series is null) {