Refactor deserialization and authentication (#550)

* Refactor deserialization and more

* Refactor authentication flow

* Fix unit tests

* Make deserialization better
This commit is contained in:
Josh 2022-11-10 21:14:16 -06:00 committed by GitHub
parent 505b5eb03b
commit b3a00da554
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 575 additions and 589 deletions

View file

@ -1,14 +1,17 @@
#nullable enable #nullable enable
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")] [Produces("text/plain")]
public class ClientConfigurationController : ControllerBase public class ClientConfigurationController : ControllerBase
@ -22,11 +25,8 @@ public class ClientConfigurationController : ControllerBase
[HttpGet("network_settings.nws")] [HttpGet("network_settings.nws")]
[SuppressMessage("ReSharper", "StringLiteralTypo")] [SuppressMessage("ReSharper", "StringLiteralTypo")]
public async Task<IActionResult> NetworkSettings() public IActionResult NetworkSettings()
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
string hostname = ServerConfiguration.Instance.GameApiExternalUrl; string hostname = ServerConfiguration.Instance.GameApiExternalUrl;
return this.Ok return this.Ok
( (
@ -52,7 +52,9 @@ public class ClientConfigurationController : ControllerBase
[Produces("text/xml")] [Produces("text/xml")]
public async Task<IActionResult> GetPrivacySettings() public async Task<IActionResult> GetPrivacySettings()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken token = this.GetToken();
User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
PrivacySettings ps = new() PrivacySettings ps = new()
@ -71,11 +73,7 @@ public class ClientConfigurationController : ControllerBase
User? user = await this.database.UserFromGameRequest(this.Request); User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
this.Request.Body.Position = 0; PrivacySettings? settings = await this.DeserializeBody<PrivacySettings>();
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(PrivacySettings));
PrivacySettings? settings = (PrivacySettings?)serializer.Deserialize(new StringReader(bodyString));
if (settings == null) return this.BadRequest(); if (settings == null) return this.BadRequest();
if (settings.LevelVisibility != null) if (settings.LevelVisibility != null)

View file

@ -1,17 +1,19 @@
#nullable enable #nullable enable
using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class CommentController : ControllerBase public class CommentController : ControllerBase
@ -26,10 +28,10 @@ public class CommentController : ControllerBase
[HttpPost("rateComment/{slotType}/{slotId:int}")] [HttpPost("rateComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId) public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (username == null && (SlotHelper.IsTypeInvalid(slotType) || slotId == 0)) return this.BadRequest(); // Return bad request if both are true or both are false
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
bool success = await this.database.RateComment(token.UserId, commentId, rating); bool success = await this.database.RateComment(token.UserId, commentId, rating);
if (!success) return this.BadRequest(); if (!success) return this.BadRequest();
@ -41,37 +43,33 @@ public class CommentController : ControllerBase
[HttpGet("userComments/{username}")] [HttpGet("userComments/{username}")]
public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, string? slotType, int slotId) public async Task<IActionResult> GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, string? slotType, int slotId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0 || pageStart < 0) return this.BadRequest();
int targetId = slotId; if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
CommentType type = CommentType.Level;
if (!string.IsNullOrWhiteSpace(username)) if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
int targetId;
CommentType type = username == null ? CommentType.Level : CommentType.Profile;
if (type == CommentType.Level)
{ {
targetId = this.database.Users.First(u => u.Username.Equals(username)).UserId; targetId = await this.database.Slots.Where(s => s.SlotId == slotId)
type = CommentType.Profile; .Where(s => s.CommentsEnabled && !s.Hidden)
.Select(s => s.SlotId)
.FirstOrDefaultAsync();
} }
else else
{ {
if (SlotHelper.IsTypeInvalid(slotType) || slotId == 0) return this.BadRequest(); targetId = await this.database.Users.Where(u => u.Username == username)
.Where(u => u.CommentsEnabled)
.Select(u => u.UserId)
.FirstOrDefaultAsync();
} }
if (type == CommentType.Level && slotType == "developer") targetId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); if (targetId == 0) return this.NotFound();
if (type == CommentType.Profile)
{
User? profile = await this.database.Users.FirstOrDefaultAsync(s => s.UserId == targetId);
if (profile == null) return this.BadRequest();
if (!profile.CommentsEnabled) return this.NotFound();
}
else
{
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == targetId);
if (slot == null) return this.BadRequest();
if (!slot.CommentsEnabled) return this.NotFound();
}
List<Comment> comments = await this.database.Comments.Include List<Comment> comments = await this.database.Comments.Include
(c => c.Poster) (c => c.Poster)
@ -88,7 +86,8 @@ public class CommentController : ControllerBase
private async Task<int> getReaction(int userId, int commentId) private async Task<int> getReaction(int userId, int commentId)
{ {
return await this.database.Reactions.Where(r => r.UserId == userId && r.TargetId == commentId) return await this.database.Reactions.Where(r => r.UserId == userId)
.Where(r => r.TargetId == commentId)
.Select(r => r.Rating) .Select(r => r.Rating)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
} }
@ -97,28 +96,28 @@ public class CommentController : ControllerBase
[HttpPost("postComment/{slotType}/{slotId:int}")] [HttpPost("postComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> PostComment(string? username, string? slotType, int slotId) public async Task<IActionResult> PostComment(string? username, string? slotType, int slotId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Comment));
Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString));
SanitizationHelper.SanitizeStringsInClass(comment);
CommentType type = (slotId == 0 ? CommentType.Profile : CommentType.Level);
if (type == CommentType.Level && (SlotHelper.IsTypeInvalid(slotType) || slotId == 0)) return this.BadRequest();
Comment? comment = await this.DeserializeBody<Comment>();
if (comment == null) return this.BadRequest(); if (comment == null) return this.BadRequest();
int targetId = slotId; if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
if (type == CommentType.Profile) targetId = this.database.Users.First(u => u.Username == username).UserId; CommentType type = username == null ? CommentType.Level : CommentType.Profile;
if (slotType == "developer") targetId = await SlotHelper.GetPlaceholderSlotId(this.database, targetId, SlotType.Developer); int targetId;
if (type == CommentType.Level)
{
slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
targetId = await this.database.Slots.Where(s => s.SlotId == slotId)
.Where(s => s.CommentsEnabled && !s.Hidden)
.Select(s => s.SlotId)
.FirstOrDefaultAsync();
}
else
{
targetId = await this.database.UserIdFromUsername(username!);
}
bool success = await this.database.PostComment(token.UserId, targetId, type, comment.Message); bool success = await this.database.PostComment(token.UserId, targetId, type, comment.Message);
if (success) return this.Ok(); if (success) return this.Ok();
@ -130,44 +129,44 @@ public class CommentController : ControllerBase
[HttpPost("deleteComment/{slotType}/{slotId:int}")] [HttpPost("deleteComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId) public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
if (comment == null) return this.NotFound(); if (comment == null) return this.NotFound();
if (comment.Type == CommentType.Level && (SlotHelper.IsTypeInvalid(slotType) || slotId == 0)) return this.BadRequest(); if (comment.Deleted) return this.Ok();
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); bool canDelete;
if (comment.Type == CommentType.Profile)
// if you are not the poster
if (comment.PosterUserId != token.UserId)
{ {
if (comment.Type == CommentType.Profile) canDelete = comment.PosterUserId == token.UserId || comment.TargetId == token.UserId;
{
// if you aren't the poster and aren't the profile owner
if (comment.TargetId != token.UserId)
{
return this.StatusCode(403, "");
}
}
else
{
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == comment.TargetId);
// if you aren't the creator of the level
if (slot == null || slot.CreatorId != token.UserId || slotId != slot.SlotId)
{
return this.StatusCode(403, "");
}
}
} }
else
{
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
if (slotId != comment.TargetId) return this.BadRequest();
int slotCreator = await this.database.Slots.Where(s => s.SlotId == comment.TargetId)
.Where(s => s.CommentsEnabled)
.Select(s => s.CreatorId)
.FirstOrDefaultAsync();
// Comments are disabled or the slot doesn't have a creator
if (slotCreator == 0) return this.BadRequest();
canDelete = comment.PosterUserId == token.UserId || slotCreator == token.UserId;
}
if (!canDelete) return this.StatusCode(403, "");
comment.Deleted = true; comment.Deleted = true;
comment.DeletedBy = await this.database.UsernameFromGameToken(token); comment.DeletedBy = await this.database.UsernameFromGameToken(token);
comment.DeletedType = "user"; comment.DeletedType = "user";
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(); return this.Ok();
} }
} }

View file

@ -1,28 +1,14 @@
using LBPUnion.ProjectLighthouse.PlayerData; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class DeveloperController : Controller public class DeveloperController : Controller
{ {
private readonly Database database;
public DeveloperController(Database database)
{
this.database = database;
}
[HttpGet("developer_videos")] [HttpGet("developer_videos")]
public async Task<IActionResult> DeveloperVideos() public IActionResult DeveloperVideos() => this.Ok("<videos></videos>");
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
return this.Ok("<videos></videos>");
}
} }

View file

