From ed5bb5d76954d0fe88625b5f5b8e8ed73602191a Mon Sep 17 00:00:00 2001 From: Josh Date: Sun, 28 Apr 2024 20:44:41 -0500 Subject: [PATCH 1/7] Prevent LBP3 reviews from showing up in LBP2 (#1015) --- .../Controllers/Slots/ReviewController.cs | 14 +++ .../Unit/Controllers/ReviewControllerTests.cs | 99 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/ReviewControllerTests.cs diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs index 7feae4bf..0dd54c07 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs @@ -157,6 +157,13 @@ public class ReviewController : ControllerBase List reviews = (await this.database.Reviews .Where(r => r.SlotId == slotId) + .Select(r => new + { + Review = r, + SlotVersion = r.Slot!.GameVersion, + }) + .Where(a => a.SlotVersion <= token.GameVersion) + .Select(a => a.Review) .OrderByDescending(r => r.ThumbsUp - r.ThumbsDown) .ThenByDescending(r => r.Timestamp) .ApplyPagination(pageData) @@ -178,6 +185,13 @@ public class ReviewController : ControllerBase List reviews = (await this.database.Reviews .Where(r => r.ReviewerId == targetUserId) + .Select(r => new + { + Review = r, + SlotVersion = r.Slot!.GameVersion, + }) + .Where(a => a.SlotVersion <= token.GameVersion) + .Select(a => a.Review) .OrderByDescending(r => r.Timestamp) .ApplyPagination(pageData) .ToListAsync()).ToSerializableList(r => GameReview.CreateFromEntity(r, token)); diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/ReviewControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/ReviewControllerTests.cs new file mode 100644 index 00000000..303b4f85 --- /dev/null +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/ReviewControllerTests.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; +using LBPUnion.ProjectLighthouse.Tests.Helpers; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Entities.Token; +using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Users; +using Microsoft.AspNetCore.Mvc; +using Xunit; + +namespace ProjectLighthouse.Tests.GameApiTests.Unit.Controllers; + +public class ReviewControllerTests +{ + private static async Task InsertTestData(DatabaseContext database) + { + database.Slots.Add(new SlotEntity + { + SlotId = 1, + CreatorId = 1, + GameVersion = GameVersion.LittleBigPlanet3, + }); + + database.Slots.Add(new SlotEntity + { + SlotId = 2, + CreatorId = 1, + GameVersion = GameVersion.LittleBigPlanet2, + }); + + database.Reviews.Add(new ReviewEntity + { + ReviewId = 1, + ReviewerId = 1, + SlotId = 1, + }); + + database.Reviews.Add(new ReviewEntity + { + ReviewId = 2, + ReviewerId = 1, + SlotId = 2, + }); + await database.SaveChangesAsync(); + } + + [Theory] + [InlineData(GameVersion.LittleBigPlanet2, 1)] + [InlineData(GameVersion.LittleBigPlanet3, 2)] + public async Task ReviewsBy_ShouldNotList_HigherGameVersions(GameVersion version, int expected) + { + GameTokenEntity token = MockHelper.GetUnitTestToken(); + token.GameVersion = version; + DatabaseContext database = await MockHelper.GetTestDatabase(new List + { + token, + }); + + await InsertTestData(database); + + ReviewController controller = new(database); + controller.SetupTestController(token); + + IActionResult response = await controller.ReviewsBy("unittest"); + ReviewResponse review = response.CastTo(); + + Assert.Equal(expected, review.Reviews.Count); + Assert.True(review.Reviews.All(r => database.Slots.FirstOrDefault(s => s.SlotId == r.Slot.SlotId)?.GameVersion <= version)); + } + + [Theory] + [InlineData(GameVersion.LittleBigPlanet2, 2, 1)] + [InlineData(GameVersion.LittleBigPlanet2, 1, 0)] + [InlineData(GameVersion.LittleBigPlanet3, 2, 1)] + [InlineData(GameVersion.LittleBigPlanet3, 1, 1)] + public async Task ReviewsFor_ShouldNotList_HigherGameVersions(GameVersion version, int slotId, int expected) + { + GameTokenEntity token = MockHelper.GetUnitTestToken(); + token.GameVersion = version; + DatabaseContext database = await MockHelper.GetTestDatabase(new List + { + token, + }); + + await InsertTestData(database); + + ReviewController controller = new(database); + controller.SetupTestController(token); + + IActionResult response = await controller.ReviewsFor(slotId); + ReviewResponse review = response.CastTo(); + + Assert.Equal(expected, review.Reviews.Count); + Assert.True(review.Reviews.All(r => database.Slots.FirstOrDefault(s => s.SlotId == r.Slot.SlotId)?.GameVersion <= version)); + } +} \ No newline at end of file From 9e84e468442f4522cd173b0f8b261384fb37a442 Mon Sep 17 00:00:00 2001 From: Slendy Date: Sat, 11 May 2024 17:58:48 -0500 Subject: [PATCH 2/7] Add custom ToString method for NPTicket section header --- ProjectLighthouse/Tickets/TicketReader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ProjectLighthouse/Tickets/TicketReader.cs b/ProjectLighthouse/Tickets/TicketReader.cs index ee6b1a70..697a72a1 100644 --- a/ProjectLighthouse/Tickets/TicketReader.cs +++ b/ProjectLighthouse/Tickets/TicketReader.cs @@ -33,6 +33,9 @@ public struct SectionHeader public SectionType Type; public ushort Length; public int Position; + + public override string ToString() => + $"SectionHeader(Type='{this.Type}', Length='{this.Length}', Position='{this.Position}')"; } public class TicketReader : BinaryReader From 262ada37aed61d49944fb26fb0e227712a909524 Mon Sep 17 00:00:00 2001 From: sudokoko Date: Fri, 31 May 2024 13:53:41 -0400 Subject: [PATCH 3/7] Bump dependencies --- .config/dotnet-tools.json | 2 +- .../ProjectLighthouse.Servers.Website.csproj | 2 +- ...rojectLighthouse.Tests.GameApiTests.csproj | 10 +++++----- ...rojectLighthouse.Tests.WebsiteTests.csproj | 14 ++++++------- .../ProjectLighthouse.Tests.csproj | 12 +++++------ ProjectLighthouse.sln.DotSettings | 8 +++++++- ProjectLighthouse/ProjectLighthouse.csproj | 20 +++++++++---------- 7 files changed, 37 insertions(+), 31 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a1b89d98..7217a983 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "8.0.3", + "version": "8.0.6", "commands": [ "dotnet-ef" ] diff --git a/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj b/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj index 9749d85c..7768663f 100644 --- a/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj +++ b/ProjectLighthouse.Servers.Website/ProjectLighthouse.Servers.Website.csproj @@ -11,6 +11,6 @@ - + diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj index db0ead10..ef720919 100644 --- a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -9,14 +9,14 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 0686a1d3..c5b336f5 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -9,16 +9,16 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index 4eb6d411..4c4199a9 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -14,14 +14,14 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -30,7 +30,7 @@ all - + diff --git a/ProjectLighthouse.sln.DotSettings b/ProjectLighthouse.sln.DotSettings index d3818928..174ff7a3 100644 --- a/ProjectLighthouse.sln.DotSettings +++ b/ProjectLighthouse.sln.DotSettings @@ -1,4 +1,6 @@ - + True False False @@ -96,10 +98,14 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Methods"><ElementKinds><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy></Policy> True True True True + True True True True diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index bc92bc90..c538b220 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -10,31 +10,31 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + - + From 643cb8e816b33a959c4cd2778e5caf2ae03109ee Mon Sep 17 00:00:00 2001 From: sudokoko Date: Fri, 31 May 2024 18:10:05 -0400 Subject: [PATCH 4/7] Implement the ability to forcibly verify a user's email (#1022) * Implement the ability to forcibly verify a user's email * Apply suggestions from code review --- .../Controllers/Admin/AdminUserController.cs | 26 +++++++++++++++++++ .../Pages/UserPage.cshtml | 8 ++++++ 2 files changed, 34 insertions(+) diff --git a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs index 84b024ff..42626944 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs @@ -3,6 +3,7 @@ using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Logging; using LBPUnion.ProjectLighthouse.Types.Moderation.Cases; using LBPUnion.ProjectLighthouse.Types.Users; @@ -91,6 +92,31 @@ public class AdminUserController : ControllerBase return this.Redirect($"/user/{targetedUser.UserId}"); } + /// + /// Forces the email verification of a user. + /// + [HttpGet("forceVerifyEmail")] + public async Task ForceVerifyEmail([FromRoute] int id) + { + UserEntity? user = this.database.UserFromWebRequest(this.Request); + if (user == null || !user.IsModerator) return this.NotFound(); + + UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (targetedUser == null) return this.NotFound(); + if (user.EmailAddressVerified) return this.NotFound(); + + List tokens = await this.database.EmailVerificationTokens + .Where(t => t.UserId == targetedUser.UserId) + .ToListAsync(); + this.database.EmailVerificationTokens.RemoveRange(tokens); + + targetedUser.EmailAddressVerified = true; + + await this.database.SaveChangesAsync(); + + return this.Redirect($"/user/{targetedUser.UserId}"); + } + [HttpPost("/admin/user/{id:int}/setPermissionLevel")] public async Task SetUserPermissionLevel([FromRoute] int id, [FromForm] PermissionLevel role) { diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml index d675e8c1..e1b6fe78 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml @@ -333,6 +333,14 @@ else } + @if (!Model.ProfileUser.EmailAddressVerified) + { + + + Force Verify Email + + } + @if (Model.User.IsAdmin) {
From 3546f60f4f9bc7abc031bd4e5809e862b0ee7025 Mon Sep 17 00:00:00 2001 From: sudokoko Date: Fri, 31 May 2024 18:26:08 -0400 Subject: [PATCH 5/7] Disallow forced email verification of users with no email --- .../Controllers/Admin/AdminUserController.cs | 2 +- ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs index 42626944..0aada8fc 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs @@ -103,7 +103,7 @@ public class AdminUserController : ControllerBase UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); if (targetedUser == null) return this.NotFound(); - if (user.EmailAddressVerified) return this.NotFound(); + if (user.EmailAddress == null || user.EmailAddressVerified) return this.NotFound(); List tokens = await this.database.EmailVerificationTokens .Where(t => t.UserId == targetedUser.UserId) diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml index e1b6fe78..33a07af1 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml @@ -333,11 +333,11 @@ else } - @if (!Model.ProfileUser.EmailAddressVerified) + @if (Model.ProfileUser.EmailAddress != null && !Model.ProfileUser.EmailAddressVerified) { - Force Verify Email + Forcibly Verify Email } From e89a4c27fad0ec9015173e59fc957197a08c76c5 Mon Sep 17 00:00:00 2001 From: sudokoko Date: Fri, 31 May 2024 18:40:39 -0400 Subject: [PATCH 6/7] Fix mismatch between user/targetedUser in force verify endpoint --- .../Controllers/Admin/AdminUserController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs index 0aada8fc..d4fdfaf8 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs @@ -103,7 +103,7 @@ public class AdminUserController : ControllerBase UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); if (targetedUser == null) return this.NotFound(); - if (user.EmailAddress == null || user.EmailAddressVerified) return this.NotFound(); + if (targetedUser.EmailAddress == null || targetedUser.EmailAddressVerified) return this.NotFound(); List tokens = await this.database.EmailVerificationTokens .Where(t => t.UserId == targetedUser.UserId) From be11e138f032ec306f9c732b4a3a3cee47740edc Mon Sep 17 00:00:00 2001 From: Kat <30480654+Metraberryy@users.noreply.github.com> Date: Fri, 14 Jun 2024 19:27:18 -0700 Subject: [PATCH 7/7] Implement the ability for moderators to delete all scores/comments by a user (#1027) * Implement delete all scores/comments * Fix formatting in AdminUserController.cs * Move logging out of loop * Batch delete scores based on UserId * Batch update comments instead of using a foreach * Use html entity instead of apostrophe character * Confirm before deleting all comments/scores * Remove unnecessary database.SaveChanges --- .../Controllers/Admin/AdminUserController.cs | 52 ++++++++++++++++++- .../Pages/UserPage.cshtml | 12 +++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs index d4fdfaf8..55db420c 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs @@ -1,7 +1,9 @@ #nullable enable using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Logging; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Logging; @@ -27,7 +29,8 @@ public class AdminUserController : ControllerBase /// Resets the user's earth decorations to a blank state. Useful for users who abuse audio for example. /// [HttpGet("wipePlanets")] - public async Task WipePlanets([FromRoute] int id) { + public async Task WipePlanets([FromRoute] int id) + { UserEntity? user = this.database.UserFromWebRequest(this.Request); if (user == null || !user.IsModerator) return this.NotFound(); @@ -91,6 +94,53 @@ public class AdminUserController : ControllerBase return this.Redirect($"/user/{targetedUser.UserId}"); } + + /// + /// Deletes every comment by the user. Useful in case of mass spam + /// + [HttpGet("wipeComments")] + public async Task WipeComments([FromRoute] int id) + { + UserEntity? user = this.database.UserFromWebRequest(this.Request); + if (user == null || !user.IsModerator) return this.NotFound(); + + UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (targetedUser == null) return this.NotFound(); + + // Find every comment by the user, then set the deletion info on them + await this.database.Comments.Where(c => c.PosterUserId == targetedUser.UserId) + .ExecuteUpdateAsync(s => + s.SetProperty(c => c.Deleted, true) + .SetProperty(c => c.DeletedBy, user.Username) + .SetProperty(c => c.DeletedType, "moderator")); + Logger.Success($"Deleted comments for {targetedUser.Username} (id:{targetedUser.UserId})", LogArea.Admin); + + await this.database.SendNotification(targetedUser.UserId, + "Your comments have been deleted by a moderator."); + + return this.Redirect($"/user/{targetedUser.UserId}"); + } + + /// + /// Deletes every score from the user. Useful in the case where a user cheated a ton of scores + /// + [HttpGet("wipeScores")] + public async Task WipeScores([FromRoute] int id) + { + UserEntity? user = this.database.UserFromWebRequest(this.Request); + if (user == null || !user.IsModerator) return this.NotFound(); + + UserEntity? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (targetedUser == null) return this.NotFound(); + + // Find and delete every score uploaded by the target user + await this.database.Scores.Where(c => c.UserId == targetedUser.UserId).ExecuteDeleteAsync(); + Logger.Success($"Deleted scores for {targetedUser.Username} (id:{targetedUser.UserId})", LogArea.Admin); + + await this.database.SendNotification(targetedUser.UserId, "Your scores have been deleted by a moderator."); + + return this.Redirect($"/user/{targetedUser.UserId}"); + } /// /// Forces the email verification of a user. diff --git a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml index 33a07af1..98c23fcd 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/UserPage.cshtml @@ -324,6 +324,18 @@ else Wipe Earth Decorations + + + + Wipe User's Comments + + + + + Wipe User's Scores + @if (!Model.CommentsDisabledByModerator) {