Skip to content

Commit 3d0201a

Browse files
authored
Improve user fake-deletion (#1050)
More attributes get reset, separate entities get deleted/have their publisher reset based on user ID instead of the whole user, and more entities and relations now get deleted, which were missed before. Also, deleting a user now also disallows their email address from being used again.
2 parents caf2d55 + 1c83be6 commit 3d0201a

13 files changed

Lines changed: 211 additions & 21 deletions

Refresh.Database/GameDatabaseContext.Registration.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public bool IsUsernameTaken(string username, GameUser? userToName = null)
100100
{
101101
if (this.GameUsers.Any(u => u.Username == username)) return true;
102102
if (this.QueuedRegistrations.Any(r => r.Username == username)) return true;
103+
if (this.IsUserDisallowed(username)) return true;
103104

104105
PreviousUsername? previous = this.PreviousUsernames.FirstOrDefault(p => p.Username == username);
105106
// no one has ever had this name before
@@ -113,7 +114,8 @@ public bool IsUsernameTaken(string username, GameUser? userToName = null)
113114
public bool IsEmailTaken(string emailAddress)
114115
{
115116
return this.GameUsers.Any(u => u.EmailAddress == emailAddress) ||
116-
this.QueuedRegistrations.Any(r => r.EmailAddress == emailAddress);
117+
this.QueuedRegistrations.Any(r => r.EmailAddress == emailAddress) ||
118+
this.IsEmailDisallowed(emailAddress);
117119
}
118120

119121
public void AddRegistrationToQueue(string username, string emailAddress, string passwordBcrypt)
@@ -250,4 +252,35 @@ public bool IsUserDisallowed(string username)
250252
{
251253
return this.DisallowedUsers.FirstOrDefault(u => u.Username == username) != null;
252254
}
255+
256+
public bool DisallowEmail(string email)
257+
{
258+
if (this.IsEmailDisallowed(email))
259+
return false;
260+
261+
this.DisallowedEmails.Add(new()
262+
{
263+
Email = email,
264+
});
265+
this.SaveChanges();
266+
267+
return true;
268+
}
269+
270+
public bool ReallowEmail(string email)
271+
{
272+
DisallowedEmail? disallowedEmail = this.DisallowedEmails.FirstOrDefault(u => u.Email == email);
273+
if (disallowedEmail == null)
274+
return false;
275+
276+
this.DisallowedEmails.Remove(disallowedEmail);
277+
this.SaveChanges();
278+
279+
return true;
280+
}
281+
282+
public bool IsEmailDisallowed(string email)
283+
{
284+
return this.DisallowedEmails.Any(u => u.Email == email);
285+
}
253286
}

Refresh.Database/GameDatabaseContext.Users.cs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ public void DeleteUser(GameUser user)
389389
this.BanUser(user, deletedReason, DateTimeOffset.MaxValue);
390390
this.RevokeAllTokensForUser(user);
391391
this.DeleteNotificationsByUser(user);
392+
if (user.EmailAddress != null)
393+
this.DisallowEmail(user.EmailAddress);
392394

393395
this.Write(() =>
394396
{
@@ -402,7 +404,14 @@ public void DeleteUser(GameUser user)
402404
user.Lbp2PlanetsHash = "0";
403405
user.Lbp3PlanetsHash = "0";
404406
user.VitaPlanetsHash = "0";
407+
user.BetaPlanetsHash = "0";
405408
user.IconHash = "0";
409+
user.VitaIconHash = "0";
410+
user.BetaIconHash = "0";
411+
user.PspIconHash = "0";
412+
user.YayFaceHash = "0";
413+
user.BooFaceHash = "0";
414+
user.MehFaceHash = "0";
406415
user.AllowIpAuthentication = false;
407416
user.EmailAddressVerified = false;
408417
user.PsnAuthenticationAllowed = false;
@@ -413,27 +422,36 @@ public void DeleteUser(GameUser user)
413422
subject.UserId = null;
414423
}
415424

416-
this.FavouriteLevelRelations.RemoveRange(r => r.User == user);
417-
this.FavouriteUserRelations.RemoveRange(r => r.UserToFavourite == user);
418-
this.FavouriteUserRelations.RemoveRange(r => r.UserFavouriting == user);
419-
this.QueueLevelRelations.RemoveRange(r => r.User == user);
420-
this.GamePhotos.RemoveRange(p => p.Publisher == user);
421-
this.GameUserVerifiedIpRelations.RemoveRange(p => p.User == user);
425+
this.FavouriteLevelRelations.RemoveRange(r => r.UserId == user.UserId);
426+
this.FavouriteUserRelations.RemoveRange(r => r.UserToFavouriteId == user.UserId);
427+
this.FavouriteUserRelations.RemoveRange(r => r.UserFavouritingId == user.UserId);
428+
this.QueueLevelRelations.RemoveRange(r => r.UserId == user.UserId);
429+
this.TagLevelRelations.RemoveRange(r => r.UserId == user.UserId);
430+
this.GameUserVerifiedIpRelations.RemoveRange(p => p.UserId == user.UserId);
431+
432+
this.GameNotifications.RemoveRange(s => s.UserId == user.UserId);
433+
this.GamePhotos.RemoveRange(p => p.PublisherId == user.UserId);
434+
this.Events.RemoveRange(e => e.UserId == user.UserId);
435+
this.GameScores.RemoveRange(s => s.PublisherId == user.UserId);
436+
this.GameChallengeScores.RemoveRange(s => s.PublisherUserId == user.UserId);
437+
438+
this.GameLevelComments.RemoveRange(s => s.AuthorUserId == user.UserId);
439+
this.LevelCommentRelations.RemoveRange(s => s.UserId == user.UserId);
440+
this.GameProfileComments.RemoveRange(s => s.AuthorUserId == user.UserId);
441+
this.ProfileCommentRelations.RemoveRange(s => s.UserId == user.UserId);
442+
this.GameReviews.RemoveRange(s => s.PublisherUserId == user.UserId);
443+
this.RateReviewRelations.RemoveRange(s => s.UserId == user.UserId);
422444

423-
foreach (GameScore score in this.GameScores.ToList())
424-
{
425-
if (!score.PlayerIds.Contains(user.UserId)) continue;
426-
this.GameScores.Remove(score);
427-
}
428-
429-
foreach (GameLevel level in this.GameLevels.Where(l => l.Publisher == user))
445+
this.PinProgressRelations.RemoveRange(s => s.PublisherId == user.UserId);
446+
this.ProfilePinRelations.RemoveRange(s => s.PublisherId == user.UserId);
447+
this.GamePlaylists.RemoveRange(s => s.PublisherId == user.UserId);
448+
449+
foreach (GameLevel level in this.GameLevels.Where(l => l.PublisherUserId == user.UserId))
430450
{
431451
level.Publisher = null;
432452
}
433453

434-
this.GameChallengeScores.RemoveRange(s => s.Publisher == user);
435-
436-
foreach (GameChallenge challenge in this.GameChallenges.Where(c => c.Publisher == user))
454+
foreach (GameChallenge challenge in this.GameChallenges.Where(c => c.PublisherUserId == user.UserId))
437455
{
438456
challenge.Publisher = null;
439457
}

Refresh.Database/GameDatabaseContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public partial class GameDatabaseContext : DbContext, IDatabaseContext
6464
internal DbSet<AssetDependencyRelation> AssetDependencyRelations { get; set; }
6565
internal DbSet<GameReview> GameReviews { get; set; }
6666
internal DbSet<DisallowedUser> DisallowedUsers { get; set; }
67+
internal DbSet<DisallowedEmail> DisallowedEmails { get; set; }
6768
internal DbSet<RateReviewRelation> RateReviewRelations { get; set; }
6869
internal DbSet<TagLevelRelation> TagLevelRelations { get; set; }
6970
internal DbSet<GamePlaylist> GamePlaylists { get; set; }
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using Microsoft.EntityFrameworkCore.Infrastructure;
2+
using Microsoft.EntityFrameworkCore.Migrations;
3+
4+
#nullable disable
5+
6+
namespace Refresh.Database.Migrations
7+
{
8+
/// <inheritdoc />
9+
[DbContext(typeof(GameDatabaseContext))]
10+
[Migration("20260312203825_AddAbilityToDisallowEmails")]
11+
public partial class AddAbilityToDisallowEmails : Migration
12+
{
13+
/// <inheritdoc />
14+
protected override void Up(MigrationBuilder migrationBuilder)
15+
{
16+
migrationBuilder.CreateTable(
17+
name: "DisallowedEmails",
18+
columns: table => new
19+
{
20+
Email = table.Column<string>(type: "text", nullable: false)
21+
},
22+
constraints: table =>
23+
{
24+
table.PrimaryKey("PK_DisallowedEmails", x => x.Email);
25+
});
26+
}
27+
28+
/// <inheritdoc />
29+
protected override void Down(MigrationBuilder migrationBuilder)
30+
{
31+
migrationBuilder.DropTable(
32+
name: "DisallowedEmails");
33+
}
34+
}
35+
}

Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,16 @@ protected override void BuildModel(ModelBuilder modelBuilder)
15101510
b.ToTable("RequestStatistics");
15111511
});
15121512

