Skip to content
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
7 changes: 7 additions & 0 deletions Refresh.Database/GameDatabaseContext.Photos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ public GamePhotoSubject AddSubjectForPhoto(GamePhoto photo, int playerId, string
return subject;
}

public GamePhoto? GetPhotoByAnyHash(string smallHash, string mediumHash, string largeHash, string planHash)
=> this.GamePhotosIncluded.FirstOrDefault(p =>
p.SmallAssetHash == smallHash ||
p.MediumAssetHash == mediumHash ||
p.LargeAssetHash == largeHash ||
p.PlanHash == planHash);

public void RemovePhoto(GamePhoto photo)
{
foreach (GameUser subjectUser in this.GetUsersInPhoto(photo).ToArray())
Expand Down
9 changes: 9 additions & 0 deletions Refresh.Interfaces.Game/Endpoints/PhotoEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public Response UploadPhoto(RequestContext context, SerializedPhoto body, GameDa
if (body.PhotoSubjects.Count > 4)
{
context.Logger.LogWarning(BunkumCategory.UserContent, $"Too many subjects in photo, rejecting photo upload. Uploader: {user.UserId}");
database.AddErrorNotification("Photo upload failed", "The photo had more than 4 players", user);
return BadRequest;
}

Expand All @@ -52,6 +53,14 @@ public Response UploadPhoto(RequestContext context, SerializedPhoto body, GameDa
return Unauthorized;
}

GamePhoto? existingPhoto = database.GetPhotoByAnyHash(body.SmallHash, body.MediumHash, body.LargeHash, body.PlanHash);
if (existingPhoto != null)
{
// TODO: show photo names in these error notifications once we start to deserialize and store plan data
database.AddErrorNotification("Photo upload failed", "The photo already exists on the server", user);
return BadRequest;
}

List<string> hashes = [body.LargeHash, body.MediumHash, body.SmallHash];
foreach (string hash in hashes.Distinct())
{
Expand Down
129 changes: 128 additions & 1 deletion RefreshTests.GameServer/Tests/Photos/PhotoEndpointsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Refresh.Database.Models.Photos;
using Refresh.Interfaces.Game.Types.Lists;
using Refresh.Database.Helpers;
using System.Security.Cryptography;

namespace RefreshTests.GameServer.Tests.Photos;

Expand Down Expand Up @@ -318,7 +319,7 @@ public void CantDeleteInvalidPhoto()
Assert.That(message.StatusCode, Is.EqualTo(NotFound));
}

[Test]
[Test]
public void CantDeleteOthersPhoto()
{
using TestContext context = this.GetServer();
Expand Down Expand Up @@ -381,4 +382,130 @@ public void CantDeleteOthersPhoto()
response = message.Content.ReadAsXML<SerializedPhotoList>();
Assert.That(response.Items, Has.Count.EqualTo(1));
}

[Test]
public void CannotUploadPhotoIfDuplicateImage()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

//Upload our """photo"""
HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result;
Assert.That(message.StatusCode, Is.EqualTo(OK));

//Upload our """plan"""
ReadOnlySpan<byte> planData = "PLNbum"u8;
string planHash = HexHelper.BytesToHexString(SHA1.HashData(planData));
message = client.PostAsync("/lbp/upload/" + planHash, new ByteArrayContent(planData.ToArray())).Result;

//Upload another """plan"""
ReadOnlySpan<byte> anotherPlanData = "PLNble"u8;
string anotherPlanHash = HexHelper.BytesToHexString(SHA1.HashData(anotherPlanData));
message = client.PostAsync("/lbp/upload/" + anotherPlanHash, new ByteArrayContent(anotherPlanData.ToArray())).Result;

SerializedPhoto photo1 = new()
{
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
AuthorName = user.Username,
SmallHash = TEST_ASSET_HASH,
MediumHash = TEST_ASSET_HASH,
LargeHash = TEST_ASSET_HASH,
PlanHash = planHash,
Level = new SerializedPhotoLevel
{
LevelId = 0,
Title = "",
Type = "pod",
}
};

//Upload photo once
message = client.PostAsync($"/lbp/uploadPhoto", new StringContent(photo1.AsXML())).Result;
Assert.That(message.StatusCode, Is.EqualTo(OK));

//Upload photo again (different plan hash)
SerializedPhoto photo2 = new()
{
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
AuthorName = user.Username,
SmallHash = TEST_ASSET_HASH,
MediumHash = TEST_ASSET_HASH,
LargeHash = TEST_ASSET_HASH,
PlanHash = anotherPlanHash,
Level = new SerializedPhotoLevel
{
LevelId = 0,
Title = "",
Type = "pod",
}
};
message = client.PostAsync($"/lbp/uploadPhoto", new StringContent(photo2.AsXML())).Result;
Assert.That(message.StatusCode, Is.EqualTo(BadRequest));
Assert.That(context.Database.GetNotificationCountByUser(user), Is.EqualTo(1));
}

[Test]
public void CannotUploadPhotoIfDuplicatePlan()
{
using TestContext context = this.GetServer();
GameUser user = context.CreateUser();
using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

//Upload our """photo"""
HttpResponseMessage message = client.PostAsync($"/lbp/upload/{TEST_ASSET_HASH}", new ReadOnlyMemoryContent(TestAsset)).Result;
Assert.That(message.StatusCode, Is.EqualTo(OK));

//Upload another """photo"""
ReadOnlySpan<byte> anotherTextureData = "TEX r"u8;
string anotherTextureHash = HexHelper.BytesToHexString(SHA1.HashData(anotherTextureData));
message = client.PostAsync("/lbp/upload/" + anotherTextureHash, new ByteArrayContent(anotherTextureData.ToArray())).Result;
Assert.That(message.StatusCode, Is.EqualTo(OK));

//Upload our """plan"""
ReadOnlySpan<byte> planData = "PLNb"u8;
string planHash = HexHelper.BytesToHexString(SHA1.HashData(planData));
message = client.PostAsync("/lbp/upload/" + planHash, new ByteArrayContent(planData.ToArray())).Result;
Assert.That(message.StatusCode, Is.EqualTo(OK));

SerializedPhoto photo1 = new()
{
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
AuthorName = user.Username,
SmallHash = TEST_ASSET_HASH,
MediumHash = TEST_ASSET_HASH,
LargeHash = TEST_ASSET_HASH,
PlanHash = planHash,
Level = new SerializedPhotoLevel
{
LevelId = 0,
Title = "",
Type = "pod",
}
};

//Upload photo once
message = client.PostAsync($"/lbp/uploadPhoto", new StringContent(photo1.AsXML())).Result;
Assert.That(message.StatusCode, Is.EqualTo(OK));

//Upload photo again (different image hashes)
SerializedPhoto photo2 = new()
{
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
AuthorName = user.Username,
SmallHash = anotherTextureHash,
MediumHash = anotherTextureHash,
LargeHash = anotherTextureHash,
PlanHash = planHash,
Level = new SerializedPhotoLevel
{
LevelId = 0,
Title = "",
Type = "pod",
}
};
message = client.PostAsync($"/lbp/uploadPhoto", new StringContent(photo2.AsXML())).Result;
Assert.That(message.StatusCode, Is.EqualTo(BadRequest));
Assert.That(context.Database.GetNotificationCountByUser(user), Is.EqualTo(1));
}
}
Loading