Implement basic privacy settings (#392)

* Add ability for clients to submit and retrieve privacy settings data

* Make slot pages and user pages respect user's privacy settings

* Prevent webhook from publishing new levels if user's privacy settings disallow it

* Hide levels/profiles from respective pages depending on privacy settings

* Apply suggestions from review
This commit is contained in:
Jayden 2022-08-02 18:22:56 -04:00 committed by GitHub
parent 5a3439e634
commit 2ab1e72037
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 210 additions and 12 deletions

View file

@ -1,7 +1,9 @@
#nullable enable
using System.Diagnostics.CodeAnalysis;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc;
@ -45,12 +47,56 @@ public class ClientConfigurationController : ControllerBase
[HttpGet("privacySettings")]
[Produces("text/xml")]
public IActionResult PrivacySettings()
public async Task<IActionResult> GetPrivacySettings()
{
User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, "");
PrivacySettings ps = new()
{
LevelVisibility = "all",
ProfileVisibility = "all",
LevelVisibility = user.LevelVisibility.ToSerializedString(),
ProfileVisibility = user.ProfileVisibility.ToSerializedString(),
};
return this.Ok(ps.Serialize());
}
[HttpPost("privacySettings")]
[Produces("text/xml")]
public async Task<IActionResult> SetPrivacySetting()
{
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));
if (settings == null) return this.BadRequest();
if (settings.LevelVisibility != null)
{
PrivacyType? type = PrivacyTypeExtensions.FromSerializedString(settings.LevelVisibility);
if (type == null) return this.BadRequest();
user.LevelVisibility = (PrivacyType)type;
}
if (settings.ProfileVisibility != null)
{
PrivacyType? type = PrivacyTypeExtensions.FromSerializedString(settings.ProfileVisibility);
if (type == null) return this.BadRequest();
user.ProfileVisibility = (PrivacyType)type;
}
await this.database.SaveChangesAsync();
PrivacySettings ps = new()
{
LevelVisibility = user.LevelVisibility.ToSerializedString(),
ProfileVisibility = user.ProfileVisibility.ToSerializedString(),
};
return this.Ok(ps.Serialize());

View file

@ -234,13 +234,13 @@ public class PublishController : ControllerBase
this.database.Slots.Add(slot);
await this.database.SaveChangesAsync();
await WebhookHelper.SendWebhook
(
"New level published!",
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}"
);
if (user.LevelVisibility == PrivacyType.All)
{
await WebhookHelper.SendWebhook("New level published!",
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}");
}
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));

View file

@ -31,6 +31,29 @@ public class SlotPage : BaseLayout
.Where(s => s.Type == SlotType.User)
.FirstOrDefaultAsync(s => s.SlotId == id);
if (slot == null) return this.NotFound();
System.Diagnostics.Debug.Assert(slot.Creator != null);
// Determine if user can view slot according to creator's privacy settings
if (this.User == null || !this.User.IsAdmin)
{
switch (slot.Creator.ProfileVisibility)
{
case PrivacyType.PSN:
{
if (this.User != null) return this.NotFound();
break;
}
case PrivacyType.Game:
{
if (slot.Creator != this.User) return this.NotFound();
break;
}
case PrivacyType.All: break;
default: throw new ArgumentOutOfRangeException();
}
}
this.Slot = slot;

View file

@ -3,6 +3,7 @@ using System.Text;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc;
@ -70,6 +71,7 @@ public class SlotsPage : BaseLayout
.Where(p => p.Type == SlotType.User)
.Where(p => p.Name.Contains(finalSearch.ToString()))
.Where(p => p.Creator != null && (targetAuthor == null || string.Equals(p.Creator.Username.ToLower(), targetAuthor.ToLower())))
.Where(p => p.Creator!.LevelVisibility == PrivacyType.All) // TODO: change check for when user is logged in
.Where(p => targetGame == null || p.GameVersion == targetGame)
.OrderByDescending(p => p.FirstUploaded)
.Skip(pageNumber * ServerStatics.PageSize)

View file

@ -28,6 +28,28 @@ public class UserPage : BaseLayout
this.ProfileUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == userId);
if (this.ProfileUser == null) return this.NotFound();
// Determine if user can view profile according to profileUser's privacy settings
if (this.User == null || !this.User.IsAdmin)
{
switch (this.ProfileUser.ProfileVisibility)
{
case PrivacyType.PSN:
{
if (this.User != null) return this.NotFound();
break;
}
case PrivacyType.Game:
{
if (this.ProfileUser != this.User) return this.NotFound();
break;
}
case PrivacyType.All: break;
default: throw new ArgumentOutOfRangeException();
}
}
this.Photos = await this.Database.Photos.Include(p => p.Slot).OrderByDescending(p => p.Timestamp).Where(p => p.CreatorId == userId).Take(6).ToListAsync();
if (this.CommentsEnabled)
{

View file

@ -37,6 +37,7 @@ public class UsersPage : BaseLayout
if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) return this.Redirect($"/users/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}");
this.Users = await this.Database.Users.Where(u => !u.Banned && u.Username.Contains(this.SearchValue))
.Where(u => u.ProfileVisibility == PrivacyType.All) // TODO: change check for when user is logged in
.OrderByDescending(b => b.UserId)
.Skip(pageNumber * ServerStatics.PageSize)
.Take(ServerStatics.PageSize)

View file

@ -87,6 +87,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NP/@EntryIndexedValue">NP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PNG/@EntryIndexedValue">PNG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PS/@EntryIndexedValue">PS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PSN/@EntryIndexedValue">PSN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PSP/@EntryIndexedValue">PSP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RPCS/@EntryIndexedValue">RPCS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SMTP/@EntryIndexedValue">SMTP</s:String>

View file

@ -1,16 +1,26 @@
#nullable enable
using System;
using System.Xml.Serialization;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization;
namespace LBPUnion.ProjectLighthouse.Configuration;
[XmlRoot("privacySettings")]
[XmlType("privacySettings")]
public class PrivacySettings
{
public string LevelVisibility { get; set; }
public string ProfileVisibility { get; set; }
[XmlElement("levelVisiblity")]
public string? LevelVisibility { get; set; }
[XmlElement("profileVisiblity")]
public string? ProfileVisibility { get; set; }
public string Serialize()
=> LbpSerializer.StringElement
(
"privacySettings",
LbpSerializer.StringElement("levelVisibility", this.LevelVisibility) + LbpSerializer.StringElement("profileVisibility", this.ProfileVisibility)
LbpSerializer.StringElement("levelVisibility", this.LevelVisibility) +
LbpSerializer.StringElement("profileVisibility", this.ProfileVisibility)
);
}

View file

@ -0,0 +1,42 @@
using System;
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20220801055525_AddPrivacySettingsToUser")]
public partial class AddPrivacySettingsToUser : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "LevelVisibility",
table: "Users",
type: "int",
nullable: false,
defaultValue: 2);
migrationBuilder.AddColumn<int>(
name: "ProfileVisibility",
table: "Users",
type: "int",
nullable: false,
defaultValue: 2);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LevelVisibility",
table: "Users");
migrationBuilder.DropColumn(
name: "ProfileVisibility",
table: "Users");
}
}
}

View file

@ -688,6 +688,9 @@ namespace ProjectLighthouse.Migrations
b.Property<bool>("IsAdmin")
.HasColumnType("tinyint(1)");
b.Property<int>("LevelVisibility")
.HasColumnType("int");
b.Property<int>("LocationId")
.HasColumnType("int");
@ -712,6 +715,9 @@ namespace ProjectLighthouse.Migrations
b.Property<string>("PlanetHashLBPVita")
.HasColumnType("longtext");
b.Property<int>("ProfileVisibility")
.HasColumnType("int");
b.Property<string>("Username")
.HasColumnType("longtext");

View file

@ -0,0 +1,41 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace LBPUnion.ProjectLighthouse.PlayerData.Profiles;
/// <summary>
/// Where user levels/profiles should show.
/// </summary>
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public enum PrivacyType
{
/// <summary>
/// Shows your levels/profile only to those signed in on the website or the game.
/// </summary>
PSN = 0,
/// <summary>
/// Shows your levels/profile only to those in-game.
/// </summary>
Game = 1,
/// <summary>
/// Shows your levels/profile to everyone.
/// </summary>
All = 2,
}
public static class PrivacyTypeExtensions
{
public static string ToSerializedString(this PrivacyType type)
=> type.ToString().ToLower();
public static PrivacyType? FromSerializedString(string type)
{
return type switch
{
"psn" => PrivacyType.PSN,
"game" => PrivacyType.Game,
"all" => PrivacyType.All,
_ => null,
};
}
}

View file

@ -153,6 +153,10 @@ public class User
/// </summary>
public bool IsAPirate { get; set; }
public PrivacyType LevelVisibility { get; set; } = PrivacyType.All;
public PrivacyType ProfileVisibility { get; set; } = PrivacyType.All;
public string Serialize(GameVersion gameVersion = GameVersion.LittleBigPlanet1)
{
string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) +