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
using System.Diagnostics.CodeAnalysis;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")]
public class ClientConfigurationController : ControllerBase
@ -22,11 +25,8 @@ public class ClientConfigurationController : ControllerBase
[HttpGet("network_settings.nws")]
[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;
return this.Ok
(
@ -52,7 +52,9 @@ public class ClientConfigurationController : ControllerBase
[Produces("text/xml")]
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, "");
PrivacySettings ps = new()
@ -71,11 +73,7 @@ public class ClientConfigurationController : ControllerBase
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(PrivacySettings));
PrivacySettings? settings = (PrivacySettings?)serializer.Deserialize(new StringReader(bodyString));
PrivacySettings? settings = await this.DeserializeBody<PrivacySettings>();
if (settings == null) return this.BadRequest();
if (settings.LevelVisibility != null)

View file

@ -1,17 +1,19 @@
#nullable enable
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class CommentController : ControllerBase
@ -26,10 +28,10 @@ public class CommentController : ControllerBase
[HttpPost("rateComment/{slotType}/{slotId:int}")]
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);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
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);
if (!success) return this.BadRequest();
@ -41,37 +43,33 @@ public class CommentController : ControllerBase
[HttpGet("userComments/{username}")]
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);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
if (pageSize <= 0 || pageStart < 0) return this.BadRequest();
int targetId = slotId;
CommentType type = CommentType.Level;
if (!string.IsNullOrWhiteSpace(username))
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
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;
type = CommentType.Profile;
targetId = await this.database.Slots.Where(s => s.SlotId == slotId)
.Where(s => s.CommentsEnabled && !s.Hidden)
.Select(s => s.SlotId)
.FirstOrDefaultAsync();
}
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 (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();
}
if (targetId == 0) return this.NotFound();
List<Comment> comments = await this.database.Comments.Include
(c => c.Poster)
@ -88,7 +86,8 @@ public class CommentController : ControllerBase
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)
.FirstOrDefaultAsync();
}
@ -97,28 +96,28 @@ public class CommentController : ControllerBase
[HttpPost("postComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> PostComment(string? username, string? slotType, int slotId)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
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();
GameToken token = this.GetToken();
Comment? comment = await this.DeserializeBody<Comment>();
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);
if (success) return this.Ok();
@ -130,44 +129,44 @@ public class CommentController : ControllerBase
[HttpPost("deleteComment/{slotType}/{slotId:int}")]
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest();
Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId);
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);
// if you are not the poster
if (comment.PosterUserId != token.UserId)
bool canDelete;
if (comment.Type == CommentType.Profile)
{
if (comment.Type == CommentType.Profile)
{
// 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, "");
}
}
canDelete = comment.PosterUserId == token.UserId || comment.TargetId == token.UserId;
}
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.DeletedBy = await this.database.UsernameFromGameToken(token);
comment.DeletedType = "user";
await this.database.SaveChangesAsync();
return this.Ok();
}
}

View file

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

View file

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

View file

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

View file

