Skip to content

Grace period (et al) for membership cleaner#479

Open
andreasfrisch wants to merge 1 commit into
developfrom
fix/membership-cleaner-grace
Open

Grace period (et al) for membership cleaner#479
andreasfrisch wants to merge 1 commit into
developfrom
fix/membership-cleaner-grace

Conversation

@andreasfrisch

Copy link
Copy Markdown
Contributor

💡 Context:

  • Temporary issues in Azure risks removing users from our systems prematurely
  • Removing users from our system does not clean up RBAC permissions

🌟 Changes:

  • Membership cleaner maintains a list of potential deletees
  • Only after a grace period of 7 days are members cleaned up
  • If the Azure user is reactivated or re-appears the deletion is cancelled
  • The membership cleaner also clean up RBAC permissions

@andreasfrisch andreasfrisch requested a review from Copilot June 16, 2026 20:53
@DFDS-Snyk

DFDS-Snyk commented Jun 16, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

"FirstSeenMissingAt" timestamp NOT NULL,
"LastCheckedAt" timestamp NOT NULL,
"CreatedAt" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"UpdatedAt" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreatedAt and UpdatedAt are technically not necessary, but I like them to keep things aligned

(
"Id" uuid NOT NULL,
"UserId" varchar(255) NOT NULL,
"Status" varchar(50) NOT NULL, -- NotFound, Deactivated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be overkill, but I like the additional data for why we remove people

private readonly ILogger<DeactivatedMemberCleanerApplicationService> _logger;

private readonly StringBuilder _sb = new();
private const int GracePeriodDays = 7;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 seems to be our magic grace period number 🤷


namespace SelfService.Domain.Events;

public class MemberMarkedAsMissing : IDomainEvent

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introduced for later usage.
We should be fully event based for this, I believe


namespace SelfService.Domain.Events;

public class MemberRemovedDueToMissingStatus : IDomainEvent

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introduced for later usage.
We should be fully event based for this, I believe

@andreasfrisch andreasfrisch requested a review from SEQUOIIA June 16, 2026 20:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the membership-cleaner workflow to avoid premature deletions due to transient Azure AD issues by tracking “missing” members and only deleting after a 7-day grace period, while also adding RBAC cleanup on deletion.

Changes:

  • Add persistence + repository for tracking missing members across cleaner runs (grace period state).
  • Update DeactivatedMemberCleanerApplicationService to (1) mark members missing, (2) delete only after grace period, and (3) clean RBAC grants/group memberships when deleting.
  • Add SQLite-backed tests covering first-detection vs grace-period-expired deletion behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/SelfService/Infrastructure/Persistence/SelfServiceDbContext.cs Adds DbSet for missing-member tracking.
src/SelfService/Infrastructure/Persistence/MissingMemberRepository.cs New repository for CRUD around missing-member tracking records.
src/SelfService/Domain/Models/MissingMemberRecord.cs New domain model for missing-member state + grace-period checks.
src/SelfService/Domain/Models/IMissingMemberRepository.cs New repository interface.
src/SelfService/Domain/Events/MemberRemovedDueToMissingStatus.cs New domain event type (added in this PR).
src/SelfService/Domain/Events/MemberMarkedAsMissing.cs New domain event type (added in this PR).
src/SelfService/Configuration/Domain.cs Registers IMissingMemberRepository in DI.
src/SelfService/Application/DeactivatedMemberCleanerApplicationService.cs Implements grace-period logic, missing-member bookkeeping, and RBAC cleanup on delete.
src/SelfService.Tests/Infrastructure/Persistence/TestMemberCleaner.cs Adds tests for grace-period behavior.
src/SelfService.Tests/Builders/DeactivatedMemberCleanerApplicationServiceBuilder.cs Updates builder for new cleaner dependencies; mocks RBAC repos.
db/migrations/20260616100000_add-missing-member-record-table.sql Adds SQL migration for MissingMemberRecords.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

(
"Id" uuid NOT NULL,
"UserId" varchar(255) NOT NULL,
"Status" varchar(50) NOT NULL, -- NotFound, Deactivated
Comment on lines +166 to +168
// Keep heartbeat fresh only while still inside grace period.
existingRecord.UpdateLastChecked();
await _missingMemberRepository.Update(existingRecord);
Comment on lines +229 to +231
var permissionGrants = await _rbacPermissionGrantRepository.GetAllWithPredicate(pg =>
pg.AssignedEntityId == userId
);
Comment on lines +261 to +262
var groupMembers = await _rbacGroupMemberRepository.GetAllWithPredicate(gm => gm.UserId == userId);

Comment on lines +11 to +13
public Guid Id { get; set; }
public string UserId { get; set; }
public MissingMemberStatus Status { get; set; }
Comment on lines +186 to +188
// Clean up RBAC permissions
await CleanupRbacPermissions(member.Id.ToString());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants