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 all 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
4 changes: 1 addition & 3 deletions Refresh.GameServer/Database/GameDatabaseContext.Photos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ public partial class GameDatabaseContext // Photos
{
public void UploadPhoto(SerializedPhoto photo, GameUser publisher)
{
long firstValidTime = new DateTimeOffset(2007, 1, 1, 0, 0, 0, TimeSpan.Zero).ToUnixTimeSeconds();

GamePhoto newPhoto = new()
{
SmallHash = photo.SmallHash,
Expand All @@ -25,7 +23,7 @@ public void UploadPhoto(SerializedPhoto photo, GameUser publisher)
LevelType = photo.Level.Type,
LevelId = photo.Level.LevelId,

TakenAt = DateTimeOffset.FromUnixTimeSeconds(Math.Clamp(photo.Timestamp, firstValidTime, this._time.TimestampSeconds)),
TakenAt = DateTimeOffset.FromUnixTimeSeconds(Math.Clamp(photo.Timestamp, this._time.EarliestDate, this._time.TimestampSeconds)),
PublishedAt = this._time.Now,
};

Expand Down
8 changes: 7 additions & 1 deletion Refresh.GameServer/Endpoints/Game/CommentEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using NotEnoughLogs;
using Refresh.GameServer.Database;
using Refresh.GameServer.Extensions;
using Refresh.GameServer.Time;
using Refresh.GameServer.Types.Comments;
using Refresh.GameServer.Types.Levels;
using Refresh.GameServer.Types.Lists;
Expand All @@ -16,8 +17,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,94 @@
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!;

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

[XmlElement("resource")] public List<string> XmlResources { get; set; } = new();

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

GameLevelRequest request = new()
{
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,
SkillRewards = old.SkillRewards.ToList(),
};

return request;
}

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)!;
}
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
46 changes: 39 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,40 @@ 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;
}

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 +65,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 +102,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
5 changes: 4 additions & 1 deletion Refresh.GameServer/Endpoints/Game/ResourceEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Refresh.GameServer.Configuration;
using Refresh.GameServer.Database;
using Refresh.GameServer.Importing;
using Refresh.GameServer.Time;
using Refresh.GameServer.Types.Assets;
using Refresh.GameServer.Types.Lists;
using Refresh.GameServer.Types.Roles;
Expand All @@ -23,7 +24,7 @@ public class ResourceEndpoints : EndpointGroup
[GameEndpoint("upload/{hash}", Method.Post)]
[SuppressMessage("ReSharper", "ConvertIfStatementToReturnStatement")]
public Response UploadAsset(RequestContext context, string hash, string type, byte[] body, IDataStore dataStore,
GameDatabaseContext database, GameUser user, AssetImporter importer, GameServerConfig config)
GameDatabaseContext database, GameUser user, AssetImporter importer, GameServerConfig config, IDateTimeProvider timeProvider)
{
if (dataStore.ExistsInStore(hash))
return Conflict;
Expand All @@ -32,6 +33,8 @@ public Response UploadAsset(RequestContext context, string hash, string type, by
if (gameAsset == null)
return BadRequest;

gameAsset.UploadDate = DateTimeOffset.FromUnixTimeSeconds(Math.Clamp(gameAsset.UploadDate.ToUnixTimeSeconds(), timeProvider.EarliestDate, timeProvider.TimestampSeconds));

// for example, if asset safety level is Dangerous (2) and maximum is configured as Safe (0), return 401
// if asset safety is Safe (0), and maximum is configured as Safe (0), proceed
if (gameAsset.SafetyLevel > config.MaximumAssetSafetyLevel)
Expand Down
Loading
Loading