@ -1,16 +1,18 @@
#nullable enable #nullable enable
using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.StorableLists.Stores; using LBPUnion.ProjectLighthouse.StorableLists.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
public class FriendsController : ControllerBase public class FriendsController : ControllerBase
{ {
@ -24,14 +26,9 @@ public class FriendsController : ControllerBase
[HttpPost("npdata")] [HttpPost("npdata")]
public async Task<IActionResult> NPData() public async Task<IActionResult> NPData()
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
this.Request.Body.Position = 0; NPData? npData = await this.DeserializeBody<NPData>();
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(NPData));
NPData? npData = (NPData?)serializer.Deserialize(new StringReader(bodyString));
if (npData == null) return this.BadRequest(); if (npData == null) return this.BadRequest();
SanitizationHelper.SanitizeStringsInClass(npData); SanitizationHelper.SanitizeStringsInClass(npData);
@ -73,7 +70,6 @@ public class FriendsController : ControllerBase
if (userAndToken == null) return this.StatusCode(403, ""); if (userAndToken == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1; User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2; GameToken gameToken = userAndToken.Value.Item2;

View file

@ -72,7 +72,14 @@ public class LoginController : ControllerBase
} }
} }
User? user = await this.database.UserFromGameToken(token, true); // The GameToken LINQ statement above is case insensitive so we check that they are equal here
if (token.User.Username != npTicket.Username)
{
Logger.Warn($"Username case does not match for user {npTicket.Username}, expected={token.User.Username}", LogArea.Login);
return this.StatusCode(403, "");
}
User? user = await this.database.UserFromGameToken(token);
if (user == null || user.IsBanned) if (user == null || user.IsBanned)
{ {

View file

@ -2,12 +2,14 @@
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/goodbye")] [Route("LITTLEBIGPLANETPS3_XML/goodbye")]
[Produces("text/xml")] [Produces("text/xml")]
public class LogoutController : ControllerBase public class LogoutController : ControllerBase
@ -23,10 +25,9 @@ public class LogoutController : ControllerBase
[HttpPost] [HttpPost]
public async Task<IActionResult> OnPost() public async Task<IActionResult> OnPost()
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
User? user = await this.database.Users.Where(u => u.UserId == token.UserId).FirstOrDefaultAsync(); User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
user.LastLogout = TimeHelper.TimestampMillis; user.LastLogout = TimeHelper.TimestampMillis;

View file

@ -1,15 +1,18 @@
#nullable enable #nullable enable
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
// [Produces("text/plain")] [Produces("text/xml")]
public class EnterLevelController : ControllerBase public class EnterLevelController : ControllerBase
{ {
private readonly Database database; private readonly Database database;
@ -22,8 +25,7 @@ public class EnterLevelController : ControllerBase
[HttpPost("play/{slotType}/{slotId:int}")] [HttpPost("play/{slotType}/{slotId:int}")]
public async Task<IActionResult> PlayLevel(string slotType, int slotId) public async Task<IActionResult> PlayLevel(string slotType, int slotId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
@ -33,13 +35,11 @@ public class EnterLevelController : ControllerBase
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion;
IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == token.UserId); IQueryable<VisitedLevel> visited = this.database.VisitedLevels.Where(s => s.SlotId == slotId && s.UserId == token.UserId);
VisitedLevel? v; VisitedLevel? v;
if (!visited.Any()) if (!visited.Any())
{ {
switch (gameVersion) switch (token.GameVersion)
{ {
case GameVersion.LittleBigPlanet2: case GameVersion.LittleBigPlanet2:
case GameVersion.LittleBigPlanetVita: case GameVersion.LittleBigPlanetVita:
@ -48,6 +48,9 @@ public class EnterLevelController : ControllerBase
case GameVersion.LittleBigPlanet3: case GameVersion.LittleBigPlanet3:
slot.PlaysLBP3Unique++; slot.PlaysLBP3Unique++;
break; break;
case GameVersion.LittleBigPlanet1:
case GameVersion.LittleBigPlanetPSP:
case GameVersion.Unknown:
default: return this.BadRequest(); default: return this.BadRequest();
} }
@ -65,7 +68,7 @@ public class EnterLevelController : ControllerBase
if (v == null) return this.NotFound(); if (v == null) return this.NotFound();
switch (gameVersion) switch (token.GameVersion)
{ {
case GameVersion.LittleBigPlanet2: case GameVersion.LittleBigPlanet2:
case GameVersion.LittleBigPlanetVita: case GameVersion.LittleBigPlanetVita:
@ -76,9 +79,9 @@ public class EnterLevelController : ControllerBase
slot.PlaysLBP3++; slot.PlaysLBP3++;
v.PlaysLBP3++; v.PlaysLBP3++;
break; break;
case GameVersion.LittleBigPlanetPSP: throw new NotImplementedException();
case GameVersion.Unknown:
case GameVersion.LittleBigPlanet1: case GameVersion.LittleBigPlanet1:
case GameVersion.LittleBigPlanetPSP:
case GameVersion.Unknown:
default: default:
return this.BadRequest(); return this.BadRequest();
} }
@ -92,8 +95,7 @@ public class EnterLevelController : ControllerBase
[HttpPost("enterLevel/{slotType}/{slotId:int}")] [HttpPost("enterLevel/{slotType}/{slotId:int}")]
public async Task<IActionResult> EnterLevel(string slotType, int slotId) public async Task<IActionResult> EnterLevel(string slotType, int slotId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();

View file

@ -8,12 +8,14 @@ using LBPUnion.ProjectLighthouse.Match.MatchCommands;
using LBPUnion.ProjectLighthouse.Match.Rooms; using LBPUnion.ProjectLighthouse.Match.Rooms;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class MatchController : ControllerBase public class MatchController : ControllerBase
@ -27,26 +29,16 @@ public class MatchController : ControllerBase
[HttpPost("gameState")] [HttpPost("gameState")]
[Produces("text/plain")] [Produces("text/plain")]
public async Task<IActionResult> GameState() public IActionResult GameState() => this.Ok("VALID");
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
return this.Ok("VALID");
}
[HttpPost("match")] [HttpPost("match")]
[Produces("text/plain")] [Produces("text/plain")]
public async Task<IActionResult> Match() public async Task<IActionResult> Match()
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (userAndToken == null) return this.StatusCode(403, ""); User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
#region Parse match data #region Parse match data
@ -81,70 +73,74 @@ public class MatchController : ControllerBase
#endregion #endregion
await LastContactHelper.SetLastContact(this.database, user, gameToken.GameVersion, gameToken.Platform); await LastContactHelper.SetLastContact(this.database, user, token.GameVersion, token.Platform);
#region Process match data #region Process match data
if (matchData is UpdateMyPlayerData playerData) switch (matchData)
{ {
MatchHelper.SetUserLocation(user.UserId, gameToken.UserLocation); case UpdateMyPlayerData playerData:
Room? room = RoomHelper.FindRoomByUser(user.UserId, gameToken.GameVersion, gameToken.Platform, true);
if (playerData.RoomState != null)
if (room != null && Equals(room.HostId, user.UserId))
room.State = (RoomState)playerData.RoomState;
}
// Check how many people are online in release builds, disabled for debug for ..well debugging.
#if DEBUG
else if (matchData is FindBestRoom diveInData)
#else
else if (matchData is FindBestRoom diveInData && MatchHelper.UserLocations.Count > 1)
#endif
{
FindBestRoomResponse? response = RoomHelper.FindBestRoom
(user, gameToken.GameVersion, diveInData.RoomSlot, gameToken.Platform, gameToken.UserLocation);
if (response == null) return this.NotFound();
string serialized = JsonSerializer.Serialize(response, typeof(FindBestRoomResponse));
foreach (Player player in response.Players) MatchHelper.AddUserRecentlyDivedIn(user.UserId, player.User.UserId);
return this.Ok($"[{{\"StatusCode\":200}},{serialized}]");
}
else if (matchData is CreateRoom createRoom && MatchHelper.UserLocations.Count >= 1)
{
List<int> users = new();
foreach (string playerUsername in createRoom.Players)
{ {
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername); MatchHelper.SetUserLocation(user.UserId, token.UserLocation);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse Room? room = RoomHelper.FindRoomByUser(user.UserId, token.GameVersion, token.Platform, true);
if (player != null) users.Add(player.UserId);
else return this.BadRequest(); if (playerData.RoomState != null)
if (room != null && Equals(room.HostId, user.UserId))
room.State = (RoomState)playerData.RoomState;
break;
} }
// Check how many people are online in release builds, disabled for debug for ..well debugging.
// Create a new one as requested #if DEBUG
RoomHelper.CreateRoom(users, gameToken.GameVersion, gameToken.Platform, createRoom.RoomSlot); case FindBestRoom diveInData:
} #else
case FindBestRoom diveInData when MatchHelper.UserLocations.Count > 1:
else if (matchData is UpdatePlayersInRoom updatePlayersInRoom) #endif
{
Room? room = RoomHelper.Rooms.FirstOrDefault(r => r.HostId == user.UserId);
if (room != null)
{ {
List<User> users = new(); FindBestRoomResponse? response = RoomHelper.FindBestRoom
foreach (string playerUsername in updatePlayersInRoom.Players) (user, token.GameVersion, diveInData.RoomSlot, token.Platform, token.UserLocation);
if (response == null) return this.NotFound();
string serialized = JsonSerializer.Serialize(response, typeof(FindBestRoomResponse));
foreach (Player player in response.Players) MatchHelper.AddUserRecentlyDivedIn(user.UserId, player.User.UserId);
return this.Ok($"[{{\"StatusCode\":200}},{serialized}]");
}
case CreateRoom createRoom when MatchHelper.UserLocations.Count >= 1:
{
List<int> users = new();
foreach (string playerUsername in createRoom.Players)
{ {
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername); User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse // ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (player != null) users.Add(player); if (player != null) users.Add(player.UserId);
else return this.BadRequest(); else return this.BadRequest();
} }
room.PlayerIds = users.Select(u => u.UserId).ToList(); // Create a new one as requested
await RoomHelper.CleanupRooms(null, room); RoomHelper.CreateRoom(users, token.GameVersion, token.Platform, createRoom.RoomSlot);
break;
}
case UpdatePlayersInRoom updatePlayersInRoom:
{
Room? room = RoomHelper.Rooms.FirstOrDefault(r => r.HostId == user.UserId);
if (room != null)
{
List<User> users = new();
foreach (string playerUsername in updatePlayersInRoom.Players)
{
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (player != null) users.Add(player);
else return this.BadRequest();
}
room.PlayerIds = users.Select(u => u.UserId).ToList();
await RoomHelper.CleanupRooms(null, room);
}
break;
} }
} }

View file

@ -1,14 +1,17 @@
#nullable enable #nullable enable
using System.Globalization; using System.Globalization;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")] [Produces("text/plain")]
public class MessageController : ControllerBase public class MessageController : ControllerBase
@ -35,20 +38,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
} }
[HttpGet("eula")] [HttpGet("eula")]
public async Task<IActionResult> Eula() public IActionResult Eula() => this.Ok($"{license}\n{ServerConfiguration.Instance.EulaText}");
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
// ReSharper disable once ConvertIfStatementToReturnStatement
if (token == null) return this.StatusCode(403, "");
return this.Ok($"{license}\n{ServerConfiguration.Instance.EulaText}");
}
[HttpGet("announce")] [HttpGet("announce")]
public async Task<IActionResult> Announce() public async Task<IActionResult> Announce()
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
string username = await this.database.UsernameFromGameToken(token); string username = await this.database.UsernameFromGameToken(token);
@ -76,16 +71,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
[HttpGet("notification")] [HttpGet("notification")]
public IActionResult Notification() => this.Ok(); public IActionResult Notification() => this.Ok();
/// <summary> /// <summary>
/// Filters chat messages sent by a user. /// Filters chat messages sent by a user.
/// The reponse sent is the text that will appear in-game. /// The response sent is the text that will appear in-game.
/// </summary> /// </summary>
[HttpPost("filter")] [HttpPost("filter")]
public async Task<IActionResult> Filter() public async Task<IActionResult> Filter()
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
string response = await new StreamReader(this.Request.Body).ReadToEndAsync(); string response = await new StreamReader(this.Request.Body).ReadToEndAsync();

View file

@ -1,16 +1,18 @@
#nullable enable #nullable enable
using System.Text.Json; using System.Text.Json;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Administration.Reports; using LBPUnion.ProjectLighthouse.Administration.Reports;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class ReportController : ControllerBase public class ReportController : ControllerBase
@ -25,15 +27,11 @@ public class ReportController : ControllerBase
[HttpPost("grief")] [HttpPost("grief")]
public async Task<IActionResult> Report() public async Task<IActionResult> Report()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken token = this.GetToken();
if (user == null) return this.StatusCode(403, "");
this.Request.Body.Position = 0; string username = await this.database.UsernameFromGameToken(token);
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(GriefReport));
GriefReport? report = (GriefReport?)serializer.Deserialize(new StringReader(bodyString));
GriefReport? report = await this.DeserializeBody<GriefReport>();
if (report == null) return this.BadRequest(); if (report == null) return this.BadRequest();
SanitizationHelper.SanitizeStringsInClass(report); SanitizationHelper.SanitizeStringsInClass(report);
@ -41,14 +39,14 @@ public class ReportController : ControllerBase
report.Bounds = JsonSerializer.Serialize(report.XmlBounds.Rect, typeof(Rectangle)); report.Bounds = JsonSerializer.Serialize(report.XmlBounds.Rect, typeof(Rectangle));
report.Players = JsonSerializer.Serialize(report.XmlPlayers, typeof(ReportPlayer[])); report.Players = JsonSerializer.Serialize(report.XmlPlayers, typeof(ReportPlayer[]));
report.Timestamp = TimeHelper.UnixTimeMilliseconds(); report.Timestamp = TimeHelper.UnixTimeMilliseconds();
report.ReportingPlayerId = user.UserId; report.ReportingPlayerId = token.UserId;
this.database.Reports.Add(report); this.database.Reports.Add(report);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
await WebhookHelper.SendWebhook( await WebhookHelper.SendWebhook(
title: "New grief report", title: "New grief report",
description: $"Submitted by {user.Username}\n" + description: $"Submitted by {username}\n" +
$"To view it, click [here]({ServerConfiguration.Instance.ExternalUrl}/moderation/report/{report.ReportId}).", $"To view it, click [here]({ServerConfiguration.Instance.ExternalUrl}/moderation/report/{report.ReportId}).",
dest: WebhookHelper.WebhookDestination.Moderation dest: WebhookHelper.WebhookDestination.Moderation
); );

View file

@ -1,5 +1,4 @@
#nullable enable #nullable enable
using System.Xml.Serialization;
using Discord; using Discord;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
@ -9,12 +8,14 @@ using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class PhotosController : ControllerBase public class PhotosController : ControllerBase
@ -29,16 +30,12 @@ public class PhotosController : ControllerBase
[HttpPost("uploadPhoto")] [HttpPost("uploadPhoto")]
public async Task<IActionResult> UploadPhoto() public async Task<IActionResult> UploadPhoto()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); User? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
if (user.PhotosByMe >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest(); if (user.PhotosByMe >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest();
this.Request.Body.Position = 0; Photo? photo = await this.DeserializeBody<Photo>();
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Photo));
Photo? photo = (Photo?)serializer.Deserialize(new StringReader(bodyString));
if (photo == null) return this.BadRequest(); if (photo == null) return this.BadRequest();
SanitizationHelper.SanitizeStringsInClass(photo); SanitizationHelper.SanitizeStringsInClass(photo);
@ -65,9 +62,9 @@ public class PhotosController : ControllerBase
{ {
// We'll grab the slot by the RootLevel and see what happens from here. // We'll grab the slot by the RootLevel and see what happens from here.
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.ResourceCollection.Contains(photoSlot.RootLevel)); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.ResourceCollection.Contains(photoSlot.RootLevel));
if(slot == null) break; if (slot == null) break;
if (!string.IsNullOrEmpty(slot!.RootLevel)) validLevel = true; if (!string.IsNullOrEmpty(slot.RootLevel)) validLevel = true;
if (slot.IsAdventurePlanet) photoSlot.SlotId = slot.SlotId; if (slot.IsAdventurePlanet) photoSlot.SlotId = slot.SlotId;
break; break;
} }
@ -83,6 +80,10 @@ public class PhotosController : ControllerBase
validLevel = true; validLevel = true;
break; break;
} }
case SlotType.Moon:
case SlotType.Unknown:
case SlotType.Unknown2:
case SlotType.DLC:
default: Logger.Warn($"Invalid photo level type: {photoSlot.SlotType}", LogArea.Photos); default: Logger.Warn($"Invalid photo level type: {photoSlot.SlotType}", LogArea.Photos);
break; break;
} }
@ -103,10 +104,8 @@ public class PhotosController : ControllerBase
subjectUserIds.Add(subject.Username); subjectUserIds.Add(subject.Username);
} }
foreach (PhotoSubject subject in photo.Subjects) foreach (PhotoSubject subject in photo.Subjects.Where(subject => !string.IsNullOrEmpty(subject.Username)))
{ {
if (string.IsNullOrEmpty(subject.Username)) continue;
subject.User = await this.database.Users.FirstOrDefaultAsync(u => u.Username == subject.Username); subject.User = await this.database.Users.FirstOrDefaultAsync(u => u.Username == subject.Username);
if (subject.User == null) continue; if (subject.User == null) continue;
@ -144,9 +143,6 @@ public class PhotosController : ControllerBase
[HttpGet("photos/{slotType}/{id:int}")] [HttpGet("photos/{slotType}/{id:int}")]
public async Task<IActionResult> SlotPhotos([FromQuery] int pageStart, [FromQuery] int pageSize, string slotType, int id) public async Task<IActionResult> SlotPhotos([FromQuery] int pageStart, [FromQuery] int pageSize, string slotType, int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
@ -166,12 +162,9 @@ public class PhotosController : ControllerBase
[HttpGet("photos/by")] [HttpGet("photos/by")]
public async Task<IActionResult> UserPhotosBy([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> UserPhotosBy([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
int targetUserId = await this.database.Users.Where(u => u.Username == user).Select(u => u.UserId).FirstOrDefaultAsync(); int targetUserId = await this.database.UserIdFromUsername(user);
if (targetUserId == 0) return this.NotFound(); if (targetUserId == 0) return this.NotFound();
List<Photo> photos = await this.database.Photos.Include List<Photo> photos = await this.database.Photos.Include
@ -188,12 +181,9 @@ public class PhotosController : ControllerBase
[HttpGet("photos/with")] [HttpGet("photos/with")]
public async Task<IActionResult> UserPhotosWith([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> UserPhotosWith([FromQuery] string user, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
int targetUserId = await this.database.Users.Where(u => u.Username == user).Select(u => u.UserId).FirstOrDefaultAsync(); int targetUserId = await this.database.UserIdFromUsername(user);
if (targetUserId == 0) return this.NotFound(); if (targetUserId == 0) return this.NotFound();
List<int> photoSubjectIds = new(); List<int> photoSubjectIds = new();
@ -220,8 +210,7 @@ public class PhotosController : ControllerBase
[HttpPost("deletePhoto/{id:int}")] [HttpPost("deletePhoto/{id:int}")]
public async Task<IActionResult> DeletePhoto(int id) public async Task<IActionResult> DeletePhoto(int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Photo? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id); Photo? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id);
if (photo == null) return this.NotFound(); if (photo == null) return this.NotFound();

View file

@ -1,28 +1,23 @@
#nullable enable #nullable enable
using System.Buffers; using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using IOFile = System.IO.File; using IOFile = System.IO.File;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources;
[ApiController] [ApiController]
[Authorize]
[Produces("text/xml")] [Produces("text/xml")]
[Route("LITTLEBIGPLANETPS3_XML")] [Route("LITTLEBIGPLANETPS3_XML")]
public class ResourcesController : ControllerBase public class ResourcesController : ControllerBase
{ {
private readonly Database database;
public ResourcesController(Database database)
{
this.database = database;
}
[HttpPost("showModerated")] [HttpPost("showModerated")]
public IActionResult ShowModerated() => this.Ok(LbpSerializer.BlankElement("resources")); public IActionResult ShowModerated() => this.Ok(LbpSerializer.BlankElement("resources"));
@ -31,14 +26,7 @@ public class ResourcesController : ControllerBase
[HttpPost("showNotUploaded")] [HttpPost("showNotUploaded")]
public async Task<IActionResult> FilterResources() public async Task<IActionResult> FilterResources()
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); ResourceList? resourceList = await this.DeserializeBody<ResourceList>();
if (token == null) return this.StatusCode(403, "");
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(ResourceList));
ResourceList? resourceList = (ResourceList?)serializer.Deserialize(new StringReader(bodyString));
if (resourceList == null) return this.BadRequest(); if (resourceList == null) return this.BadRequest();
string resources = resourceList.Resources.Where string resources = resourceList.Resources.Where
@ -49,18 +37,14 @@ public class ResourcesController : ControllerBase
} }
[HttpGet("r/{hash}")] [HttpGet("r/{hash}")]
public async Task<IActionResult> GetResource(string hash) public IActionResult GetResource(string hash)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
string path = FileHelper.GetResourcePath(hash); string path = FileHelper.GetResourcePath(hash);
string fullPath = Path.GetFullPath(path); string fullPath = Path.GetFullPath(path);
string basePath = Path.GetFullPath(FileHelper.ResourcePath);
// Prevent directory traversal attacks // Prevent directory traversal attacks
if (!fullPath.StartsWith(basePath)) return this.BadRequest(); if (!fullPath.StartsWith(FileHelper.FullResourcePath)) return this.BadRequest();
if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream"); if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream");
@ -72,16 +56,17 @@ public class ResourcesController : ControllerBase
[HttpPost("upload/{hash}")] [HttpPost("upload/{hash}")]
public async Task<IActionResult> UploadResource(string hash) public async Task<IActionResult> UploadResource(string hash)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
string assetsDirectory = FileHelper.ResourcePath; string assetsDirectory = FileHelper.ResourcePath;
string path = FileHelper.GetResourcePath(hash); string path = FileHelper.GetResourcePath(hash);
string fullPath = Path.GetFullPath(path);
FileHelper.EnsureDirectoryCreated(assetsDirectory); FileHelper.EnsureDirectoryCreated(assetsDirectory);
// lbp treats code 409 as success and as an indicator that the file is already present // lbp treats code 409 as success and as an indicator that the file is already present
if (FileHelper.ResourceExists(hash)) return this.Conflict(); if (FileHelper.ResourceExists(hash)) return this.Conflict();
// theoretically shouldn't be possible because of hash check but handle anyways
if (!fullPath.StartsWith(FileHelper.FullResourcePath)) return this.BadRequest();
Logger.Info($"Processing resource upload (hash: {hash})", LogArea.Resources); Logger.Info($"Processing resource upload (hash: {hash})", LogArea.Resources);
LbpFile file = new(await readFromPipeReader(this.Request.BodyReader)); LbpFile file = new(await readFromPipeReader(this.Request.BodyReader));

View file

@ -1,19 +1,20 @@
#nullable enable #nullable enable
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.Levels.Categories; using LBPUnion.ProjectLighthouse.Levels.Categories;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class CollectionController : ControllerBase public class CollectionController : ControllerBase
@ -28,9 +29,6 @@ public class CollectionController : ControllerBase
[HttpGet("playlists/{playlistId:int}/slots")] [HttpGet("playlists/{playlistId:int}/slots")]
public async Task<IActionResult> GetPlaylistSlots(int playlistId) public async Task<IActionResult> GetPlaylistSlots(int playlistId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
Playlist? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId); Playlist? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId);
if (targetPlaylist == null) return this.BadRequest(); if (targetPlaylist == null) return this.BadRequest();
@ -50,8 +48,7 @@ public class CollectionController : ControllerBase
[HttpPost("playlists/{playlistId:int}/order_slots")] [HttpPost("playlists/{playlistId:int}/order_slots")]
public async Task<IActionResult> UpdatePlaylist(int playlistId, int slotId) public async Task<IActionResult> UpdatePlaylist(int playlistId, int slotId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Playlist? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId); Playlist? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId);
if (targetPlaylist == null) return this.BadRequest(); if (targetPlaylist == null) return this.BadRequest();
@ -66,7 +63,7 @@ public class CollectionController : ControllerBase
return this.Ok(this.GetUserPlaylists(token.UserId)); return this.Ok(this.GetUserPlaylists(token.UserId));
} }
Playlist? newPlaylist = await this.getPlaylistFromBody(); Playlist? newPlaylist = await this.DeserializeBody<Playlist>("playlist", "levels");
if (newPlaylist == null) return this.BadRequest(); if (newPlaylist == null) return this.BadRequest();
@ -116,14 +113,13 @@ public class CollectionController : ControllerBase
[HttpPost("playlists")] [HttpPost("playlists")]
public async Task<IActionResult> CreatePlaylist() public async Task<IActionResult> CreatePlaylist()
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
int playlistCount = await this.database.Playlists.CountAsync(p => p.CreatorId == token.UserId); int playlistCount = await this.database.Playlists.CountAsync(p => p.CreatorId == token.UserId);
if (playlistCount > ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota) return this.BadRequest(); if (playlistCount > ServerConfiguration.Instance.UserGeneratedContentLimits.ListsQuota) return this.BadRequest();
Playlist? playlist = await this.getPlaylistFromBody(); Playlist? playlist = await this.DeserializeBody<Playlist>("playlist");
if (playlist == null) return this.BadRequest(); if (playlist == null) return this.BadRequest();
@ -139,10 +135,7 @@ public class CollectionController : ControllerBase
[HttpGet("user/{username}/playlists")] [HttpGet("user/{username}/playlists")]
public async Task<IActionResult> GetUserPlaylists(string username) public async Task<IActionResult> GetUserPlaylists(string username)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); int targetUserId = await this.database.UserIdFromUsername(username);
if (token == null) return this.StatusCode(403, "");
int targetUserId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync();
if (targetUserId == 0) return this.BadRequest(); if (targetUserId == 0) return this.BadRequest();
return this.Ok(this.GetUserPlaylists(targetUserId)); return this.Ok(this.GetUserPlaylists(targetUserId));
@ -152,8 +145,9 @@ public class CollectionController : ControllerBase
[HttpGet("genres")] [HttpGet("genres")]
public async Task<IActionResult> GenresAndSearches() public async Task<IActionResult> GenresAndSearches()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); GameToken token = this.GetToken();
if (user == null) return this.StatusCode(403, "");
User? user = await this.database.UserFromGameToken(token);
string categoriesSerialized = CategoryHelper.Categories.Aggregate string categoriesSerialized = CategoryHelper.Categories.Aggregate
( (
@ -196,13 +190,9 @@ public class CollectionController : ControllerBase
[HttpGet("searches/{endpointName}")] [HttpGet("searches/{endpointName}")]
public async Task<IActionResult> GetCategorySlots(string endpointName, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> GetCategorySlots(string endpointName, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (userAndToken == null) return this.StatusCode(403, ""); User? user = await this.database.UserFromGameToken(token);
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
Category? category = CategoryHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName); Category? category = CategoryHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName);
if (category == null) return this.NotFound(); if (category == null) return this.NotFound();
@ -223,7 +213,7 @@ public class CollectionController : ControllerBase
totalSlots = category.GetTotalSlots(this.database); totalSlots = category.GetTotalSlots(this.database);
} }
string slotsSerialized = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(gameToken.GameVersion)); string slotsSerialized = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(token.GameVersion));
return this.Ok return this.Ok
( (
@ -243,19 +233,4 @@ public class CollectionController : ControllerBase
) )
); );
} }
private async Task<Playlist?> getPlaylistFromBody()
{
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
string rootElement = bodyString.StartsWith("<playlist>") ? "playlist" : "levels";
XmlSerializer serializer = new(typeof(Playlist), new XmlRootAttribute(rootElement));
Playlist? playlist = (Playlist?)serializer.Deserialize(new StringReader(bodyString));
SanitizationHelper.SanitizeStringsInClass(playlist);
return playlist;
}
} }

View file

@ -1,12 +1,15 @@
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML")] [Route("LITTLEBIGPLANETPS3_XML")]
[Produces("text/plain")] [Produces("text/plain")]
public class LevelTagsController : ControllerBase public class LevelTagsController : ControllerBase
@ -34,15 +37,14 @@ public class LevelTagsController : ControllerBase
} }
[HttpPost("tag/{slotType}/{id:int}")] [HttpPost("tag/{slotType}/{id:int}")]
public async Task<IActionResult> PostTag([FromForm] string t, [FromRoute] string slotType, [FromRoute] int id) public async Task<IActionResult> PostTag([FromForm(Name = "t")] string tagName, [FromRoute] string slotType, [FromRoute] int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Where(s => s.SlotId == id).FirstOrDefaultAsync(); Slot? slot = await this.database.Slots.Where(s => s.SlotId == id).FirstOrDefaultAsync();
if (slot == null) return this.BadRequest(); if (slot == null) return this.BadRequest();
if (!LabelHelper.IsValidTag(t)) return this.BadRequest(); if (!LabelHelper.IsValidTag(tagName)) return this.BadRequest();
if (token.UserId == slot.CreatorId) return this.BadRequest(); if (token.UserId == slot.CreatorId) return this.BadRequest();
@ -53,7 +55,7 @@ public class LevelTagsController : ControllerBase
RatedLevel? rating = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.UserId == token.UserId && r.SlotId == slot.SlotId); RatedLevel? rating = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.UserId == token.UserId && r.SlotId == slot.SlotId);
if (rating == null) return this.BadRequest(); if (rating == null) return this.BadRequest();
rating.TagLBP1 = t; rating.TagLBP1 = tagName;
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();

View file

@ -5,12 +5,14 @@ using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class ListController : ControllerBase public class ListController : ControllerBase
@ -37,8 +39,7 @@ public class ListController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -64,8 +65,7 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/add/user/{id:int}")] [HttpPost("lolcatftw/add/user/{id:int}")]
public async Task<IActionResult> AddQueuedLevel(int id) public async Task<IActionResult> AddQueuedLevel(int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
@ -78,8 +78,7 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/remove/user/{id:int}")] [HttpPost("lolcatftw/remove/user/{id:int}")]
public async Task<IActionResult> RemoveQueuedLevel(int id) public async Task<IActionResult> RemoveQueuedLevel(int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
@ -92,8 +91,7 @@ public class ListController : ControllerBase
[HttpPost("lolcatftw/clear")] [HttpPost("lolcatftw/clear")]
public async Task<IActionResult> ClearQueuedLevels() public async Task<IActionResult> ClearQueuedLevels()
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == token.UserId)); this.database.QueuedLevels.RemoveRange(this.database.QueuedLevels.Where(q => q.UserId == token.UserId));
@ -118,8 +116,7 @@ public class ListController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -150,8 +147,7 @@ public class ListController : ControllerBase
[HttpPost("favourite/slot/{slotType}/{id:int}")] [HttpPost("favourite/slot/{slotType}/{id:int}")]
public async Task<IActionResult> AddFavouriteSlot(string slotType, int id) public async Task<IActionResult> AddFavouriteSlot(string slotType, int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
@ -174,8 +170,7 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/slot/{slotType}/{id:int}")] [HttpPost("unfavourite/slot/{slotType}/{id:int}")]
public async Task<IActionResult> RemoveFavouriteSlot(string slotType, int id) public async Task<IActionResult> RemoveFavouriteSlot(string slotType, int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
@ -202,12 +197,9 @@ public class ListController : ControllerBase
[HttpGet("favouritePlaylists/{username}")] [HttpGet("favouritePlaylists/{username}")]
public async Task<IActionResult> GetFavouritePlaylists(string username, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> GetFavouritePlaylists(string username, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
int targetUserId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync(); int targetUserId = await this.database.UserIdFromUsername(username);
if (targetUserId == 0) return this.StatusCode(403, ""); if (targetUserId == 0) return this.StatusCode(403, "");
IEnumerable<Playlist> heartedPlaylists = this.database.HeartedPlaylists.Where(p => p.UserId == targetUserId) IEnumerable<Playlist> heartedPlaylists = this.database.HeartedPlaylists.Where(p => p.UserId == targetUserId)
@ -228,8 +220,7 @@ public class ListController : ControllerBase
[HttpPost("favourite/playlist/{playlistId:int}")] [HttpPost("favourite/playlist/{playlistId:int}")]
public async Task<IActionResult> AddFavouritePlaylist(int playlistId) public async Task<IActionResult> AddFavouritePlaylist(int playlistId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Playlist? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId); Playlist? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId);
if (playlist == null) return this.NotFound(); if (playlist == null) return this.NotFound();
@ -242,8 +233,7 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/playlist/{playlistId:int}")] [HttpPost("unfavourite/playlist/{playlistId:int}")]
public async Task<IActionResult> RemoveFavouritePlaylist(int playlistId) public async Task<IActionResult> RemoveFavouritePlaylist(int playlistId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Playlist? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId); Playlist? playlist = await this.database.Playlists.FirstOrDefaultAsync(s => s.PlaylistId == playlistId);
if (playlist == null) return this.NotFound(); if (playlist == null) return this.NotFound();
@ -262,8 +252,7 @@ public class ListController : ControllerBase
[HttpGet("favouriteUsers/{username}")] [HttpGet("favouriteUsers/{username}")]
public async Task<IActionResult> GetFavouriteUsers(string username, [FromQuery] int pageSize, [FromQuery] int pageStart) public async Task<IActionResult> GetFavouriteUsers(string username, [FromQuery] int pageSize, [FromQuery] int pageStart)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
User? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); User? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (targetUser == null) return this.StatusCode(403, ""); if (targetUser == null) return this.StatusCode(403, "");
@ -295,8 +284,7 @@ public class ListController : ControllerBase
[HttpPost("favourite/user/{username}")] [HttpPost("favourite/user/{username}")]
public async Task<IActionResult> AddFavouriteUser(string username) public async Task<IActionResult> AddFavouriteUser(string username)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();
@ -309,8 +297,7 @@ public class ListController : ControllerBase
[HttpPost("unfavourite/user/{username}")] [HttpPost("unfavourite/user/{username}")]
public async Task<IActionResult> RemoveFavouriteUser(string username) public async Task<IActionResult> RemoveFavouriteUser(string username)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (heartedUser == null) return this.NotFound(); if (heartedUser == null) return this.NotFound();

View file

@ -1,6 +1,6 @@
#nullable enable #nullable enable
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
@ -8,12 +8,14 @@ using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class PublishController : ControllerBase public class PublishController : ControllerBase
@ -31,15 +33,12 @@ public class PublishController : ControllerBase
[HttpPost("startPublish")] [HttpPost("startPublish")]
public async Task<IActionResult> StartPublish() public async Task<IActionResult> StartPublish()
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (userAndToken == null) return this.StatusCode(403, ""); User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException Slot? slot = await this.DeserializeBody<Slot>();
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
Slot? slot = await this.getSlotFromBody();
if (slot == null) if (slot == null)
{ {
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish); Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
@ -69,7 +68,7 @@ public class PublishController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
} }
else if (user.GetUsedSlotsForGame(gameToken.GameVersion) > user.EntitledSlots) else if (user.GetUsedSlotsForGame(token.GameVersion) > user.EntitledSlots)
{ {
return this.StatusCode(403, ""); return this.StatusCode(403, "");
} }
@ -89,14 +88,12 @@ public class PublishController : ControllerBase
[HttpPost("publish")] [HttpPost("publish")]
public async Task<IActionResult> Publish([FromQuery] string? game) public async Task<IActionResult> Publish([FromQuery] string? game)
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (userAndToken == null) return this.StatusCode(403, ""); User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException Slot? slot = await this.DeserializeBody<Slot>();
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
Slot? slot = await this.getSlotFromBody();
if (slot == null) if (slot == null)
{ {
@ -156,7 +153,7 @@ public class PublishController : ControllerBase
GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel); GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel);
slot.GameVersion = slotVersion; slot.GameVersion = slotVersion;
if (slotVersion == GameVersion.Unknown) slot.GameVersion = gameToken.GameVersion; if (slotVersion == GameVersion.Unknown) slot.GameVersion = token.GameVersion;
slot.AuthorLabels = LabelHelper.RemoveInvalidLabels(slot.AuthorLabels); slot.AuthorLabels = LabelHelper.RemoveInvalidLabels(slot.AuthorLabels);
@ -185,7 +182,7 @@ public class PublishController : ControllerBase
if (intendedVersion != GameVersion.Unknown && intendedVersion != slotVersion) if (intendedVersion != GameVersion.Unknown && intendedVersion != slotVersion)
{ {
// Delete the useless rootLevel that lbp3 just uploaded // Delete the useless rootLevel that lbp3 just uploaded
if(slotVersion == GameVersion.LittleBigPlanet3) if (slotVersion == GameVersion.LittleBigPlanet3)
FileHelper.DeleteResource(slot.RootLevel); FileHelper.DeleteResource(slot.RootLevel);
slot.GameVersion = oldSlot.GameVersion; slot.GameVersion = oldSlot.GameVersion;
@ -230,7 +227,7 @@ public class PublishController : ControllerBase
this.database.Entry(oldSlot).CurrentValues.SetValues(slot); this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(oldSlot.Serialize(gameToken.GameVersion)); return this.Ok(oldSlot.Serialize(token.GameVersion));
} }
if (user.GetUsedSlotsForGame(slotVersion) > user.EntitledSlots) if (user.GetUsedSlotsForGame(slotVersion) > user.EntitledSlots)
@ -269,14 +266,13 @@ public class PublishController : ControllerBase
Logger.Success($"Successfully published level {slot.Name} (id: {slot.SlotId}) by {user.Username} (id: {user.UserId})", LogArea.Publish); Logger.Success($"Successfully published level {slot.Name} (id: {slot.SlotId}) by {user.Username} (id: {user.UserId})", LogArea.Publish);
return this.Ok(slot.Serialize(gameToken.GameVersion)); return this.Ok(slot.Serialize(token.GameVersion));
} }
[HttpPost("unpublish/{id:int}")] [HttpPost("unpublish/{id:int}")]
public async Task<IActionResult> Unpublish(int id) public async Task<IActionResult> Unpublish(int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
@ -305,17 +301,4 @@ public class PublishController : ControllerBase
_ => GameVersion.Unknown, _ => GameVersion.Unknown,
}; };
} }
private async Task<Slot?> getSlotFromBody()
{
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Slot));
Slot? slot = (Slot?)serializer.Deserialize(new StringReader(bodyString));
SanitizationHelper.SanitizeStringsInClass(slot);
return slot;
}
} }

View file

@ -1,5 +1,4 @@
#nullable enable #nullable enable
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Administration; using LBPUnion.ProjectLighthouse.Administration;
using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
@ -7,12 +6,14 @@ using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Reviews; using LBPUnion.ProjectLighthouse.PlayerData.Reviews;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class ReviewController : ControllerBase public class ReviewController : ControllerBase
@ -28,8 +29,7 @@ public class ReviewController : ControllerBase
[HttpPost("rate/user/{slotId:int}")] [HttpPost("rate/user/{slotId:int}")]
public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating) public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId); Slot? slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.StatusCode(403, "");
@ -58,8 +58,7 @@ public class ReviewController : ControllerBase
[HttpPost("dpadrate/user/{slotId:int}")] [HttpPost("dpadrate/user/{slotId:int}")]
public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating) public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId); Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, ""); if (slot == null) return this.StatusCode(403, "");
@ -90,10 +89,9 @@ public class ReviewController : ControllerBase
[HttpPost("postReview/user/{slotId:int}")] [HttpPost("postReview/user/{slotId:int}")]
public async Task<IActionResult> PostReview(int slotId) public async Task<IActionResult> PostReview(int slotId)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
Review? newReview = await this.getReviewFromBody(); Review? newReview = await this.DeserializeBody<Review>();
if (newReview == null) return this.BadRequest(); if (newReview == null) return this.BadRequest();
if (newReview.Text.Length > 512) return this.BadRequest(); if (newReview.Text.Length > 512) return this.BadRequest();
@ -143,8 +141,7 @@ public class ReviewController : ControllerBase
[HttpGet("reviewsFor/user/{slotId:int}")] [HttpGet("reviewsFor/user/{slotId:int}")]
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10) public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -195,14 +192,13 @@ public class ReviewController : ControllerBase
[HttpGet("reviewsBy/{username}")] [HttpGet("reviewsBy/{username}")]
public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10) public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
int targetUserId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync(); int targetUserId = await this.database.UserIdFromUsername(username);
if (targetUserId == 0) return this.BadRequest(); if (targetUserId == 0) return this.BadRequest();
@ -249,10 +245,9 @@ public class ReviewController : ControllerBase
[HttpPost("rateReview/user/{slotId:int}/{username}")] [HttpPost("rateReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0) public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
int reviewerId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync(); int reviewerId = await this.database.UserIdFromUsername(username);
if (reviewerId == 0) return this.StatusCode(400, ""); if (reviewerId == 0) return this.StatusCode(400, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId); Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId);
@ -303,15 +298,14 @@ public class ReviewController : ControllerBase
[HttpPost("deleteReview/user/{slotId:int}/{username}")] [HttpPost("deleteReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> DeleteReview(int slotId, string username) public async Task<IActionResult> DeleteReview(int slotId, string username)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
int creatorId = await this.database.Slots.Where(s => s.SlotId == slotId).Select(s => s.CreatorId).FirstOrDefaultAsync(); int creatorId = await this.database.Slots.Where(s => s.SlotId == slotId).Select(s => s.CreatorId).FirstOrDefaultAsync();
if (creatorId == 0) return this.StatusCode(400, ""); if (creatorId == 0) return this.StatusCode(400, "");
if (token.UserId != creatorId) return this.StatusCode(403, ""); if (token.UserId != creatorId) return this.StatusCode(403, "");
int reviewerId = await this.database.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync(); int reviewerId = await this.database.UserIdFromUsername(username);
if (reviewerId == 0) return this.StatusCode(400, ""); if (reviewerId == 0) return this.StatusCode(400, "");
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId); Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == reviewerId);
@ -323,15 +317,4 @@ public class ReviewController : ControllerBase
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Ok(); return this.Ok();
} }
private async Task<Review?> getReviewFromBody()
{
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Review));
Review? review = (Review?)serializer.Deserialize(new StringReader(bodyString));
SanitizationHelper.SanitizeStringsInClass(review);
return review;
}
} }

View file

@ -1,6 +1,7 @@
#nullable enable #nullable enable
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Xml.Serialization; using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
@ -30,8 +31,7 @@ public class ScoreController : ControllerBase
[HttpPost("scoreboard/{slotType}/{id:int}/{childId:int}")] [HttpPost("scoreboard/{slotType}/{id:int}/{childId:int}")]
public async Task<IActionResult> SubmitScore(string slotType, int id, int childId, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false) public async Task<IActionResult> SubmitScore(string slotType, int id, int childId, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
string username = await this.database.UsernameFromGameToken(token); string username = await this.database.UsernameFromGameToken(token);
@ -41,11 +41,7 @@ public class ScoreController : ControllerBase
return this.BadRequest(); return this.BadRequest();
} }
this.Request.Body.Position = 0; Score? score = await this.DeserializeBody<Score>();
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Score));
Score? score = (Score?)serializer.Deserialize(new StringReader(bodyString));
if (score == null) if (score == null)
{ {
Logger.Warn($"Rejecting score upload, score is null (slotType={slotType}, slotId={id}, user={username})", LogArea.Score); Logger.Warn($"Rejecting score upload, score is null (slotType={slotType}, slotId={id}, user={username})", LogArea.Score);
@ -157,8 +153,7 @@ public class ScoreController : ControllerBase
[HttpGet("friendscores/{slotType}/{slotId:int}/{childId:int}/{type:int}")] [HttpGet("friendscores/{slotType}/{slotId:int}/{childId:int}/{type:int}")]
public async Task<IActionResult> FriendScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) public async Task<IActionResult> FriendScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -189,8 +184,7 @@ public class ScoreController : ControllerBase
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public async Task<IActionResult> TopScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) public async Task<IActionResult> TopScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();

View file

