Skip to content

Commit

Permalink
Add new "SafeMedia" tier between Safe and PotentiallyUnwanted (#405)
Browse files Browse the repository at this point in the history
This PR introduces a new tier for asset safety levels named "SafeMedia".
It's intended to represent assets that are images or audio to help
administrators single out images during content blocking.

Hopefully, this should replace our need to disable asset uploads on
production.
  • Loading branch information
jvyden authored Apr 15, 2024
2 parents 4d8f40f + d258c44 commit db52d64
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 16 deletions.
6 changes: 4 additions & 2 deletions Refresh.GameServer/Configuration/GameServerConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ namespace Refresh.GameServer.Configuration;
[SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")]
public class GameServerConfig : Config
{
public override int CurrentConfigVersion => 11;
public override int CurrentConfigVersion => 12;
public override int Version { get; set; } = 0;

protected override void Migrate(int oldVer, dynamic oldConfig) {}

public string LicenseText { get; set; } = "Welcome to Refresh!";

public AssetSafetyLevel MaximumAssetSafetyLevel { get; set; } = AssetSafetyLevel.Safe;
public AssetSafetyLevel MaximumAssetSafetyLevel { get; set; } = AssetSafetyLevel.SafeMedia;
/// <seealso cref="GameUserRole.Trusted"/>
public AssetSafetyLevel MaximumAssetSafetyLevelForTrustedUsers { get; set; } = AssetSafetyLevel.SafeMedia;
public bool AllowUsersToUseIpAuthentication { get; set; } = false;
public bool UseTicketVerification { get; set; } = true;
public bool RegistrationEnabled { get; set; } = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class ApiInstanceResponse : IApiResponse

public required bool RegistrationEnabled { get; set; }
public required AssetSafetyLevel MaximumAssetSafetyLevel { get; set; }
public required AssetSafetyLevel MaximumAssetSafetyLevelForTrustedUsers { get; set; }

public required IEnumerable<ApiGameAnnouncementResponse> Announcements { get; set; }
public required ApiRichPresenceConfigurationResponse RichPresenceConfiguration { get; set; }
Expand Down
8 changes: 6 additions & 2 deletions Refresh.GameServer/Endpoints/ApiV3/InstanceApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Refresh.GameServer.Endpoints.ApiV3.ApiTypes;
using Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response;
using Refresh.GameServer.Services;
using Refresh.GameServer.Types.Assets;
using Refresh.GameServer.Types.Matching;
using Refresh.GameServer.Types.RichPresence;

Expand Down Expand Up @@ -68,11 +69,14 @@ public ApiResponse<ApiInstanceResponse> GetInstanceInformation(RequestContext co
SoftwareLicenseName = "AGPL-3.0",
SoftwareLicenseUrl = "https://www.gnu.org/licenses/agpl-3.0.txt",
MaximumAssetSafetyLevel = gameConfig.MaximumAssetSafetyLevel,
MaximumAssetSafetyLevelForTrustedUsers = gameConfig.MaximumAssetSafetyLevelForTrustedUsers,
Announcements = ApiGameAnnouncementResponse.FromOldList(database.GetAnnouncements()),
MaintenanceModeEnabled = gameConfig.MaintenanceMode,
RichPresenceConfiguration = ApiRichPresenceConfigurationResponse.FromOld(RichPresenceConfiguration.Create(gameConfig, richConfig))!,
RichPresenceConfiguration = ApiRichPresenceConfigurationResponse.FromOld(RichPresenceConfiguration.Create(
gameConfig,
richConfig))!,
GrafanaDashboardUrl = integrationConfig.GrafanaDashboardUrl,

ContactInfo = new ApiContactInfoResponse
{
AdminName = contactInfoConfig.AdminName,
Expand Down
10 changes: 7 additions & 3 deletions Refresh.GameServer/Endpoints/Game/ResourceEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,18 @@ public Response UploadAsset(RequestContext context, string hash, string type, by
return BadRequest;

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

AssetSafetyLevel safetyLevel = config.MaximumAssetSafetyLevel;
if (user.Role >= GameUserRole.Trusted)
safetyLevel = config.MaximumAssetSafetyLevelForTrustedUsers;

// Dont block any assets uploaded from PSP, and block any unwanted assets,
// 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 && !isPSP)
if (gameAsset.SafetyLevel > safetyLevel && !isPSP)
{
context.Logger.LogWarning(BunkumCategory.UserContent, $"{gameAsset.AssetType} {hash} is above configured safety limit " +
$"({gameAsset.SafetyLevel} > {config.MaximumAssetSafetyLevel})");
context.Logger.LogWarning(BunkumCategory.UserContent, $"{gameAsset.AssetType} {hash} by {user} is above configured safety limit " +
$"({gameAsset.SafetyLevel} > {safetyLevel})");
return Unauthorized;
}

Expand Down
24 changes: 15 additions & 9 deletions Refresh.GameServer/Types/Assets/AssetSafetyLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ public enum AssetSafetyLevel
/// <summary>
/// This asset is used in normal gameplay/operation and is okay to be uploaded.
/// </summary>
/// <seealso cref="SafeMedia"/>
Safe = 0,
/// <summary>
/// This asset is still used in normal gameplay similar to <see cref="Safe"/>, and is okay to be uploaded, but the distinction can be useful.
/// </summary>
SafeMedia = 1,
/// <summary>
/// This asset may disrupt the "vanilla" feel of a server and it's content, but is otherwise harmless.
/// </summary>
PotentiallyUnwanted = 1,
PotentiallyUnwanted = 2,
/// <summary>
/// This asset may cause harm if uploaded.
/// </summary>
Dangerous = 2,
Dangerous = 3,
}

public static class AssetSafetyLevelExtensions
Expand All @@ -24,17 +29,18 @@ public static AssetSafetyLevel FromAssetType(GameAssetType type)
{
GameAssetType.Level => AssetSafetyLevel.Safe,
GameAssetType.Plan => AssetSafetyLevel.Safe,
GameAssetType.Texture => AssetSafetyLevel.Safe,
GameAssetType.Jpeg => AssetSafetyLevel.Safe,
GameAssetType.Png => AssetSafetyLevel.Safe,
GameAssetType.Tga => AssetSafetyLevel.Safe,
GameAssetType.MoveRecording => AssetSafetyLevel.Safe,
GameAssetType.VoiceRecording => AssetSafetyLevel.Safe,
GameAssetType.Painting => AssetSafetyLevel.Safe,
GameAssetType.SyncedProfile => AssetSafetyLevel.Safe,
GameAssetType.Mip => AssetSafetyLevel.Safe,
GameAssetType.GriefSongState => AssetSafetyLevel.Safe,

GameAssetType.VoiceRecording => AssetSafetyLevel.SafeMedia,
GameAssetType.Painting => AssetSafetyLevel.SafeMedia,
GameAssetType.Texture => AssetSafetyLevel.SafeMedia,
GameAssetType.Jpeg => AssetSafetyLevel.SafeMedia,
GameAssetType.Png => AssetSafetyLevel.SafeMedia,
GameAssetType.Tga => AssetSafetyLevel.SafeMedia,
GameAssetType.Mip => AssetSafetyLevel.SafeMedia,

GameAssetType.GfxMaterial => AssetSafetyLevel.PotentiallyUnwanted,
GameAssetType.Material => AssetSafetyLevel.PotentiallyUnwanted,
GameAssetType.Mesh => AssetSafetyLevel.PotentiallyUnwanted,
Expand Down
46 changes: 46 additions & 0 deletions RefreshTests.GameServer/Tests/Assets/AssetUploadTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Security.Cryptography;
using Refresh.GameServer.Authentication;
using Refresh.GameServer.Services;
using Refresh.GameServer.Types.Assets;
using Refresh.GameServer.Types.Lists;
using Refresh.GameServer.Types.Roles;
using Refresh.GameServer.Types.UserData;
Expand Down Expand Up @@ -141,6 +142,51 @@ public void AdminCanUploadAssetWhenBlocked(bool psp)
Assert.That(response.StatusCode, Is.EqualTo(OK));
}

[Test]
public void TrustedCanUploadAssetWithSafetyLevel()
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
context.Server.Value.GameServerConfig.MaximumAssetSafetyLevel = AssetSafetyLevel.Safe;
context.Server.Value.GameServerConfig.MaximumAssetSafetyLevelForTrustedUsers = AssetSafetyLevel.SafeMedia;

GameUser user = context.CreateUser();
context.Database.SetUserRole(user, GameUserRole.Trusted);

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

ReadOnlySpan<byte> data = "TEX a"u8;

string hash = BitConverter.ToString(SHA1.HashData(data))
.Replace("-", "")
.ToLower();

HttpResponseMessage response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(OK));
}

[Test]
public void NormalUserCantUploadAssetWithSafetyLevel()
{
using TestContext context = this.GetServer();
context.Server.Value.Server.AddService<ImportService>();
context.Server.Value.GameServerConfig.MaximumAssetSafetyLevel = AssetSafetyLevel.Safe;
context.Server.Value.GameServerConfig.MaximumAssetSafetyLevelForTrustedUsers = AssetSafetyLevel.SafeMedia;

GameUser user = context.CreateUser();

using HttpClient client = context.GetAuthenticatedClient(TokenType.Game, user);

ReadOnlySpan<byte> data = "TEX a"u8;

string hash = BitConverter.ToString(SHA1.HashData(data))
.Replace("-", "")
.ToLower();

HttpResponseMessage response = client.PostAsync("/lbp/upload/" + hash, new ByteArrayContent(data.ToArray())).Result;
Assert.That(response.StatusCode, Is.EqualTo(Unauthorized));
}

[Test]
public void CantUploadAssetWithInvalidHash()
{
Expand Down

0 comments on commit db52d64

Please sign in to comment.