Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify all the things + fix grief reports #139

Merged
merged 19 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Bunkum.HttpServer.Responses;
using Refresh.GameServer.Database;
using Refresh.GameServer.Extensions;
using Refresh.GameServer.Time;
using Refresh.GameServer.Types.Comments;
using Refresh.GameServer.Types.Lists;
using Refresh.GameServer.Types.Roles;
Expand All @@ -14,8 +15,13 @@ namespace Refresh.GameServer.Endpoints.Game;
public class CommentEndpoints : EndpointGroup
{
[GameEndpoint("postUserComment/{username}", ContentType.Xml, Method.Post)]
public Response PostProfileComment(RequestContext context, GameDatabaseContext database, string username, GameComment body, GameUser user)
public Response PostProfileComment(RequestContext context, GameDatabaseContext database, string username, GameComment body, GameUser user, IDateTimeProvider timeProvider)
{
if (body.Content.Length > 4096)
{
return BadRequest;
}

GameUser? profile = database.GetUserByUsername(username);
if (profile == null) return NotFound;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System.Xml.Serialization;
using Refresh.GameServer.Authentication;
using Refresh.GameServer.Database;
using Refresh.GameServer.Endpoints.ApiV3.DataTypes;
using Refresh.GameServer.Endpoints.Game.DataTypes.Response;
using Refresh.GameServer.Services;
using Refresh.GameServer.Types;
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.Levels.SkillRewards;
using Refresh.GameServer.Types.Matching;
using Refresh.GameServer.Types.Reviews;
using Refresh.GameServer.Types.UserData;

namespace Refresh.GameServer.Endpoints.Game.DataTypes.Request;

[XmlRoot("slot")]
[XmlType("slot")]
public class GameLevelRequest : IDataConvertableFrom<GameLevelRequest, GameLevel>
{
[XmlElement("id")] public required int LevelId { get; set; }

[XmlElement("name")] public required string Title { get; set; }
[XmlElement("icon")] public required string IconHash { get; set; }
[XmlElement("description")] public required string Description { get; set; }
[XmlElement("location")] public required GameLocation Location { get; set; }

[XmlElement("game")] public required int GameVersion { get; set; }
[XmlElement("rootLevel")] public required string RootResource { get; set; }

[XmlElement("firstPublished")] public required long PublishDate { get; set; } // unix seconds
[XmlElement("lastUpdated")] public required long UpdateDate { get; set; }

[XmlElement("minPlayers")] public required int MinPlayers { get; set; }
[XmlElement("maxPlayers")] public required int MaxPlayers { get; set; }
[XmlElement("enforceMinMaxPlayers")] public required bool EnforceMinMaxPlayers { get; set; }

[XmlElement("sameScreenGame")] public required bool SameScreenGame { get; set; }

[XmlAttribute("type")] public string Type { get; set; } = "user";

[XmlElement("npHandle")] public SerializedUserHandle Handle { get; set; } = null!;

[XmlElement("heartCount")] public required int HeartCount { get; set; }

[XmlElement("playCount")] public required int TotalPlayCount { get; set; }
[XmlElement("uniquePlayCount")] public required int UniquePlayCount { get; set; }

[XmlElement("yourDPadRating")] public int YourRating { get; set; }
[XmlElement("thumbsup")] public required int YayCount { get; set; }
[XmlElement("thumbsdown")] public required int BooCount { get; set; }
Beyley marked this conversation as resolved.
Show resolved Hide resolved

[XmlArray("customRewards")]
[XmlArrayItem("customReward")]
public required List<GameSkillReward> SkillRewards { get; set; }

[XmlElement("mmpick")] public required bool TeamPicked { get; set; }
[XmlElement("resource")] public List<string> XmlResources { get; set; } = new();
[XmlElement("playerCount")] public int PlayerCount { get; set; }
Beyley marked this conversation as resolved.
Show resolved Hide resolved

public static GameLevelRequest? FromOldWithExtraData(GameLevel? old, GameDatabaseContext database, MatchService matchService, GameUser user)
{
if (old == null) return null;

GameLevelRequest response = FromOld(old)!;
response.FillInExtraData(database, matchService, user);

return response;
}

public static GameLevelRequest? FromOld(GameLevel? old)
{
if (old == null) return null;

GameLevelRequest response = new()
Beyley marked this conversation as resolved.
Show resolved Hide resolved
{
LevelId = old.LevelId,
Title = old.Title,
IconHash = old.IconHash,
Description = old.Description,
Location = old.Location,
GameVersion = old.GameVersion.ToSerializedGame(),
RootResource = old.RootResource,
PublishDate = old.PublishDate,
UpdateDate = old.UpdateDate,
MinPlayers = old.MinPlayers,
MaxPlayers = old.MaxPlayers,
EnforceMinMaxPlayers = old.EnforceMinMaxPlayers,
SameScreenGame = old.SameScreenGame,
HeartCount = old.FavouriteRelations.Count(),
TotalPlayCount = old.AllPlays.Count(),
UniquePlayCount = old.UniquePlays.Count(),
YayCount = old.Ratings.Count(r => r._RatingType == (int)RatingType.Yay),
BooCount = old.Ratings.Count(r => r._RatingType == (int)RatingType.Boo),
SkillRewards = old.SkillRewards.ToList(),
TeamPicked = old.TeamPicked,
};

if (old.Publisher == null)
{
response.Type = "developer";
}
else
{
response.Type = "user";
response.Handle = SerializedUserHandle.FromUser(old.Publisher);
}
Beyley marked this conversation as resolved.
Show resolved Hide resolved

return response;
}

public GameLevel ToGameLevel(GameUser publisher) =>
new()
{
LevelId = this.LevelId,
Title = this.Title,
IconHash = this.IconHash,
Description = this.Description,
Location = this.Location,
RootResource = this.RootResource,
PublishDate = this.PublishDate,
UpdateDate = this.UpdateDate,
MinPlayers = this.MinPlayers,
MaxPlayers = this.MaxPlayers,
EnforceMinMaxPlayers = this.EnforceMinMaxPlayers,
SameScreenGame = this.SameScreenGame,
SkillRewards = this.SkillRewards.ToArray(),
Publisher = publisher,
};

public static IEnumerable<GameLevelRequest> FromOldList(IEnumerable<GameLevel> oldList) => oldList.Select(FromOld)!;

private void FillInExtraData(GameDatabaseContext database, MatchService matchService, GameUser user)
{
GameLevel? level = database.GetLevelById(this.LevelId);
if (level == null) throw new InvalidOperationException("Cannot fill in level data for a level that does not exist.");

this.YourRating = (int)database.GetRatingByUser(level, user);
this.PlayerCount = matchService.GetPlayerCountForLevel(RoomSlotType.Online, this.LevelId);
}
}
Beyley marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 7 additions & 15 deletions Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,19 @@ namespace Refresh.GameServer.Endpoints.Game.Levels;
public class LeaderboardEndpoints : EndpointGroup
{
[GameEndpoint("play/user/{id}", ContentType.Xml, Method.Post)]
public Response PlayLevel(RequestContext context, GameUser user, GameDatabaseContext database, int? id)
public Response PlayLevel(RequestContext context, GameUser user, GameDatabaseContext database, int id)
{
if (id == null) return BadRequest;

GameLevel? level = database.GetLevelById(id.Value);
GameLevel? level = database.GetLevelById(id);
if (level == null) return NotFound;

database.PlayLevel(level, user);
return OK;
}

[GameEndpoint("scoreboard/user/{id}", ContentType.Xml, Method.Post)]
public Response SubmitScore(RequestContext context, GameUser user, GameDatabaseContext database, int? id, SerializedScore body)
public Response SubmitScore(RequestContext context, GameUser user, GameDatabaseContext database, int id, SerializedScore body)
{
if (id == null) return BadRequest;

GameLevel? level = database.GetLevelById(id.Value);
GameLevel? level = database.GetLevelById(id);
if (level == null) return NotFound;

//Validate the score is a non-negative amount
Expand All @@ -41,8 +37,7 @@ public Response SubmitScore(RequestContext context, GameUser user, GameDatabaseC
return BadRequest;
}

GameSubmittedScore? score = database.SubmitScore(body, user, level);
if (score == null) return Unauthorized;
GameSubmittedScore score = database.SubmitScore(body, user, level);

IEnumerable<ScoreWithRank>? scores = database.GetRankedScoresAroundScore(score, 5);
Debug.Assert(scores != null);
Expand All @@ -52,12 +47,9 @@ public Response SubmitScore(RequestContext context, GameUser user, GameDatabaseC

[GameEndpoint("topscores/user/{id}/{type}", ContentType.Xml)]
[MinimumRole(GameUserRole.Restricted)]
public SerializedScoreList? GetTopScoresForLevel(RequestContext context, GameDatabaseContext database, int? id, int? type)
public SerializedScoreList? GetTopScoresForLevel(RequestContext context, GameDatabaseContext database, int id, int type)
{
if (id == null) return null;
if (type == null) return null;

GameLevel? level = database.GetLevelById(id.Value);
GameLevel? level = database.GetLevelById(id);
if (level == null) return null;

(int skip, int count) = context.GetPageData();
Expand Down
63 changes: 56 additions & 7 deletions Refresh.GameServer/Endpoints/Game/Levels/PublishEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
using Bunkum.HttpServer.Endpoints;
using Bunkum.HttpServer.Responses;
using Bunkum.HttpServer.Storage;
using NotEnoughLogs;
using Refresh.GameServer.Authentication;
using Refresh.GameServer.Database;
using Refresh.GameServer.Endpoints.Game.DataTypes.Request;
using Refresh.GameServer.Endpoints.Game.DataTypes.Response;
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.UserData;
Expand All @@ -13,10 +15,57 @@ namespace Refresh.GameServer.Endpoints.Game.Levels;

public class PublishEndpoints : EndpointGroup
{
/// <summary>
/// Does basic verification on a level
/// </summary>
/// <param name="body">The level to verify</param>
/// <param name="user">The user that is attempting to upload</param>
/// <param name="logger">A logger instance</param>
/// <returns>Whether or not validation succeeded</returns>
private static bool VerifyLevel(GameLevelRequest body, GameUser user, LoggerContainer<BunkumContext> logger)
{
if (body.Title.Length > 256)
{
return false;
}

if (body.Description.Length > 4096)
{
return false;
}

if (body.MaxPlayers > 4 || body.MinPlayers > 4)
{
return false;
}

if (body.TeamPicked)
{
logger.LogWarning(BunkumContext.UserContent, $"User {user.Username} attempted to force their level to be team picked! This is very likely a forged request.");
return false;
}

if (body.BooCount != 0 || body.YayCount != 0 || body.HeartCount != 0)
{
logger.LogWarning(BunkumContext.UserContent, $"User {user.Username} attempted to force non-0 boo/yay/heart counts! This is very likely a forged request.");
return false;
}

if (body.PlayerCount > 4)
{
return false;
}
Beyley marked this conversation as resolved.
Show resolved Hide resolved

return true;
}

[GameEndpoint("startPublish", ContentType.Xml, Method.Post)]
[NullStatusCode(BadRequest)]
public SerializedLevelResources? StartPublish(RequestContext context, GameDatabaseContext database, GameLevelResponse body, IDataStore dataStore)
public SerializedLevelResources? StartPublish(RequestContext context, GameUser user, GameDatabaseContext database, GameLevelRequest body, IDataStore dataStore, LoggerContainer<BunkumContext> logger)
{
//If verifying the request fails, return null
if (!VerifyLevel(body, user, logger)) return null;

List<string> hashes = new();
hashes.AddRange(body.XmlResources);
hashes.Add(body.RootResource);
Expand All @@ -33,8 +82,11 @@ public class PublishEndpoints : EndpointGroup
}

[GameEndpoint("publish", ContentType.Xml, Method.Post)]
public Response PublishLevel(RequestContext context, GameUser user, Token token, GameDatabaseContext database, GameLevelResponse body, IDataStore dataStore)
public Response PublishLevel(RequestContext context, GameUser user, Token token, GameDatabaseContext database, GameLevelRequest body, IDataStore dataStore, LoggerContainer<BunkumContext> logger)
{
//If verifying the request fails, return null
if (!VerifyLevel(body, user, logger)) return BadRequest;

GameLevel level = body.ToGameLevel(user);
level.GameVersion = token.TokenGame;

Expand Down Expand Up @@ -67,12 +119,9 @@ public Response PublishLevel(RequestContext context, GameUser user, Token token,
return new Response(GameLevelResponse.FromOld(level)!, ContentType.Xml);
}

[GameEndpoint("unpublish/{idStr}", ContentType.Xml, Method.Post)]
public Response DeleteLevel(RequestContext context, GameUser user, GameDatabaseContext database, string idStr)
[GameEndpoint("unpublish/{id}", ContentType.Xml, Method.Post)]
public Response DeleteLevel(RequestContext context, GameUser user, GameDatabaseContext database, int id)
{
int.TryParse(idStr, out int id);
if (id == default) return BadRequest;

GameLevel? level = database.GetLevelById(id);
if (level == null) return NotFound;

Expand Down
28 changes: 8 additions & 20 deletions Refresh.GameServer/Endpoints/Game/RelationEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ namespace Refresh.GameServer.Endpoints.Game;

public class RelationEndpoints : EndpointGroup
{
[GameEndpoint("favourite/slot/user/{idStr}", Method.Post)]
public Response FavouriteLevel(RequestContext context, GameDatabaseContext database, GameUser user, string idStr)
[GameEndpoint("favourite/slot/user/{id}", Method.Post)]
public Response FavouriteLevel(RequestContext context, GameDatabaseContext database, GameUser user, int id)
{
int.TryParse(idStr, out int id);
if (id == default) return BadRequest;

GameLevel? level = database.GetLevelById(id);
if (level == null) return NotFound;

Expand All @@ -30,12 +27,9 @@ public Response FavouriteLevel(RequestContext context, GameDatabaseContext datab
return Unauthorized;
}

[GameEndpoint("unfavourite/slot/user/{idStr}", Method.Post)]
public Response UnfavouriteLevel(RequestContext context, GameDatabaseContext database, GameUser user, string idStr)
[GameEndpoint("unfavourite/slot/user/{id}", Method.Post)]
public Response UnfavouriteLevel(RequestContext context, GameDatabaseContext database, GameUser user, int id)
{
int.TryParse(idStr, out int id);
if (id == default) return BadRequest;

GameLevel? level = database.GetLevelById(id);
if (level == null) return NotFound;

Expand Down Expand Up @@ -84,12 +78,9 @@ public Response UnfavouriteUser(RequestContext context, GameDatabaseContext data
return new SerializedFavouriteUserList(GameUserResponse.FromOldListWithExtraData(users, token.TokenGame).ToList(), users.Count);
}

[GameEndpoint("lolcatftw/add/user/{idStr}", Method.Post)]
public Response QueueLevel(RequestContext context, GameDatabaseContext database, GameUser user, string idStr)
[GameEndpoint("lolcatftw/add/user/{id}", Method.Post)]
public Response QueueLevel(RequestContext context, GameDatabaseContext database, GameUser user, int id)
{
int.TryParse(idStr, out int id);
if (id == default) return BadRequest;

GameLevel? level = database.GetLevelById(id);
if (level == null) return NotFound;

Expand All @@ -99,12 +90,9 @@ public Response QueueLevel(RequestContext context, GameDatabaseContext database,
return Unauthorized;
}

[GameEndpoint("lolcatftw/remove/user/{idStr}", Method.Post)]
public Response DequeueLevel(RequestContext context, GameDatabaseContext database, GameUser user, string idStr)
[GameEndpoint("lolcatftw/remove/user/{id}", Method.Post)]
public Response DequeueLevel(RequestContext context, GameDatabaseContext database, GameUser user, int id)
{
int.TryParse(idStr, out int id);
if (id == default) return BadRequest;

GameLevel? level = database.GetLevelById(id);
if (level == null) return NotFound;

Expand Down
7 changes: 6 additions & 1 deletion Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ namespace Refresh.GameServer.Endpoints.Game;
public class ReportingEndpoints : EndpointGroup
{
[GameEndpoint("grief", Method.Post, ContentType.Xml)]
public Response UploadReport(RequestContext context, GameDatabaseContext database, GameReport body)
public Response UploadReport(RequestContext context, GameDatabaseContext database, GameReport body)
{
if ((body.LevelId != 0 && database.GetLevelById(body.LevelId) == null) || body.Players.Length > 4 || body.ScreenElements.Player.Length > 4)
{
return BadRequest;
}

database.AddGriefReport(body);

return OK;
Expand Down
Loading