1513+
modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedEmail", b =>
1514+
{
1515+
b.Property<string>("Email")
1516+
.HasColumnType("text");
1517+
1518+
b.HasKey("Email");
1519+
1520+
b.ToTable("DisallowedEmails");
1521+
});
1522+
15131523
modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedUser", b =>
15141524
{
15151525
b.Property<string>("Username")

Refresh.Database/Models/Relations/LevelCommentRelation.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ namespace Refresh.Database.Models.Relations;
99
public partial class LevelCommentRelation : ICommentRelation<GameLevelComment>
1010
{
1111
[Key] public ObjectId CommentRelationId { get; set; } = ObjectId.GenerateNewId();
12-
[Required] public GameUser User { get; set; }
12+
13+
[ForeignKey(nameof(UserId)), Required]
14+
public GameUser User { get; set; }
15+
[Required] public ObjectId UserId { get; set; }
16+
1317
[Required] public GameLevelComment Comment { get; set; }
1418
public RatingType RatingType { get; set; }
1519
public DateTimeOffset Timestamp { get; set; }

Refresh.Database/Models/Relations/ProfileCommentRelation.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ namespace Refresh.Database.Models.Relations;
99
public partial class ProfileCommentRelation : ICommentRelation<GameProfileComment>
1010
{
1111
[Key] public ObjectId CommentRelationId { get; set; } = ObjectId.GenerateNewId();
12-
[Required] public GameUser User { get; set; }
12+
13+
[ForeignKey(nameof(UserId)), Required]
14+
public GameUser User { get; set; }
15+
[Required] public ObjectId UserId { get; set; }
16+
1317
[Required] public GameProfileComment Comment { get; set; }
1418
public RatingType RatingType { get; set; }
1519
public DateTimeOffset Timestamp { get; set; }
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Refresh.Database.Models.Users;
2+
3+
#nullable disable
4+
5+
public partial class DisallowedEmail
6+
{
7+
[Key]
8+
public string Email { get; set; }
9+
}

Refresh.GameServer/CommandLineManager.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ private class Options
5959

6060
[Option("reallow-user", HelpText = "Re-allow a user to register. Username option is required if this is set.")]
6161
public bool ReallowUser { get; set; }
62+
63+
[Option("disallow-email", HelpText = "Disallow the email from being used by anyone in the future. Email option is required if this is set.")]
64+
public bool DisallowEmail { get; set; }
65+
66+
[Option("reallow-email", HelpText = "Re-allow the email to be used by anyone. Email option is required if this is set")]
67+
public bool ReallowEmail { get; set; }
6268

6369
[Option("rename-user", HelpText = "Changes a user's username. (old) username or Email option is required if this is set.")]
6470
public string? RenameUser { get; set; }
@@ -188,10 +194,28 @@ private void StartWithOptions(Options options)
188194
}
189195
else Fail("No username was provided");
190196
}
197+
else if (options.DisallowEmail)
198+
{
199+
if (options.EmailAddress != null)
200+
{
201+
if (!this._server.DisallowEmail(options.EmailAddress))
202+
Fail("Email address is already disallowed");
203+
}
204+
else Fail("No email address was provided");
205+
}
206+
else if (options.ReallowEmail)
207+
{
208+
if (options.EmailAddress != null)
209+
{
210+
if (!this._server.ReallowEmail(options.EmailAddress))
211+
Fail("Email address is already allowed");
212+
}
213+
else Fail("No email address was provided");
214+
}
191215
else if (options.RenameUser != null)
192216
{
193217
if(string.IsNullOrWhiteSpace(options.RenameUser))
194-
Fail("Username must contain content");
218+
Fail("Email address must contain content");
195219

196220
GameUser user = this.GetUserOrFail(options);
197221
this._server.RenameUser(user, options.RenameUser, options.Force);

Refresh.GameServer/RefreshGameServer.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,20 @@ public bool ReallowUser(string username)
285285
return context.ReallowUser(username);
286286
}
287287

288+
public bool DisallowEmail(string email)
289+
{
290+
using GameDatabaseContext context = this.GetContext();
291+
292+
return context.DisallowEmail(email);
293+
}
294+
295+
public bool ReallowEmail(string email)
296+
{
297+
using GameDatabaseContext context = this.GetContext();
298+
299+
return context.ReallowEmail(email);
300+
}
301+
288302
public void RenameUser(GameUser user, string newUsername, bool force = false)
289303
{
290304
using GameDatabaseContext context = this.GetContext();

0 commit comments

Comments
 (0)