@ -1,15 +1,18 @@
#nullable enable
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
// [Produces("text/plain")]
[Produces("text/xml")]
public class EnterLevelController : ControllerBase
{
private readonly Database database;
@ -22,8 +25,7 @@ public class EnterLevelController : ControllerBase
[HttpPost("play/{slotType}/{slotId:int}")]
public async Task<IActionResult> PlayLevel(string slotType, int slotId)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
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);
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);
VisitedLevel? v;
if (!visited.Any())
{
switch (gameVersion)
switch (token.GameVersion)
{
case GameVersion.LittleBigPlanet2:
case GameVersion.LittleBigPlanetVita:
@ -48,6 +48,9 @@ public class EnterLevelController : ControllerBase
case GameVersion.LittleBigPlanet3:
slot.PlaysLBP3Unique++;
break;
case GameVersion.LittleBigPlanet1:
case GameVersion.LittleBigPlanetPSP:
case GameVersion.Unknown:
default: return this.BadRequest();
}
@ -65,7 +68,7 @@ public class EnterLevelController : ControllerBase
if (v == null) return this.NotFound();
switch (gameVersion)
switch (token.GameVersion)
{
case GameVersion.LittleBigPlanet2:
case GameVersion.LittleBigPlanetVita:
@ -76,9 +79,9 @@ public class EnterLevelController : ControllerBase
slot.PlaysLBP3++;
v.PlaysLBP3++;
break;
case GameVersion.LittleBigPlanetPSP: throw new NotImplementedException();
case GameVersion.Unknown:
case GameVersion.LittleBigPlanet1:
case GameVersion.LittleBigPlanetPSP:
case GameVersion.Unknown:
default:
return this.BadRequest();
}
@ -92,8 +95,7 @@ public class EnterLevelController : ControllerBase
[HttpPost("enterLevel/{slotType}/{slotId:int}")]
public async Task<IActionResult> EnterLevel(string slotType, int slotId)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
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.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class MatchController : ControllerBase
@ -27,26 +29,16 @@ public class MatchController : ControllerBase
[HttpPost("gameState")]
[Produces("text/plain")]
public async Task<IActionResult> GameState()
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
return this.Ok("VALID");
}
public IActionResult GameState() => this.Ok("VALID");
[HttpPost("match")]
[Produces("text/plain")]
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, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
User? user = await this.database.UserFromGameToken(token);
if (user == null) return this.StatusCode(403, "");
#region Parse match data
@ -81,70 +73,74 @@ public class MatchController : ControllerBase
#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
if (matchData is UpdateMyPlayerData playerData)
switch (matchData)
{
MatchHelper.SetUserLocation(user.UserId, gameToken.UserLocation);
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)
case UpdateMyPlayerData playerData:
{
User? player = await this.database.Users.FirstOrDefaultAsync(u => u.Username == playerUsername);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (player != null) users.Add(player.UserId);
else return this.BadRequest();
MatchHelper.SetUserLocation(user.UserId, token.UserLocation);
Room? room = RoomHelper.FindRoomByUser(user.UserId, token.GameVersion, token.Platform, true);
if (playerData.RoomState != null)
if (room != null && Equals(room.HostId, user.UserId))
room.State = (RoomState)playerData.RoomState;
break;
}
// Create a new one as requested
RoomHelper.CreateRoom(users, gameToken.GameVersion, gameToken.Platform, createRoom.RoomSlot);
}
else if (matchData is UpdatePlayersInRoom updatePlayersInRoom)
{
Room? room = RoomHelper.Rooms.FirstOrDefault(r => r.HostId == user.UserId);
if (room != null)
// Check how many people are online in release builds, disabled for debug for ..well debugging.
#if DEBUG
case FindBestRoom diveInData:
#else
case FindBestRoom diveInData when MatchHelper.UserLocations.Count > 1:
#endif
{
List<User> users = new();
foreach (string playerUsername in updatePlayersInRoom.Players)
FindBestRoomResponse? response = RoomHelper.FindBestRoom
(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);
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (player != null) users.Add(player);
if (player != null) users.Add(player.UserId);
else return this.BadRequest();
}
room.PlayerIds = users.Select(u => u.UserId).ToList();
await RoomHelper.CleanupRooms(null, room);
// Create a new one as requested
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
using System.Globalization;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")]
public class MessageController : ControllerBase
@ -35,20 +38,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
}
[HttpGet("eula")]
public async Task<IActionResult> Eula()
{
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}");
}
public IActionResult Eula() => this.Ok($"{license}\n{ServerConfiguration.Instance.EulaText}");
[HttpGet("announce")]
public async Task<IActionResult> Announce()
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
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")]
public IActionResult Notification() => this.Ok();
/// <summary>
/// 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>
[HttpPost("filter")]
public async Task<IActionResult> Filter()
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
string response = await new StreamReader(this.Request.Body).ReadToEndAsync();

View file

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

View file

@ -1,5 +1,4 @@
#nullable enable
using System.Xml.Serialization;
using Discord;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
@ -9,12 +8,14 @@ using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class PhotosController : ControllerBase
@ -29,16 +30,12 @@ public class PhotosController : ControllerBase
[HttpPost("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.PhotosByMe >= ServerConfiguration.Instance.UserGeneratedContentLimits.PhotosQuota) return this.BadRequest();
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Photo));
Photo? photo = (Photo?)serializer.Deserialize(new StringReader(bodyString));
Photo? photo = await this.DeserializeBody<Photo>();
if (photo == null) return this.BadRequest();
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.
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;
break;
}
@ -83,6 +80,10 @@ public class PhotosController : ControllerBase
validLevel = true;
break;
}
case SlotType.Moon:
case SlotType.Unknown:
case SlotType.Unknown2:
case SlotType.DLC:
default: Logger.Warn($"Invalid photo level type: {photoSlot.SlotType}", LogArea.Photos);
break;
}
@ -103,10 +104,8 @@ public class PhotosController : ControllerBase
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);
if (subject.User == null) continue;
@ -144,9 +143,6 @@ public class PhotosController : ControllerBase
[HttpGet("photos/{slotType}/{id:int}")]
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 (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest();
@ -166,12 +162,9 @@ public class PhotosController : ControllerBase
[HttpGet("photos/by")]
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();
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();
List<Photo> photos = await this.database.Photos.Include
@ -188,12 +181,9 @@ public class PhotosController : ControllerBase
[HttpGet("photos/with")]
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();
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();
List<int> photoSubjectIds = new();
@ -220,8 +210,7 @@ public class PhotosController : ControllerBase
[HttpPost("deletePhoto/{id:int}")]
public async Task<IActionResult> DeletePhoto(int id)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
Photo? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id);
if (photo == null) return this.NotFound();

View file

@ -1,28 +1,23 @@
#nullable enable
using System.Buffers;
using System.IO.Pipelines;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.Serialization;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using IOFile = System.IO.File;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources;
[ApiController]
[Authorize]
[Produces("text/xml")]
[Route("LITTLEBIGPLANETPS3_XML")]
public class ResourcesController : ControllerBase
{
private readonly Database database;
public ResourcesController(Database database)
{
this.database = database;
}
[HttpPost("showModerated")]
public IActionResult ShowModerated() => this.Ok(LbpSerializer.BlankElement("resources"));
@ -31,14 +26,7 @@ public class ResourcesController : ControllerBase
[HttpPost("showNotUploaded")]
public async Task<IActionResult> FilterResources()
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
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));
ResourceList? resourceList = await this.DeserializeBody<ResourceList>();
if (resourceList == null) return this.BadRequest();
string resources = resourceList.Resources.Where
@ -49,18 +37,14 @@ public class ResourcesController : ControllerBase
}
[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 fullPath = Path.GetFullPath(path);
string basePath = Path.GetFullPath(FileHelper.ResourcePath);
// 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");
@ -72,16 +56,17 @@ public class ResourcesController : ControllerBase
[HttpPost("upload/{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 path = FileHelper.GetResourcePath(hash);
string fullPath = Path.GetFullPath(path);
FileHelper.EnsureDirectoryCreated(assetsDirectory);
// lbp treats code 409 as success and as an indicator that the file is already present
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);
LbpFile file = new(await readFromPipeReader(this.Request.BodyReader));

View file

@ -1,19 +1,20 @@
#nullable enable
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.Levels.Categories;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class CollectionController : ControllerBase
@ -28,9 +29,6 @@ public class CollectionController : ControllerBase
[HttpGet("playlists/{playlistId:int}/slots")]
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);
if (targetPlaylist == null) return this.BadRequest();
@ -50,8 +48,7 @@ public class CollectionController : ControllerBase
[HttpPost("playlists/{playlistId:int}/order_slots")]
public async Task<IActionResult> UpdatePlaylist(int playlistId, int slotId)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
Playlist? targetPlaylist = await this.database.Playlists.FirstOrDefaultAsync(p => p.PlaylistId == playlistId);
if (targetPlaylist == null) return this.BadRequest();
@ -66,7 +63,7 @@ public class CollectionController : ControllerBase
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();
@ -116,14 +113,13 @@ public class CollectionController : ControllerBase
[HttpPost("playlists")]
public async Task<IActionResult> CreatePlaylist()
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
int playlistCount = await this.database.Playlists.CountAsync(p => p.CreatorId == token.UserId);
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();
@ -139,10 +135,7 @@ public class CollectionController : ControllerBase
[HttpGet("user/{username}/playlists")]
public async Task<IActionResult> GetUserPlaylists(string username)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
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();
return this.Ok(this.GetUserPlaylists(targetUserId));
@ -152,8 +145,9 @@ public class CollectionController : ControllerBase
[HttpGet("genres")]
public async Task<IActionResult> GenresAndSearches()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
User? user = await this.database.UserFromGameToken(token);
string categoriesSerialized = CategoryHelper.Categories.Aggregate
(
@ -196,13 +190,9 @@ public class CollectionController : ControllerBase
[HttpGet("searches/{endpointName}")]
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, "");
// ReSharper disable once PossibleInvalidOperationException
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
User? user = await this.database.UserFromGameToken(token);
Category? category = CategoryHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName);
if (category == null) return this.NotFound();
@ -223,7 +213,7 @@ public class CollectionController : ControllerBase
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
(
@ -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.Levels;
using LBPUnion.ProjectLighthouse.PlayerData;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML")]
[Produces("text/plain")]
public class LevelTagsController : ControllerBase
@ -34,15 +37,14 @@ public class LevelTagsController : ControllerBase
}
[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);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
Slot? slot = await this.database.Slots.Where(s => s.SlotId == id).FirstOrDefaultAsync();
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();
@ -53,7 +55,7 @@ public class LevelTagsController : ControllerBase
RatedLevel? rating = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.UserId == token.UserId && r.SlotId == slot.SlotId);
if (rating == null) return this.BadRequest();
rating.TagLBP1 = t;
rating.TagLBP1 = tagName;
await this.database.SaveChangesAsync();

View file

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

View file

@ -1,6 +1,6 @@
#nullable enable
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels;
@ -8,12 +8,14 @@ using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class PublishController : ControllerBase
@ -31,15 +33,12 @@ public class PublishController : ControllerBase
[HttpPost("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
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
Slot? slot = await this.getSlotFromBody();
Slot? slot = await this.DeserializeBody<Slot>();
if (slot == null)
{
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
@ -69,7 +68,7 @@ public class PublishController : ControllerBase
return this.BadRequest();
}
}
else if (user.GetUsedSlotsForGame(gameToken.GameVersion) > user.EntitledSlots)
else if (user.GetUsedSlotsForGame(token.GameVersion) > user.EntitledSlots)
{
return this.StatusCode(403, "");
}
@ -89,14 +88,12 @@ public class PublishController : ControllerBase
[HttpPost("publish")]
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
User user = userAndToken.Value.Item1;
GameToken gameToken = userAndToken.Value.Item2;
Slot? slot = await this.getSlotFromBody();
Slot? slot = await this.DeserializeBody<Slot>();
if (slot == null)
{
@ -156,7 +153,7 @@ public class PublishController : ControllerBase
GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel);
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);
@ -185,7 +182,7 @@ public class PublishController : ControllerBase
if (intendedVersion != GameVersion.Unknown && intendedVersion != slotVersion)
{
// Delete the useless rootLevel that lbp3 just uploaded
if(slotVersion == GameVersion.LittleBigPlanet3)
if (slotVersion == GameVersion.LittleBigPlanet3)
FileHelper.DeleteResource(slot.RootLevel);
slot.GameVersion = oldSlot.GameVersion;
@ -230,7 +227,7 @@ public class PublishController : ControllerBase
this.database.Entry(oldSlot).CurrentValues.SetValues(slot);
await this.database.SaveChangesAsync();
return this.Ok(oldSlot.Serialize(gameToken.GameVersion));
return this.Ok(oldSlot.Serialize(token.GameVersion));
}
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);
return this.Ok(slot.Serialize(gameToken.GameVersion));
return this.Ok(slot.Serialize(token.GameVersion));
}
[HttpPost("unpublish/{id:int}")]
public async Task<IActionResult> Unpublish(int id)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
@ -305,17 +301,4 @@ public class PublishController : ControllerBase
_ => 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
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Administration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
@ -7,12 +6,14 @@ using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Reviews;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class ReviewController : ControllerBase
@ -28,8 +29,7 @@ public class ReviewController : ControllerBase
[HttpPost("rate/user/{slotId:int}")]
public async Task<IActionResult> Rate(int slotId, [FromQuery] int rating)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
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, "");
@ -58,8 +58,7 @@ public class ReviewController : ControllerBase
[HttpPost("dpadrate/user/{slotId:int}")]
public async Task<IActionResult> DPadRate(int slotId, [FromQuery] int rating)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
Slot? slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slotId);
if (slot == null) return this.StatusCode(403, "");
@ -90,10 +89,9 @@ public class ReviewController : ControllerBase
[HttpPost("postReview/user/{slotId:int}")]
public async Task<IActionResult> PostReview(int slotId)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
Review? newReview = await this.getReviewFromBody();
Review? newReview = await this.DeserializeBody<Review>();
if (newReview == null) return this.BadRequest();
if (newReview.Text.Length > 512) return this.BadRequest();
@ -143,8 +141,7 @@ public class ReviewController : ControllerBase
[HttpGet("reviewsFor/user/{slotId:int}")]
public async Task<IActionResult> ReviewsFor(int slotId, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
@ -195,14 +192,13 @@ public class ReviewController : ControllerBase
[HttpGet("reviewsBy/{username}")]
public async Task<IActionResult> ReviewsBy(string username, [FromQuery] int pageStart = 1, [FromQuery] int pageSize = 10)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
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();
@ -249,10 +245,9 @@ public class ReviewController : ControllerBase
[HttpPost("rateReview/user/{slotId:int}/{username}")]
public async Task<IActionResult> RateReview(int slotId, string username, [FromQuery] int rating = 0)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
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, "");
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}")]
public async Task<IActionResult> DeleteReview(int slotId, string username)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
int creatorId = await this.database.Slots.Where(s => s.SlotId == slotId).Select(s => s.CreatorId).FirstOrDefaultAsync();
if (creatorId == 0) return this.StatusCode(400, "");
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, "");
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();
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
using System.Diagnostics.CodeAnalysis;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.Logging;
@ -30,8 +31,7 @@ public class ScoreController : ControllerBase
[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)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
string username = await this.database.UsernameFromGameToken(token);
@ -41,11 +41,7 @@ public class ScoreController : ControllerBase
return this.BadRequest();
}
this.Request.Body.Position = 0;
string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync();
XmlSerializer serializer = new(typeof(Score));
Score? score = (Score?)serializer.Deserialize(new StringReader(bodyString));
Score? score = await this.DeserializeBody<Score>();
if (score == null)
{
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}")]
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);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
@ -189,8 +184,7 @@ public class ScoreController : ControllerBase
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
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);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();

View file

@ -3,12 +3,14 @@ using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/slots")]
[Produces("text/xml")]
public class SearchController : ControllerBase
@ -31,8 +33,7 @@ public class SearchController : ControllerBase
string? keyName = "slots"
)
{
GameToken? gameToken = await this.database.GameTokenFromRequest(this.Request);
if (gameToken == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
if (pageSize <= 0) return this.BadRequest();
@ -42,7 +43,7 @@ public class SearchController : ControllerBase
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)
.OrderBy(s => !s.TeamPick)
.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();
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()));
}

View file

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

View file

@ -1,10 +1,12 @@
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/plain")]
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;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class StoreController : Controller

View file

@ -1,18 +1,20 @@
#nullable enable
using System.Text.Json;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[ApiController]
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
public class UserController : ControllerBase
@ -34,14 +36,11 @@ public class UserController : ControllerBase
{
// use an anonymous type to only fetch certain columns
var partialUser = await this.database.Users.Where(u => u.Username == username)
.Select
(
u => new
{
u.Username,
u.IconHash,
}
)
.Select(u => new
{
u.Username,
u.IconHash,
})
.FirstOrDefaultAsync();
if (partialUser == null) return null;
@ -52,8 +51,7 @@ public class UserController : ControllerBase
[HttpGet("user/{username}")]
public async Task<IActionResult> GetUser(string username)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
GameToken token = this.GetToken();
string? user = await this.getSerializedUser(username, token.GameVersion);
if (user == null) return this.NotFound();
@ -64,9 +62,6 @@ public class UserController : ControllerBase
[HttpGet("users")]
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();
foreach (string userId in u) serializedUsers.Add(await this.getSerializedUserPicture(userId));
@ -78,20 +73,12 @@ public class UserController : ControllerBase
[HttpPost("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
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));
UserUpdate? update = await this.DeserializeBody<UserUpdate>("updateUser", "user");
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);
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);
@ -146,7 +133,7 @@ public class UserController : ControllerBase
if (update.PlanetHash != null)
{
switch (gameToken.GameVersion)
switch (token.GameVersion)
{
case GameVersion.LittleBigPlanet2: // LBP2 planets will apply to LBP3
{
@ -169,7 +156,7 @@ public class UserController : ControllerBase
case GameVersion.Unknown:
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")]
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, "");
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.Serialization;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.HttpOverrides;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Startup;
@ -21,6 +24,19 @@ public class GameServerStartup
{
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
(
options =>
@ -64,12 +80,14 @@ public class GameServerStartup
app.UseForwardedHeaders();
app.UseMiddleware<RequestLogMiddleware>();
app.UseMiddleware<RateLimitMiddleware>();
app.UseMiddleware<DigestMiddleware>(computeDigests);
app.UseMiddleware<SetLastContactMiddleware>();
app.UseMiddleware<RateLimitMiddleware>();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers());
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
using LBPUnion.ProjectLighthouse.Administration.Reports;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -32,7 +31,7 @@ public class AdminReportController : ControllerBase
report.JpegHash,
report.GriefStateHash,
};
if(report.LevelType != "user")
if (report.LevelType != "user")
hashes.Add(report.InitialStateHash);
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);
if (slot == null) return this.NotFound();
slot.TeamPick = true;
// 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 this.database.SaveChangesAsync();
return this.Redirect("~/slot/" + id);
}
@ -44,9 +46,11 @@ public class ModerationSlotController : ControllerBase
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
slot.TeamPick = false;
await this.database.SaveChangesAsync();
return this.Redirect("~/slot/" + id);
}