@ -3,12 +3,14 @@ using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/slots")] [Route("LITTLEBIGPLANETPS3_XML/slots")]
[Produces("text/xml")] [Produces("text/xml")]
public class SearchController : ControllerBase public class SearchController : ControllerBase
@ -31,8 +33,7 @@ public class SearchController : ControllerBase
string? keyName = "slots" string? keyName = "slots"
) )
{ {
GameToken? gameToken = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (gameToken == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -42,7 +43,7 @@ public class SearchController : ControllerBase
string[] keywords = query.Split(" "); string[] keywords = query.Split(" ");
IQueryable<Slot> dbQuery = this.database.Slots.ByGameVersion(gameToken.GameVersion, false, true) IQueryable<Slot> dbQuery = this.database.Slots.ByGameVersion(token.GameVersion, false, true)
.Where(s => s.Type == SlotType.User) .Where(s => s.Type == SlotType.User)
.OrderBy(s => !s.TeamPick) .OrderBy(s => !s.TeamPick)
.ThenByDescending(s => s.FirstUploaded) .ThenByDescending(s => s.FirstUploaded)
@ -60,7 +61,7 @@ public class SearchController : ControllerBase
List<Slot> slots = await dbQuery.Skip(Math.Max(0, pageStart - 1)).Take(Math.Min(pageSize, 30)).ToListAsync(); List<Slot> slots = await dbQuery.Skip(Math.Max(0, pageStart - 1)).Take(Math.Min(pageSize, 30)).ToListAsync();
string response = slots.Aggregate("", (current, slot) => current + slot.Serialize(gameToken.GameVersion)); string response = slots.Aggregate("", (current, slot) => current + slot.Serialize(token.GameVersion));
return this.Ok(LbpSerializer.TaggedStringElement(keyName, response, "total", dbQuery.Count())); return this.Ok(LbpSerializer.TaggedStringElement(keyName, response, "total", dbQuery.Count()));
} }

View file

@ -8,12 +8,14 @@ using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.PlayerData.Reviews; using LBPUnion.ProjectLighthouse.PlayerData.Reviews;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class SlotsController : ControllerBase public class SlotsController : ControllerBase
@ -38,38 +40,36 @@ public class SlotsController : ControllerBase
}); });
[HttpGet("slots/by")] [HttpGet("slots/by")]
public async Task<IActionResult> SlotsBy([FromQuery] string u, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> SlotsBy([FromQuery(Name="u")] string username, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
User? targetUser = await this.database.Users.Where(dbUser => dbUser.Username == u).FirstOrDefaultAsync(); int targetUserId = await this.database.UserIdFromUsername(username);
if (targetUser == null) return this.NotFound(); if (targetUserId == 0) return this.NotFound();
int usedSlots = this.database.Slots.Count(s => s.CreatorId == targetUserId);
string response = Enumerable.Aggregate string response = Enumerable.Aggregate
( (
this.database.Slots.ByGameVersion(gameVersion, token.UserId == targetUser.UserId, true) this.database.Slots.Where(s => s.CreatorId == targetUserId)
.Where(s => s.CreatorId == targetUser.UserId) .ByGameVersion(gameVersion, token.UserId == targetUserId, true)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, targetUser.UsedSlots)), .Take(Math.Min(pageSize, usedSlots)),
string.Empty, string.Empty,
(current, slot) => current + slot.Serialize(token.GameVersion) (current, slot) => current + slot.Serialize(token.GameVersion)
); );
int start = pageStart + Math.Min(pageSize, targetUser.UsedSlots); int start = pageStart + Math.Min(pageSize, usedSlots);
int total = await this.database.Slots.CountAsync(s => s.CreatorId == targetUser.UserId); int total = await this.database.Slots.CountAsync(s => s.CreatorId == targetUserId);
return this.Ok(generateSlotsResponse(response, start, total)); return this.Ok(generateSlotsResponse(response, start, total));
} }
[HttpGet("slotList")] [HttpGet("slotList")]
public async Task<IActionResult> GetSlotListAlt([FromQuery] int[] s) public async Task<IActionResult> GetSlotListAlt([FromQuery] int[] s)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
List<string?> serializedSlots = new(); List<string?> serializedSlots = new();
foreach (int slotId in s) foreach (int slotId in s)
{ {
@ -93,9 +93,6 @@ public class SlotsController : ControllerBase
[HttpGet("slots/developer")] [HttpGet("slots/developer")]
public async Task<IActionResult> StoryPlayers() public async Task<IActionResult> StoryPlayers()
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
List<int> activeSlotIds = RoomHelper.Rooms.Where(r => r.Slot.SlotType == SlotType.Developer).Select(r => r.Slot.SlotId).ToList(); List<int> activeSlotIds = RoomHelper.Rooms.Where(r => r.Slot.SlotType == SlotType.Developer).Select(r => r.Slot.SlotId).ToList();
List<string> serializedSlots = new(); List<string> serializedSlots = new();
@ -115,9 +112,6 @@ public class SlotsController : ControllerBase
[HttpGet("s/developer/{id:int}")] [HttpGet("s/developer/{id:int}")]
public async Task<IActionResult> SDev(int id) public async Task<IActionResult> SDev(int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
int slotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); int slotId = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == slotId); Slot slot = await this.database.Slots.FirstAsync(s => s.SlotId == slotId);
@ -127,8 +121,7 @@ public class SlotsController : ControllerBase
[HttpGet("s/user/{id:int}")] [HttpGet("s/user/{id:int}")]
public async Task<IActionResult> SUser(int id) public async Task<IActionResult> SUser(int id)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
@ -168,8 +161,7 @@ public class SlotsController : ControllerBase
[HttpGet("slots")] [HttpGet("slots")]
public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -189,8 +181,7 @@ public class SlotsController : ControllerBase
[HttpGet("slots/like/{slotType}/{slotId:int}")] [HttpGet("slots/like/{slotType}/{slotId:int}")]
public async Task<IActionResult> SimilarSlots([FromRoute] string slotType, [FromRoute] int slotId, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> SimilarSlots([FromRoute] string slotType, [FromRoute] int slotId, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -225,8 +216,7 @@ public class SlotsController : ControllerBase
[HttpGet("slots/highestRated")] [HttpGet("slots/highestRated")]
public async Task<IActionResult> HighestRatedSlots([FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> HighestRatedSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -248,8 +238,7 @@ public class SlotsController : ControllerBase
[HttpGet("slots/tag")] [HttpGet("slots/tag")]
public async Task<IActionResult> SimilarSlots([FromQuery] string tag, [FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> SimilarSlots([FromQuery] string tag, [FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -260,8 +249,8 @@ public class SlotsController : ControllerBase
.Select(s => s.SlotId) .Select(s => s.SlotId)
.ToListAsync(); .ToListAsync();
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true) IQueryable<Slot> slots = this.database.Slots.Where(s => slotIdsWithTag.Contains(s.SlotId))
.Where(s => slotIdsWithTag.Contains(s.SlotId)) .ByGameVersion(gameVersion, false, true)
.OrderByDescending(s => s.PlaysLBP1) .OrderByDescending(s => s.PlaysLBP1)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30));
@ -276,15 +265,14 @@ public class SlotsController : ControllerBase
[HttpGet("slots/mmpicks")] [HttpGet("slots/mmpicks")]
public async Task<IActionResult> TeamPickedSlots([FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> TeamPickedSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
GameVersion gameVersion = token.GameVersion; GameVersion gameVersion = token.GameVersion;
IQueryable<Slot> slots = this.database.Slots.ByGameVersion(gameVersion, false, true) IQueryable<Slot> slots = this.database.Slots.Where(s => s.TeamPick)
.Where(s => s.TeamPick) .ByGameVersion(gameVersion, false, true)
.OrderByDescending(s => s.LastUpdated) .OrderByDescending(s => s.LastUpdated)
.Skip(Math.Max(0, pageStart - 1)) .Skip(Math.Max(0, pageStart - 1))
.Take(Math.Min(pageSize, 30)); .Take(Math.Min(pageSize, 30));
@ -298,8 +286,7 @@ public class SlotsController : ControllerBase
[HttpGet("slots/lbp2luckydip")] [HttpGet("slots/lbp2luckydip")]
public async Task<IActionResult> LuckyDipSlots([FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] int seed) public async Task<IActionResult> LuckyDipSlots([FromQuery] int pageStart, [FromQuery] int pageSize, [FromQuery] int seed)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -325,8 +312,7 @@ public class SlotsController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -357,8 +343,7 @@ public class SlotsController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -403,8 +388,7 @@ public class SlotsController : ControllerBase
[FromQuery] string? dateFilterType = null [FromQuery] string? dateFilterType = null
) )
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -435,8 +419,7 @@ public class SlotsController : ControllerBase
[FromQuery] bool? move = null [FromQuery] bool? move = null
) )
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
if (pageSize <= 0) return this.BadRequest(); if (pageSize <= 0) return this.BadRequest();
@ -445,7 +428,7 @@ public class SlotsController : ControllerBase
foreach (Room room in RoomHelper.Rooms) foreach (Room room in RoomHelper.Rooms)
{ {
// TODO: support developer slotTypes? // TODO: support developer slotTypes?
if(room.Slot.SlotType != SlotType.User) continue; if (room.Slot.SlotType != SlotType.User) continue;
if (!playersBySlotId.TryGetValue(room.Slot.SlotId, out int playerCount)) if (!playersBySlotId.TryGetValue(room.Slot.SlotId, out int playerCount))
playersBySlotId.Add(room.Slot.SlotId, 0); playersBySlotId.Add(room.Slot.SlotId, 0);
@ -468,7 +451,7 @@ public class SlotsController : ControllerBase
{ {
Slot? slot = await this.database.Slots.ByGameVersion(token.GameVersion, false, true) Slot? slot = await this.database.Slots.ByGameVersion(token.GameVersion, false, true)
.FirstOrDefaultAsync(s => s.SlotId == slotId); .FirstOrDefaultAsync(s => s.SlotId == slotId);
if(slot == null) continue; // shouldn't happen ever unless the room is borked if (slot == null) continue; // shouldn't happen ever unless the room is borked
slots.Add(slot); slots.Add(slot);
} }

View file

@ -1,10 +1,12 @@
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")] [Produces("text/plain")]
public class StatisticsController : ControllerBase public class StatisticsController : ControllerBase

View file

@ -1,8 +1,10 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class StoreController : Controller public class StoreController : Controller

View file

@ -1,18 +1,20 @@
#nullable enable #nullable enable
using System.Text.Json; using System.Text.Json;
using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController] [ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")] [Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")] [Produces("text/xml")]
public class UserController : ControllerBase public class UserController : ControllerBase
@ -34,14 +36,11 @@ public class UserController : ControllerBase
{ {
// use an anonymous type to only fetch certain columns // use an anonymous type to only fetch certain columns
var partialUser = await this.database.Users.Where(u => u.Username == username) var partialUser = await this.database.Users.Where(u => u.Username == username)
.Select .Select(u => new
( {
u => new u.Username,
{ u.IconHash,
u.Username, })
u.IconHash,
}
)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (partialUser == null) return null; if (partialUser == null) return null;
@ -52,8 +51,7 @@ public class UserController : ControllerBase
[HttpGet("user/{username}")] [HttpGet("user/{username}")]
public async Task<IActionResult> GetUser(string username) public async Task<IActionResult> GetUser(string username)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (token == null) return this.StatusCode(403, "");
string? user = await this.getSerializedUser(username, token.GameVersion); string? user = await this.getSerializedUser(username, token.GameVersion);
if (user == null) return this.NotFound(); if (user == null) return this.NotFound();
@ -64,9 +62,6 @@ public class UserController : ControllerBase
[HttpGet("users")] [HttpGet("users")]
public async Task<IActionResult> GetUserAlt([FromQuery] string[] u) public async Task<IActionResult> GetUserAlt([FromQuery] string[] u)
{ {
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
List<string?> serializedUsers = new(); List<string?> serializedUsers = new();
foreach (string userId in u) serializedUsers.Add(await this.getSerializedUserPicture(userId)); foreach (string userId in u) serializedUsers.Add(await this.getSerializedUserPicture(userId));
@ -78,20 +73,12 @@ public class UserController : ControllerBase
[HttpPost("updateUser")] [HttpPost("updateUser")]
public async Task<IActionResult> UpdateUser() public async Task<IActionResult> UpdateUser()
{ {
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); GameToken token = this.GetToken();
if (userAndToken == null) return this.StatusCode(403, ""); User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, "");
// ReSharper disable once PossibleInvalidOperationException UserUpdate? update = await this.DeserializeBody<UserUpdate>("updateUser", "user");
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
// xml hack so we can use one class to deserialize different root names
string rootElement = bodyString.Contains("updateUser") ? "updateUser" : "user";
XmlSerializer serializer = new(typeof(UserUpdate), new XmlRootAttribute(rootElement));
UserUpdate? update = (UserUpdate?)serializer.Deserialize(new StringReader(bodyString));
if (update == null) return this.BadRequest(); if (update == null) return this.BadRequest();
@ -133,7 +120,7 @@ public class UserController : ControllerBase
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == updateSlot.SlotId); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == updateSlot.SlotId);
if (slot == null) continue; if (slot == null) continue;
if (slot.CreatorId != gameToken.UserId) continue; if (slot.CreatorId != token.UserId) continue;
Location? loc = await this.database.Locations.FirstOrDefaultAsync(l => l.Id == slot.LocationId); Location? loc = await this.database.Locations.FirstOrDefaultAsync(l => l.Id == slot.LocationId);
@ -146,7 +133,7 @@ public class UserController : ControllerBase
if (update.PlanetHash != null) if (update.PlanetHash != null)
{ {
switch (gameToken.GameVersion) switch (token.GameVersion)
{ {
case GameVersion.LittleBigPlanet2: // LBP2 planets will apply to LBP3 case GameVersion.LittleBigPlanet2: // LBP2 planets will apply to LBP3
{ {
@ -169,7 +156,7 @@ public class UserController : ControllerBase
case GameVersion.Unknown: case GameVersion.Unknown:
default: // The rest do not support custom earths. default: // The rest do not support custom earths.
{ {
throw new ArgumentException($"invalid gameVersion {gameToken.GameVersion} for setting earth"); throw new ArgumentException($"invalid gameVersion {token.GameVersion} for setting earth");
} }
} }
} }
@ -190,7 +177,7 @@ public class UserController : ControllerBase
[HttpPost("update_my_pins")] [HttpPost("update_my_pins")]
public async Task<IActionResult> UpdateMyPins() public async Task<IActionResult> UpdateMyPins()
{ {
User? user = await this.database.UserFromGameRequest(this.Request); User? user = await this.database.UserFromGameToken(this.GetToken());
if (user == null) return this.StatusCode(403, ""); if (user == null) return this.StatusCode(403, "");
string pinsString = await new StreamReader(this.Request.Body).ReadToEndAsync(); string pinsString = await new StreamReader(this.Request.Body).ReadToEndAsync();

View file

@ -3,6 +3,9 @@ using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Middlewares; using LBPUnion.ProjectLighthouse.Middlewares;
using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares; using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Startup; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Startup;
@ -21,6 +24,19 @@ public class GameServerStartup
{ {
services.AddControllers(); services.AddControllers();
services.AddAuthentication(options =>
{
options.DefaultScheme = "tokenAuth";
options.AddScheme<TokenAuthHandler>("tokenAuth", null);
});
services.AddAuthorization(o =>
{
AuthorizationPolicyBuilder builder = new("tokenAuth");
builder = builder.RequireClaim("userId");
o.DefaultPolicy = builder.Build();
});
services.AddMvc services.AddMvc
( (
options => options =>
@ -64,12 +80,14 @@ public class GameServerStartup
app.UseForwardedHeaders(); app.UseForwardedHeaders();
app.UseMiddleware<RequestLogMiddleware>(); app.UseMiddleware<RequestLogMiddleware>();
app.UseMiddleware<RateLimitMiddleware>();
app.UseMiddleware<DigestMiddleware>(computeDigests); app.UseMiddleware<DigestMiddleware>(computeDigests);
app.UseMiddleware<SetLastContactMiddleware>(); app.UseMiddleware<SetLastContactMiddleware>();
app.UseMiddleware<RateLimitMiddleware>();
app.UseRouting(); app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers()); app.UseEndpoints(endpoints => endpoints.MapControllers());
app.UseEndpoints(endpoints => endpoints.MapRazorPages()); app.UseEndpoints(endpoints => endpoints.MapRazorPages());
} }

View file

@ -0,0 +1,49 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using LBPUnion.ProjectLighthouse.PlayerData;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Startup;
public class TokenAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly Database database;
private const string cookie = "MM_AUTH";
public TokenAuthHandler
(
IOptionsMonitor<AuthenticationSchemeOptions> options,
UrlEncoder encoder,
ISystemClock clock,
Database database
// I said I don't want any damn vegetables (logs)
) : base(options, new NullLoggerFactory(), encoder, clock)
{
this.database = database;
}
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
{
this.Context.Response.StatusCode = 403;
return Task.CompletedTask;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!this.Context.Request.Cookies.ContainsKey(cookie)) return AuthenticateResult.Fail("No auth cookie");
GameToken? gameToken = await this.database.GameTokenFromRequest(this.Request);
if (gameToken == null) return AuthenticateResult.Fail("No game token");
this.Context.Items["Token"] = gameToken;
Claim[] claims = {
new("userId", gameToken.UserId.ToString()),
};
ClaimsIdentity identity = new(claims, this.Scheme.Name);
ClaimsPrincipal principal = new(identity);
AuthenticationTicket ticket = new(principal, this.Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}

View file

@ -1,7 +1,6 @@
#nullable enable #nullable enable
using LBPUnion.ProjectLighthouse.Administration.Reports; using LBPUnion.ProjectLighthouse.Administration.Reports;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -32,7 +31,7 @@ public class AdminReportController : ControllerBase
report.JpegHash, report.JpegHash,
report.GriefStateHash, report.GriefStateHash,
}; };
if(report.LevelType != "user") if (report.LevelType != "user")
hashes.Add(report.InitialStateHash); hashes.Add(report.InitialStateHash);
foreach (string hash in hashes) foreach (string hash in hashes)
{ {

View file

@ -27,12 +27,14 @@ public class ModerationSlotController : ControllerBase
Slot? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.Include(s => s.Creator).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
slot.TeamPick = true; slot.TeamPick = true;
// Send webhook with slot.Name and slot.Creator.Username // Send webhook with slot.Name and slot.Creator.Username
await WebhookHelper.SendWebhook("New Team Pick!", $"The level [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId}) by **{slot.Creator?.Username}** has been team picked"); await WebhookHelper.SendWebhook("New Team Pick!", $"The level [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId}) by **{slot.Creator?.Username}** has been team picked");
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Redirect("~/slot/" + id); return this.Redirect("~/slot/" + id);
} }
@ -44,9 +46,11 @@ public class ModerationSlotController : ControllerBase
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id); Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound(); if (slot == null) return this.NotFound();
slot.TeamPick = false; slot.TeamPick = false;
await this.database.SaveChangesAsync(); await this.database.SaveChangesAsync();
return this.Redirect("~/slot/" + id); return this.Redirect("~/slot/" + id);
} }

View file

@ -14,10 +14,9 @@ public class ResourcesController : ControllerBase
string path = FileHelper.GetImagePath($"{hash}.png"); string path = FileHelper.GetImagePath($"{hash}.png");
string fullPath = Path.GetFullPath(path); string fullPath = Path.GetFullPath(path);
string basePath = Path.GetFullPath(FileHelper.ImagePath);
// Prevent directory traversal attacks // Prevent directory traversal attacks
if (!fullPath.StartsWith(basePath)) return this.BadRequest(); if (!fullPath.StartsWith(FileHelper.FullImagePath)) return this.BadRequest();
if (IOFile.Exists(path)) return this.File(IOFile.OpenRead(path), "image/png"); if (IOFile.Exists(path)) return this.File(IOFile.OpenRead(path), "image/png");

View file

@ -1,7 +1,6 @@
@page "/admin/users" @page "/admin/users"
@using LBPUnion.ProjectLighthouse.Administration @using LBPUnion.ProjectLighthouse.Administration
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles @using LBPUnion.ProjectLighthouse.PlayerData.Profiles
@using LBPUnion.ProjectLighthouse.Types
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin.AdminPanelUsersPage @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin.AdminPanelUsersPage
@{ @{
@ -61,7 +60,7 @@
<select name="role" class="ui selection dropdown"> <select name="role" class="ui selection dropdown">
@foreach (PermissionLevel level in Enum.GetValues<PermissionLevel>()) @foreach (PermissionLevel level in Enum.GetValues<PermissionLevel>())
{ {
if(level < 0) continue; if (level < 0) continue;
string selected = level == user.PermissionLevel ? " selected" : ""; string selected = level == user.PermissionLevel ? " selected" : "";
<option value="@((int)level)"@selected>@level.ToString()</option> <option value="@((int)level)"@selected>@level.ToString()</option>

View file

@ -63,14 +63,18 @@ public class LoginForm : BaseLayout
if (user == null) if (user == null)
{ {
Logger.Warn($"User {username} failed to login on web due to invalid username", LogArea.Login); Logger.Warn($"User {username} failed to login on web due to invalid username", LogArea.Login);
this.Error = "The username or password you entered is invalid."; this.Error = ServerConfiguration.Instance.Mail.MailEnabled
? "The email or password you entered is invalid."
: "The username or password you entered is invalid.";
return this.Page(); return this.Page();
} }
if (!BCrypt.Net.BCrypt.Verify(password, user.Password)) if (!BCrypt.Net.BCrypt.Verify(password, user.Password))
{ {
Logger.Warn($"User {user.Username} (id: {user.UserId}) failed to login on web due to invalid password", LogArea.Login); Logger.Warn($"User {user.Username} (id: {user.UserId}) failed to login on web due to invalid password", LogArea.Login);
this.Error = "The username or password you entered is invalid."; this.Error = ServerConfiguration.Instance.Mail.MailEnabled
? "The email or password you entered is invalid."
: "The username or password you entered is invalid.";
return this.Page(); return this.Page();
} }

View file

@ -14,7 +14,7 @@
// Technically, this should never happen but I'm going to handle it anyways. // Technically, this should never happen but I'm going to handle it anyways.
if (!Model.User.IsModerator) if (!Model.User.IsModerator)
{ {
if(Debugger.IsAttached) Debugger.Break(); if (Debugger.IsAttached) Debugger.Break();
throw new Exception("Tried to render mod panel with user whose not mod somehow???"); throw new Exception("Tried to render mod panel with user whose not mod somehow???");
} }
} }

View file

@ -95,7 +95,7 @@
@if (Model.Creator != null) @if (Model.Creator != null)
{ {
string date = ""; string date = "";
if(!mini) if (!mini)
date = " on " + TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeMilliseconds(Model.FirstUploaded), timeZoneInfo).DateTime.ToShortDateString(); date = " on " + TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeMilliseconds(Model.FirstUploaded), timeZoneInfo).DateTime.ToShortDateString();
<p> <p>
<i>Created by @await Model.Creator.ToLink(Html, ViewData, language) in @Model.GameVersion.ToPrettyString()@date</i> <i>Created by @await Model.Creator.ToLink(Html, ViewData, language) in @Model.GameVersion.ToPrettyString()@date</i>

View file

@ -69,7 +69,7 @@ public class SendVerificationEmailPage : BaseLayout
} }
string? existingToken = await this.Database.EmailVerificationTokens.Where(v => v.UserId == user.UserId).Select(v => v.EmailToken).FirstOrDefaultAsync(); string? existingToken = await this.Database.EmailVerificationTokens.Where(v => v.UserId == user.UserId).Select(v => v.EmailToken).FirstOrDefaultAsync();
if(existingToken != null) if (existingToken != null)
this.Database.EmailVerificationTokens.RemoveWhere(t => t.EmailToken == existingToken); this.Database.EmailVerificationTokens.RemoveWhere(t => t.EmailToken == existingToken);
EmailVerificationToken verifyToken = new() EmailVerificationToken verifyToken = new()

View file

@ -54,7 +54,7 @@ public class SlotSettingsPage : BaseLayout
if (this.User == null) return this.Redirect("~/slot/" + slotId); if (this.User == null) return this.Redirect("~/slot/" + slotId);
if(!this.User.IsModerator && this.User.UserId != this.Slot.CreatorId) return this.Redirect("~/slot/" + slotId); if (!this.User.IsModerator && this.User.UserId != this.Slot.CreatorId) return this.Redirect("~/slot/" + slotId);
return this.Page(); return this.Page();
} }

View file

@ -53,7 +53,7 @@ public class UserPage : BaseLayout
this.Photos = await this.Database.Photos.Include(p => p.Slot).OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(6).ToListAsync(); this.Photos = await this.Database.Photos.Include(p => p.Slot).OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(6).ToListAsync();
this.CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && this.ProfileUser.CommentsEnabled; this.CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && this.ProfileUser.CommentsEnabled;
if(this.CommentsEnabled) if (this.CommentsEnabled)
{ {
this.Comments = await this.Database.Comments.Include(p => p.Poster) this.Comments = await this.Database.Comments.Include(p => p.Poster)
.OrderByDescending(p => p.Timestamp) .OrderByDescending(p => p.Timestamp)

View file

@ -21,7 +21,7 @@
<script> <script>
function onSubmit(e){ function onSubmit(e){
document.getElementById("avatar-encoded").value = selectedAvatar.toString(); document.getElementById("avatar-encoded").value = selectedAvatar.toString();
@if(ServerConfiguration.Instance.Mail.MailEnabled){ @if (ServerConfiguration.Instance.Mail.MailEnabled){
<text> <text>
let newEmail = document.getElementById("email").value; let newEmail = document.getElementById("email").value;
if (newEmail.length === 0){ if (newEmail.length === 0){

View file

@ -79,7 +79,7 @@ public class UserSettingsPage : BaseLayout
if (this.User == null) return this.Redirect("~/user/" + userId); if (this.User == null) return this.Redirect("~/user/" + userId);
if(!this.User.IsModerator && this.User != this.ProfileUser) return this.Redirect("~/user/" + userId); if (!this.User.IsModerator && this.User != this.ProfileUser) return this.Redirect("~/user/" + userId);
return this.Page(); return this.Page();
} }

View file

@ -57,9 +57,6 @@ public class SlotTests : LighthouseServerTest<GameServerTestStartup>
await database.SaveChangesAsync(); await database.SaveChangesAsync();
// XmlSerializer serializer = new(typeof(Slot));
// Slot slot = (Slot)serializer.Deserialize(new StringReader(bodyString));
LoginResult loginResult = await this.Authenticate(); LoginResult loginResult = await this.Authenticate();
HttpResponseMessage respMessageA = await this.AuthenticatedRequest HttpResponseMessage respMessageA = await this.AuthenticatedRequest

View file

@ -89,7 +89,7 @@ public static class MaintenanceHelper
exception = e; exception = e;
} }
if(!success) if (!success)
{ {
Logger.Error($"Could not run migration {migrationTask.Name()}", LogArea.Database); Logger.Error($"Could not run migration {migrationTask.Name()}", LogArea.Database);
if (exception != null) Logger.Error(exception.ToDetailedException(), LogArea.Database); if (exception != null) Logger.Error(exception.ToDetailedException(), LogArea.Database);

View file

@ -31,7 +31,7 @@ public class PerformCaseActionsTask : IRepeatingTask
continue; continue;
} }
} }
else if(@case.Type.AffectsLevel()) else if (@case.Type.AffectsLevel())
{ {
slot = await @case.GetSlotAsync(database); slot = await @case.GetSlotAsync(database);
if (slot == null) if (slot == null)

View file

@ -54,7 +54,6 @@ public class ServerConfiguration
// If a valid YML configuration is available! // If a valid YML configuration is available!
if (File.Exists(ConfigFileName) && (tempConfig = fromFile(ConfigFileName)) != null) if (File.Exists(ConfigFileName) && (tempConfig = fromFile(ConfigFileName)) != null)
{ {
// Instance = JsonSerializer.Deserialize<ServerConfiguration>(configFile) ?? throw new ArgumentNullException(nameof(ConfigFileName));
Instance = tempConfig; Instance = tempConfig;
if (Instance.ConfigVersion < CurrentConfigVersion) if (Instance.ConfigVersion < CurrentConfigVersion)

View file

@ -175,6 +175,7 @@ public class Database : DbContext
comment.ThumbsDown = boo; comment.ThumbsDown = boo;
comment.ThumbsUp = yay; comment.ThumbsUp = yay;
await this.SaveChangesAsync(); await this.SaveChangesAsync();
return true; return true;
} }
@ -184,13 +185,19 @@ public class Database : DbContext
if (type == CommentType.Profile) if (type == CommentType.Profile)
{ {
User? targetUser = await this.Users.FirstOrDefaultAsync(u => u.UserId == targetId); int targetUserId = await this.Users.Where(u => u.UserId == targetId)
if (targetUser == null) return false; .Where(u => u.CommentsEnabled)
.Select(u => u.UserId)
.FirstOrDefaultAsync();
if (targetUserId == 0) return false;
} }
else else
{ {
Slot? targetSlot = await this.Slots.FirstOrDefaultAsync(u => u.SlotId == targetId); int targetSlotId = await this.Slots.Where(s => s.SlotId == targetId)
if (targetSlot == null) return false; .Where(s => s.CommentsEnabled && !s.Hidden)
.Select(s => s.SlotId)
.FirstOrDefaultAsync();
if (targetSlotId == 0) return false;
} }
this.Comments.Add this.Comments.Add
@ -205,6 +212,7 @@ public class Database : DbContext
} }
); );
await this.SaveChangesAsync(); await this.SaveChangesAsync();
return true; return true;
} }
@ -307,6 +315,16 @@ public class Database : DbContext
#endregion #endregion
#region User Helper Methods
public async Task<int> UserIdFromUsername(string? username)
{
if (username == null) return 0;
return await this.Users.Where(u => u.Username == username).Select(u => u.UserId).FirstOrDefaultAsync();
}
#endregion
#region Game Token Shenanigans #region Game Token Shenanigans
public async Task<string> UsernameFromGameToken(GameToken? token) public async Task<string> UsernameFromGameToken(GameToken? token)
@ -316,7 +334,14 @@ public class Database : DbContext
return await this.Users.Where(u => u.UserId == token.UserId).Select(u => u.Username).FirstAsync(); return await this.Users.Where(u => u.UserId == token.UserId).Select(u => u.Username).FirstAsync();
} }
public async Task<User?> UserFromMMAuth(string authToken, bool allowUnapproved = false) public async Task<User?> UserFromGameToken(GameToken? token)
{
if (token == null) return null;
return await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId);
}
private async Task<User?> UserFromMMAuth(string authToken, bool allowUnapproved = false)
{ {
if (ServerStatics.IsUnitTesting) allowUnapproved = true; if (ServerStatics.IsUnitTesting) allowUnapproved = true;
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == authToken); GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == authToken);
@ -324,20 +349,14 @@ public class Database : DbContext
if (token == null) return null; if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null; if (!allowUnapproved && !token.Approved) return null;
if (DateTime.Now > token.ExpiresAt) if (DateTime.Now <= token.ExpiresAt) return await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId);
{
this.Remove(token);
await this.SaveChangesAsync();
return null;
}
return await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId); this.Remove(token);
await this.SaveChangesAsync();
return null;
} }
public async Task<User?> UserFromGameToken
(GameToken gameToken, bool allowUnapproved = false)
=> await this.UserFromMMAuth(gameToken.UserToken, allowUnapproved);
public async Task<User?> UserFromGameRequest(HttpRequest request, bool allowUnapproved = false) public async Task<User?> UserFromGameRequest(HttpRequest request, bool allowUnapproved = false)
{ {
if (ServerStatics.IsUnitTesting) allowUnapproved = true; if (ServerStatics.IsUnitTesting) allowUnapproved = true;
@ -356,14 +375,12 @@ public class Database : DbContext
if (token == null) return null; if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null; if (!allowUnapproved && !token.Approved) return null;
if (DateTime.Now > token.ExpiresAt) if (DateTime.Now <= token.ExpiresAt) return token;
{
this.Remove(token);
await this.SaveChangesAsync();
return null;
}
return token; this.Remove(token);
await this.SaveChangesAsync();
return null;
} }
public async Task<(User, GameToken)?> UserAndGameTokenFromRequest(HttpRequest request, bool allowUnapproved = false) public async Task<(User, GameToken)?> UserAndGameTokenFromRequest(HttpRequest request, bool allowUnapproved = false)
@ -383,7 +400,6 @@ public class Database : DbContext
} }
User? user = await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId); User? user = await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId);
if (user == null) return null; if (user == null) return null;
return (user, token); return (user, token);
@ -400,19 +416,17 @@ public class Database : DbContext
return await this.Users.Where(u => u.UserId == token.UserId).Select(u => u.Username).FirstAsync(); return await this.Users.Where(u => u.UserId == token.UserId).Select(u => u.Username).FirstAsync();
} }
public User? UserFromLighthouseToken(string lighthouseToken) private User? UserFromLighthouseToken(string lighthouseToken)
{ {
WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken); WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
if (token == null) return null; if (token == null) return null;
if (DateTime.Now > token.ExpiresAt) if (DateTime.Now <= token.ExpiresAt) return this.Users.FirstOrDefault(u => u.UserId == token.UserId);
{
this.Remove(token);
this.SaveChanges();
return null;
}
return this.Users.Include(u => u.Location).FirstOrDefault(u => u.UserId == token.UserId); this.Remove(token);
this.SaveChanges();
return null;
} }
public User? UserFromWebRequest(HttpRequest request) public User? UserFromWebRequest(HttpRequest request)
@ -429,48 +443,41 @@ public class Database : DbContext
WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken); WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
if (token == null) return null; if (token == null) return null;
if (DateTime.Now > token.ExpiresAt) if (DateTime.Now <= token.ExpiresAt) return token;
{
this.Remove(token); this.Remove(token);
this.SaveChanges(); this.SaveChanges();
return null;
} return null;
return token;
} }
public async Task<User?> UserFromPasswordResetToken(string resetToken) public async Task<User?> UserFromPasswordResetToken(string resetToken)
{ {
PasswordResetToken? token = await this.PasswordResetTokens.FirstOrDefaultAsync(token => token.ResetToken == resetToken); PasswordResetToken? token = await this.PasswordResetTokens.FirstOrDefaultAsync(token => token.ResetToken == resetToken);
if (token == null) if (token == null) return null;
{
return null;
}
if (token.Created < DateTime.Now.AddHours(-1)) // if token is expired if (token.Created >= DateTime.Now.AddHours(-1))
{ return await this.Users.FirstOrDefaultAsync(user => user.UserId == token.UserId);
this.PasswordResetTokens.Remove(token);
await this.SaveChangesAsync();
return null;
}
return await this.Users.FirstOrDefaultAsync(user => user.UserId == token.UserId); this.PasswordResetTokens.Remove(token);
await this.SaveChangesAsync();
return null;
} }
public bool IsRegistrationTokenValid(string tokenString) public bool IsRegistrationTokenValid(string tokenString)
{ {
RegistrationToken? token = this.RegistrationTokens.FirstOrDefault(t => t.Token == tokenString); RegistrationToken? token = this.RegistrationTokens.FirstOrDefault(t => t.Token == tokenString);
if (token == null) return false; if (token == null) return false;
if (token.Created < DateTime.Now.AddDays(-7)) // if token is expired if (token.Created >= DateTime.Now.AddDays(-7)) return true;
{
this.RegistrationTokens.Remove(token); this.RegistrationTokens.Remove(token);
this.SaveChanges(); this.SaveChanges();
return false;
} return false;
return true;
} }
public async Task RemoveExpiredTokens() public async Task RemoveExpiredTokens()
@ -478,7 +485,7 @@ public class Database : DbContext
foreach (GameToken token in await this.GameTokens.Where(t => DateTime.Now > t.ExpiresAt).ToListAsync()) foreach (GameToken token in await this.GameTokens.Where(t => DateTime.Now > t.ExpiresAt).ToListAsync())
{ {
User? user = await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId); User? user = await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId);
if(user != null) user.LastLogout = TimeHelper.TimestampMillis; if (user != null) user.LastLogout = TimeHelper.TimestampMillis;
this.GameTokens.Remove(token); this.GameTokens.Remove(token);
} }
this.WebTokens.RemoveWhere(t => DateTime.Now > t.ExpiresAt); this.WebTokens.RemoveWhere(t => DateTime.Now > t.ExpiresAt);
@ -491,10 +498,10 @@ public class Database : DbContext
public async Task RemoveRegistrationToken(string tokenString) public async Task RemoveRegistrationToken(string tokenString)
{ {
RegistrationToken? token = await this.RegistrationTokens.FirstOrDefaultAsync(t => t.Token == tokenString); RegistrationToken? token = await this.RegistrationTokens.FirstOrDefaultAsync(t => t.Token == tokenString);
if (token == null) return; if (token == null) return;
this.RegistrationTokens.Remove(token); this.RegistrationTokens.Remove(token);
await this.SaveChangesAsync(); await this.SaveChangesAsync();
} }
@ -508,6 +515,7 @@ public class Database : DbContext
if (user.Username.Length == 0) return; // don't delete the placeholder user if (user.Username.Length == 0) return; // don't delete the placeholder user
if (user.Location != null) this.Locations.Remove(user.Location); if (user.Location != null) this.Locations.Remove(user.Location);
LastContact? lastContact = await this.LastContacts.FirstOrDefaultAsync(l => l.UserId == user.UserId); LastContact? lastContact = await this.LastContacts.FirstOrDefaultAsync(l => l.UserId == user.UserId);
if (lastContact != null) this.LastContacts.Remove(lastContact); if (lastContact != null) this.LastContacts.Remove(lastContact);
@ -536,6 +544,7 @@ public class Database : DbContext
public async Task RemoveSlot(Slot slot, bool saveChanges = true) public async Task RemoveSlot(Slot slot, bool saveChanges = true)
{ {
if (slot.Location != null) this.Locations.Remove(slot.Location); if (slot.Location != null) this.Locations.Remove(slot.Location);
this.Slots.Remove(slot); this.Slots.Remove(slot);
if (saveChanges) await this.SaveChangesAsync(); if (saveChanges) await this.SaveChangesAsync();

View file

@ -0,0 +1,58 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Extensions;
public static class ControllerExtensions
{
public static GameToken GetToken(this ControllerBase controller)
{
GameToken? token = (GameToken?)(controller.HttpContext.Items["Token"] ?? null);
if (token == null) throw new ArgumentNullException($"GameToken was null even though authentication was successful {nameof(controller)}");
return token;
}
public static async Task<T?> DeserializeBody<T>(this ControllerBase controller, params string[] rootElements)
{
controller.Request.Body.Position = 0;
string bodyString = await new StreamReader(controller.Request.Body).ReadToEndAsync();
try
{
XmlRootAttribute? root = null;
if (rootElements.Length > 0)
{
string? matchedRoot = rootElements.FirstOrDefault(e => bodyString.StartsWith($@"<{e}>"));
if (matchedRoot == null)
{
Logger.Error($"[{controller.ControllerContext.ActionDescriptor.ActionName}] " +
$"Failed to deserialize {typeof(T).Name}: Unable to match root element", LogArea.Deserialization);
Logger.Error($"{bodyString}", LogArea.Deserialization);
return default;
}
root = new XmlRootAttribute(matchedRoot);
}
XmlSerializer serializer = new(typeof(T), root);
T? obj = (T?)serializer.Deserialize(new StringReader(bodyString));
SanitizationHelper.SanitizeStringsInClass(obj);
return obj;
}
catch (Exception e)
{
Logger.Error($"[{controller.ControllerContext.ActionDescriptor.ActionName}] " +
$"Failed to deserialize {typeof(T).Name}: {e.Message}", LogArea.Deserialization);
Logger.Error($"{bodyString}", LogArea.Deserialization);
}
return default;
}
}

View file

@ -24,8 +24,12 @@ public static class FileHelper
{ {
public static readonly string ResourcePath = Path.Combine(Environment.CurrentDirectory, "r"); public static readonly string ResourcePath = Path.Combine(Environment.CurrentDirectory, "r");
public static readonly string FullResourcePath = Path.GetFullPath(ResourcePath);
public static readonly string ImagePath = Path.Combine(Environment.CurrentDirectory, "png"); public static readonly string ImagePath = Path.Combine(Environment.CurrentDirectory, "png");
public static readonly string FullImagePath = Path.GetFullPath(ImagePath);
public static string GetResourcePath(string hash) => Path.Combine(ResourcePath, hash); public static string GetResourcePath(string hash) => Path.Combine(ResourcePath, hash);
public static string GetImagePath(string hash) => Path.Combine(ImagePath, hash); public static string GetImagePath(string hash) => Path.Combine(ImagePath, hash);
@ -99,7 +103,7 @@ public static class FileHelper
int curOffset = 8; int curOffset = 8;
int dependencyTableOffset = BinaryPrimitives.ReadInt32BigEndian(file.Data.AsSpan()[curOffset..]); int dependencyTableOffset = BinaryPrimitives.ReadInt32BigEndian(file.Data.AsSpan()[curOffset..]);
if(dependencyTableOffset <= 0 || dependencyTableOffset > file.Data.Length) return dependencies; if (dependencyTableOffset <= 0 || dependencyTableOffset > file.Data.Length) return dependencies;
curOffset = dependencyTableOffset; curOffset = dependencyTableOffset;
int dependencyTableSize = BinaryPrimitives.ReadInt32BigEndian(file.Data.AsSpan()[dependencyTableOffset..]); int dependencyTableSize = BinaryPrimitives.ReadInt32BigEndian(file.Data.AsSpan()[dependencyTableOffset..]);
@ -163,7 +167,7 @@ public static class FileHelper
{ {
version = GameVersion.LittleBigPlanet3; version = GameVersion.LittleBigPlanet3;
} }
else if(revision <= lbp2Latest) else if (revision <= lbp2Latest)
{ {
version = GameVersion.LittleBigPlanet2; version = GameVersion.LittleBigPlanet2;
} }
@ -404,7 +408,7 @@ public static class FileHelper
private static bool DDSToPNG(string hash, byte[] data) private static bool DDSToPNG(string hash, byte[] data)
{ {
Dds ddsImage = Dds.Create(data, new PfimConfig()); Dds ddsImage = Dds.Create(data, new PfimConfig());
if(ddsImage.Compressed) if (ddsImage.Compressed)
ddsImage.Decompress(); ddsImage.Decompress();
// ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault

View file

@ -45,7 +45,7 @@ public static class CryptoHelper
using IncrementalHash sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); using IncrementalHash sha1 = IncrementalHash.CreateHash(HashAlgorithmName.SHA1);
// LBP games will sometimes opt to calculate the digest without the body // LBP games will sometimes opt to calculate the digest without the body
// (one example is resource upload requests) // (one example is resource upload requests)
if(!excludeBody) if (!excludeBody)
sha1.AppendData(bodyBytes); sha1.AppendData(bodyBytes);
if (cookieBytes.Length > 0) sha1.AppendData(cookieBytes); if (cookieBytes.Length > 0) sha1.AppendData(cookieBytes);
sha1.AppendData(pathBytes); sha1.AppendData(pathBytes);

View file

@ -25,4 +25,5 @@ public enum LogArea
Maintenance, Maintenance,
Score, Score,
RateLimit, RateLimit,
Deserialization,
} }

View file

@ -154,23 +154,19 @@ public class User
public UserStatus Status => new(this.database, this.UserId); public UserStatus Status => new(this.database, this.UserId);
[JsonIgnore] [JsonIgnore]
public bool IsBanned => this.PermissionLevel == PermissionLevel.Banned; public bool IsBanned => this.PermissionLevel is PermissionLevel.Banned;
[JsonIgnore] [JsonIgnore]
public bool IsRestricted => this.PermissionLevel == PermissionLevel.Restricted || public bool IsRestricted => this.PermissionLevel is PermissionLevel.Restricted or PermissionLevel.Banned;
this.PermissionLevel == PermissionLevel.Banned;
[JsonIgnore] [JsonIgnore]
public bool IsSilenced => this.PermissionLevel == PermissionLevel.Silenced || public bool IsSilenced => this.PermissionLevel is PermissionLevel.Silenced or PermissionLevel.Restricted or PermissionLevel.Banned;
this.PermissionLevel == PermissionLevel.Restricted ||
this.PermissionLevel == PermissionLevel.Banned;
[JsonIgnore] [JsonIgnore]
public bool IsModerator => this.PermissionLevel == PermissionLevel.Moderator || public bool IsModerator => this.PermissionLevel is PermissionLevel.Moderator or PermissionLevel.Administrator;
this.PermissionLevel == PermissionLevel.Administrator;
[JsonIgnore] [JsonIgnore]
public bool IsAdmin => this.PermissionLevel == PermissionLevel.Administrator; public bool IsAdmin => this.PermissionLevel is PermissionLevel.Administrator;
[JsonIgnore] [JsonIgnore]
public PermissionLevel PermissionLevel { get; set; } = PermissionLevel.Default; public PermissionLevel PermissionLevel { get; set; } = PermissionLevel.Default;

View file

@ -54,7 +54,7 @@ public static class StartupTasks
using Database database = new(); using Database database = new();
#if !DEBUG #if !DEBUG
if(serverType == ServerType.GameServer) if (serverType == ServerType.GameServer)
#endif #endif
migrateDatabase(database); migrateDatabase(database);