From 63cdcf788be64fd9aec4b4bad569b17f2eb43508 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 1 Jan 2024 20:32:29 -0500 Subject: [PATCH 1/2] Allow admins to edit user's levels --- .../DataTypes/Request/ApiEditLevelRequest.cs | 3 ++ .../Endpoints/ApiV3/LevelApiEndpoints.cs | 10 +++++- RefreshTests.GameServer/TestContext.cs | 8 +++++ .../Tests/ApiV3/EditApiTests.cs | 31 +++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiEditLevelRequest.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiEditLevelRequest.cs index babcb56f..5eb42e08 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiEditLevelRequest.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiEditLevelRequest.cs @@ -1,3 +1,5 @@ +using Refresh.GameServer.Authentication; + namespace Refresh.GameServer.Endpoints.ApiV3.DataTypes.Request; [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] @@ -6,4 +8,5 @@ public class ApiEditLevelRequest public string? Title { get; set; } public string? Description { get; set; } public string? IconHash { get; set; } + public TokenGame? GameVersion { get; set; } } \ No newline at end of file diff --git a/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs index 44d4dda3..dbd82ba0 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs @@ -14,6 +14,7 @@ using Refresh.GameServer.Services; using Refresh.GameServer.Types.Levels; using Refresh.GameServer.Types.Levels.Categories; +using Refresh.GameServer.Types.Roles; using Refresh.GameServer.Types.UserData; namespace Refresh.GameServer.Endpoints.ApiV3; @@ -80,9 +81,16 @@ public ApiResponse EditLevelById(RequestContext context, G GameLevel? level = database.GetLevelById(id); if (level == null) return ApiNotFoundError.LevelMissingError; - if (level.Publisher?.UserId != user.UserId) + // If the user hasn't published this level and the user isn't an admin, reject this edit + if (level.Publisher?.UserId != user.UserId && user.Role < GameUserRole.Admin) return ApiAuthenticationError.NoPermissionsForObject; + // Only allow admins to set the GameVersion + if (user.Role < GameUserRole.Admin) + { + body.GameVersion = null; + } + database.UpdateLevel(body, level); return ApiGameLevelResponse.FromOld(level); diff --git a/RefreshTests.GameServer/TestContext.cs b/RefreshTests.GameServer/TestContext.cs index e308fdd7..a2061dbf 100644 --- a/RefreshTests.GameServer/TestContext.cs +++ b/RefreshTests.GameServer/TestContext.cs @@ -5,6 +5,7 @@ using Refresh.GameServer.Database; using Refresh.GameServer.Types; using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Roles; using Refresh.GameServer.Types.UserData; using Refresh.GameServer.Types.UserData.Leaderboard; using RefreshTests.GameServer.Time; @@ -95,6 +96,13 @@ public GameUser CreateUser(string? username = null) return this.Database.CreateUser(username, $"{username}@{username}.local"); } + public GameUser CreateAdmin(string? username = null) + { + GameUser user = this.CreateUser(username); + this.Database.SetUserRole(user, GameUserRole.Admin); + return user; + } + public Token CreateToken(GameUser user, TokenType type = TokenType.Game, TokenGame game = TokenGame.LittleBigPlanet2, TokenPlatform platform = TokenPlatform.PS3) { return this.Database.GenerateTokenForUser(user, type, game, platform); diff --git a/RefreshTests.GameServer/Tests/ApiV3/EditApiTests.cs b/RefreshTests.GameServer/Tests/ApiV3/EditApiTests.cs index 4b0c4171..75f9db10 100644 --- a/RefreshTests.GameServer/Tests/ApiV3/EditApiTests.cs +++ b/RefreshTests.GameServer/Tests/ApiV3/EditApiTests.cs @@ -2,6 +2,7 @@ using Refresh.GameServer.Authentication; using Refresh.GameServer.Endpoints.ApiV3.DataTypes.Request; using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Roles; using Refresh.GameServer.Types.UserData; namespace RefreshTests.GameServer.Tests.ApiV3; @@ -60,6 +61,36 @@ public void OtherUserCantUpdateLevel() Assert.That(level.Title, Is.EqualTo("Not updated")); }); } + + [Test] + public void AdminCanUpdateLevel() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + GameUser admin = context.CreateAdmin(); + GameLevel level = context.CreateLevel(user, "Not updated"); + + long oldUpdate = level.UpdateDate; + + ApiEditLevelRequest payload = new() + { + Title = "Updated", + }; + + context.Time.TimestampMilliseconds = 1; + + using HttpClient client = context.GetAuthenticatedClient(TokenType.Api, admin); + HttpResponseMessage response = client.PatchAsync($"/api/v3/levels/id/{level.LevelId}", JsonContent.Create(payload)).Result; + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + context.Database.Refresh(); + Assert.Multiple(() => + { + Assert.That(level.Title, Is.EqualTo("Updated")); + Assert.That(level.UpdateDate, Is.Not.EqualTo(oldUpdate)); + Assert.That(level.UpdateDate, Is.EqualTo(context.Time.TimestampMilliseconds)); + }); + } [Test] public void CantUpdateMissingLevel() From 0f9120d2f97e1e12fd5ff359780a83829acc6ba8 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 1 Jan 2024 20:55:19 -0500 Subject: [PATCH 2/2] Rework admin level editing to use its own endpoint --- .../Database/GameDatabaseContext.Levels.cs | 2 +- .../ApiV3/Admin/AdminLevelApiEndpoints.cs | 18 +++++++++++++++++- .../Request/ApiAdminEditLevelRequest.cs | 9 +++++++++ .../DataTypes/Request/ApiEditLevelRequest.cs | 1 - .../Endpoints/ApiV3/LevelApiEndpoints.cs | 13 +++---------- .../Tests/ApiV3/EditApiTests.cs | 6 ++++-- 6 files changed, 34 insertions(+), 15 deletions(-) create mode 100644 Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiAdminEditLevelRequest.cs diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs index 0d98820a..05548e4f 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Levels.cs @@ -99,7 +99,7 @@ public GameLevel UpdateLevel(ApiEditLevelRequest body, GameLevel level) { this._realm.Write(() => { - PropertyInfo[] userProps = typeof(ApiEditLevelRequest).GetProperties(); + PropertyInfo[] userProps = body.GetType().GetProperties(); foreach (PropertyInfo prop in userProps) { if (!prop.CanWrite || !prop.CanRead) continue; diff --git a/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLevelApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLevelApiEndpoints.cs index bc6867e5..1411890b 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLevelApiEndpoints.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/Admin/AdminLevelApiEndpoints.cs @@ -1,11 +1,12 @@ using AttribDoc.Attributes; using Bunkum.Core; using Bunkum.Core.Endpoints; -using Bunkum.Listener.Protocol; using Bunkum.Protocols.Http; using Refresh.GameServer.Database; using Refresh.GameServer.Endpoints.ApiV3.ApiTypes; using Refresh.GameServer.Endpoints.ApiV3.ApiTypes.Errors; +using Refresh.GameServer.Endpoints.ApiV3.DataTypes.Request; +using Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response; using Refresh.GameServer.Types.Levels; using Refresh.GameServer.Types.Roles; using Refresh.GameServer.Types.UserData; @@ -39,6 +40,21 @@ public ApiOkResponse RemoveTeamPickFromLevel(RequestContext context, GameDatabas return new ApiOkResponse(); } + [ApiV3Endpoint("admin/levels/id/{id}", HttpMethods.Patch), MinimumRole(GameUserRole.Admin)] + [DocSummary("Updates a level.")] + [DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)] + [DocError(typeof(ApiAuthenticationError), ApiAuthenticationError.NoPermissionsForObjectWhen)] + public ApiResponse EditLevelById(RequestContext context, GameDatabaseContext database, GameUser user, + [DocSummary("The ID of the level")] int id, ApiAdminEditLevelRequest body) + { + GameLevel? level = database.GetLevelById(id); + if (level == null) return ApiNotFoundError.LevelMissingError; + + level = database.UpdateLevel(body, level); + + return ApiGameLevelResponse.FromOld(level); + } + [ApiV3Endpoint("admin/levels/id/{id}", HttpMethods.Delete), MinimumRole(GameUserRole.Admin)] [DocSummary("Deletes a level.")] [DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)] diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiAdminEditLevelRequest.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiAdminEditLevelRequest.cs new file mode 100644 index 00000000..2f59a54b --- /dev/null +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiAdminEditLevelRequest.cs @@ -0,0 +1,9 @@ +using Refresh.GameServer.Authentication; + +namespace Refresh.GameServer.Endpoints.ApiV3.DataTypes.Request; + +[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] +public class ApiAdminEditLevelRequest : ApiEditLevelRequest +{ + public TokenGame? GameVersion { get; set; } +} \ No newline at end of file diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiEditLevelRequest.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiEditLevelRequest.cs index 5eb42e08..4f74c1b6 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiEditLevelRequest.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiEditLevelRequest.cs @@ -8,5 +8,4 @@ public class ApiEditLevelRequest public string? Title { get; set; } public string? Description { get; set; } public string? IconHash { get; set; } - public TokenGame? GameVersion { get; set; } } \ No newline at end of file diff --git a/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs index dbd82ba0..caa269a7 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/LevelApiEndpoints.cs @@ -80,18 +80,11 @@ public ApiResponse EditLevelById(RequestContext context, G { GameLevel? level = database.GetLevelById(id); if (level == null) return ApiNotFoundError.LevelMissingError; - - // If the user hasn't published this level and the user isn't an admin, reject this edit - if (level.Publisher?.UserId != user.UserId && user.Role < GameUserRole.Admin) + + if (level.Publisher?.UserId != user.UserId) return ApiAuthenticationError.NoPermissionsForObject; - // Only allow admins to set the GameVersion - if (user.Role < GameUserRole.Admin) - { - body.GameVersion = null; - } - - database.UpdateLevel(body, level); + level = database.UpdateLevel(body, level); return ApiGameLevelResponse.FromOld(level); } diff --git a/RefreshTests.GameServer/Tests/ApiV3/EditApiTests.cs b/RefreshTests.GameServer/Tests/ApiV3/EditApiTests.cs index 75f9db10..7eee8495 100644 --- a/RefreshTests.GameServer/Tests/ApiV3/EditApiTests.cs +++ b/RefreshTests.GameServer/Tests/ApiV3/EditApiTests.cs @@ -72,21 +72,23 @@ public void AdminCanUpdateLevel() long oldUpdate = level.UpdateDate; - ApiEditLevelRequest payload = new() + ApiAdminEditLevelRequest payload = new() { Title = "Updated", + GameVersion = TokenGame.LittleBigPlanetPSP, }; context.Time.TimestampMilliseconds = 1; using HttpClient client = context.GetAuthenticatedClient(TokenType.Api, admin); - HttpResponseMessage response = client.PatchAsync($"/api/v3/levels/id/{level.LevelId}", JsonContent.Create(payload)).Result; + HttpResponseMessage response = client.PatchAsync($"/api/v3/admin/levels/id/{level.LevelId}", JsonContent.Create(payload)).Result; Assert.That(response.StatusCode, Is.EqualTo(OK)); context.Database.Refresh(); Assert.Multiple(() => { Assert.That(level.Title, Is.EqualTo("Updated")); + Assert.That(level.GameVersion, Is.EqualTo(TokenGame.LittleBigPlanetPSP)); Assert.That(level.UpdateDate, Is.Not.EqualTo(oldUpdate)); Assert.That(level.UpdateDate, Is.EqualTo(context.Time.TimestampMilliseconds)); });