View file

@ -14,10 +14,9 @@ public class ResourcesController : ControllerBase
string path = FileHelper.GetImagePath($"{hash}.png");
string fullPath = Path.GetFullPath(path);
string basePath = Path.GetFullPath(FileHelper.ImagePath);
// 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");

View file

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

View file

@ -63,14 +63,18 @@ public class LoginForm : BaseLayout
if (user == null)
{
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();
}
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);
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();
}

View file

@ -14,7 +14,7 @@
// Technically, this should never happen but I'm going to handle it anyways.
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???");
}
}

View file

@ -95,7 +95,7 @@
@if (Model.Creator != null)
{
string date = "";
if(!mini)
if (!mini)
date = " on " + TimeZoneInfo.ConvertTime(DateTimeOffset.FromUnixTimeMilliseconds(Model.FirstUploaded), timeZoneInfo).DateTime.ToShortDateString();
<p>
<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();
if(existingToken != null)
if (existingToken != null)
this.Database.EmailVerificationTokens.RemoveWhere(t => t.EmailToken == existingToken);
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.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();
}

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.CommentsEnabled = ServerConfiguration.Instance.UserGeneratedContentLimits.LevelCommentsEnabled && this.ProfileUser.CommentsEnabled;
if(this.CommentsEnabled)
if (this.CommentsEnabled)
{
this.Comments = await this.Database.Comments.Include(p => p.Poster)
.OrderByDescending(p => p.Timestamp)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -175,6 +175,7 @@ public class Database : DbContext
comment.ThumbsDown = boo;
comment.ThumbsUp = yay;
await this.SaveChangesAsync();
return true;
}
@ -184,13 +185,19 @@ public class Database : DbContext
if (type == CommentType.Profile)
{
User? targetUser = await this.Users.FirstOrDefaultAsync(u => u.UserId == targetId);
if (targetUser == null) return false;
int targetUserId = await this.Users.Where(u => u.UserId == targetId)
.Where(u => u.CommentsEnabled)
.Select(u => u.UserId)
.FirstOrDefaultAsync();
if (targetUserId == 0) return false;
}
else
{
Slot? targetSlot = await this.Slots.FirstOrDefaultAsync(u => u.SlotId == targetId);
if (targetSlot == null) return false;
int targetSlotId = await this.Slots.Where(s => s.SlotId == targetId)
.Where(s => s.CommentsEnabled && !s.Hidden)
.Select(s => s.SlotId)
.FirstOrDefaultAsync();
if (targetSlotId == 0) return false;
}
this.Comments.Add
@ -205,6 +212,7 @@ public class Database : DbContext
}
);
await this.SaveChangesAsync();
return true;
}
@ -307,6 +315,16 @@ public class Database : DbContext
#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
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();
}
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;
GameToken? token = await this.GameTokens.FirstOrDefaultAsync(t => t.UserToken == authToken);
@ -324,20 +349,14 @@ public class Database : DbContext
if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
await this.SaveChangesAsync();
return null;
}
if (DateTime.Now <= token.ExpiresAt) return await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId);
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)
{
if (ServerStatics.IsUnitTesting) allowUnapproved = true;
@ -356,14 +375,12 @@ public class Database : DbContext
if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
await this.SaveChangesAsync();
return null;
}
if (DateTime.Now <= token.ExpiresAt) return token;
return token;
this.Remove(token);
await this.SaveChangesAsync();
return null;
}
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);
if (user == null) return null;
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();
}
public User? UserFromLighthouseToken(string lighthouseToken)
private User? UserFromLighthouseToken(string lighthouseToken)
{
WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
if (token == null) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
this.SaveChanges();
return null;
}
if (DateTime.Now <= token.ExpiresAt) return this.Users.FirstOrDefault(u => u.UserId == token.UserId);
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)
@ -429,48 +443,41 @@ public class Database : DbContext
WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
if (token == null) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
this.SaveChanges();
return null;
}
if (DateTime.Now <= token.ExpiresAt) return token;
this.Remove(token);
this.SaveChanges();
return null;
return token;
}
public async Task<User?> UserFromPasswordResetToken(string resetToken)
{
PasswordResetToken? token = await this.PasswordResetTokens.FirstOrDefaultAsync(token => token.ResetToken == resetToken);
if (token == null)
{
return null;
}
if (token == null) return null;
if (token.Created < DateTime.Now.AddHours(-1)) // if token is expired
{
this.PasswordResetTokens.Remove(token);
await this.SaveChangesAsync();
return null;
}
if (token.Created >= DateTime.Now.AddHours(-1))
return await this.Users.FirstOrDefaultAsync(user => user.UserId == token.UserId);
return await this.Users.FirstOrDefaultAsync(user => user.UserId == token.UserId);
this.PasswordResetTokens.Remove(token);
await this.SaveChangesAsync();
return null;
}
public bool IsRegistrationTokenValid(string tokenString)
{
RegistrationToken? token = this.RegistrationTokens.FirstOrDefault(t => t.Token == tokenString);
if (token == null) return false;
if (token.Created < DateTime.Now.AddDays(-7)) // if token is expired
{
this.RegistrationTokens.Remove(token);
this.SaveChanges();
return false;
}
if (token.Created >= DateTime.Now.AddDays(-7)) return true;
this.RegistrationTokens.Remove(token);
this.SaveChanges();
return false;
return true;
}
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())
{
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.WebTokens.RemoveWhere(t => DateTime.Now > t.ExpiresAt);
@ -491,10 +498,10 @@ public class Database : DbContext
public async Task RemoveRegistrationToken(string tokenString)
{
RegistrationToken? token = await this.RegistrationTokens.FirstOrDefaultAsync(t => t.Token == tokenString);
if (token == null) return;
this.RegistrationTokens.Remove(token);
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.Location != null) this.Locations.Remove(user.Location);
LastContact? lastContact = await this.LastContacts.FirstOrDefaultAsync(l => l.UserId == user.UserId);
if (lastContact != null) this.LastContacts.Remove(lastContact);
@ -536,6 +544,7 @@ public class Database : DbContext
public async Task RemoveSlot(Slot slot, bool saveChanges = true)
{
if (slot.Location != null) this.Locations.Remove(slot.Location);
this.Slots.Remove(slot);
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 FullResourcePath = Path.GetFullPath(ResourcePath);
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 GetImagePath(string hash) => Path.Combine(ImagePath, hash);
@ -99,7 +103,7 @@ public static class FileHelper
int curOffset = 8;
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;
int dependencyTableSize = BinaryPrimitives.ReadInt32BigEndian(file.Data.AsSpan()[dependencyTableOffset..]);
@ -163,7 +167,7 @@ public static class FileHelper
{
version = GameVersion.LittleBigPlanet3;
}
else if(revision <= lbp2Latest)
else if (revision <= lbp2Latest)
{
version = GameVersion.LittleBigPlanet2;
}
@ -404,7 +408,7 @@ public static class FileHelper
private static bool DDSToPNG(string hash, byte[] data)
{
Dds ddsImage = Dds.Create(data, new PfimConfig());
if(ddsImage.Compressed)
if (ddsImage.Compressed)
ddsImage.Decompress();
// ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault

View file

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

View file

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

View file

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

View file

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