mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-05-15 06:02:28 +00:00
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:
parent
5a3439e634
commit
2ab1e72037
12 changed files with 210 additions and 12 deletions
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
||||
|
|
41
ProjectLighthouse/PlayerData/Profiles/PrivacyType.cs
Normal file
41
ProjectLighthouse/PlayerData/Profiles/PrivacyType.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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) +
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue