Skip to content

GoogleAdminService.LinkAccountAsync accepts an archived user id #1731

Description

@peterdrier

Found by the adversarial read on #1730 (sprint C3). Third instance of #1707's shape; deliberately left out of that PR because the sprint scopes C3 to the GoogleEmail reads in the GoogleIntegration service files and the rest of the section belongs to another lane.

Problem

GoogleAdminService.LinkAccountAsync (src/Sections/Humans.GoogleIntegration/Services/GoogleAdminService.cs:508, reached from GoogleController.cs:579, POST /Google/Accounts/Link) still carries the pre-#1707 guard:

var user = await userService.GetUserInfoAsync(userId, ct);
if (user is null)
    return new WorkspaceAccountActionResult(false, ErrorMessage: "Human not found.");

Two ways an archived id gets through:

  • Merge tombstone — CachingUserService.GetUserInfoAsync resolves merge chains forward, so a merged-away id returns the survivor's record. Non-null, so the guard passes, and the writes below then run against the tombstone id: AddVerifiedEmailAsync(userId, email), TrySetGoogleEmailStatusFromSyncAsync(userId, …), EnqueueGoogleResyncForUserTeamsAsync(userId).
  • GDPR-anonymized / legacy tombstone — does not redirect, returns itself, non-null, accepted outright.

#1730 narrowed UserInfo.IsActive to exclude tombstones, so the second case is now catchable by IsActive alone — but this call site does not check it.

Fix

Same guard the two already-fixed siblings use (GateController.cs:129-133, EmailProvisioningService.ProvisionNobodiesEmailAsync):

if (user is not { IsActive: true } || user.Id != userId)
    return new WorkspaceAccountActionResult(false, ErrorMessage: "Human not found.");

user.Id != userId covers the merge-forward case; IsActive covers the tombstone-returns-itself case. Error message stays non-disclosing.

While in the file, check the other public methods on GoogleAdminService that take a userId from outside for the same shape — this issue is scoped to whatever that audit turns up in that one service.

Acceptance

  • POST /Google/Accounts/Link with a merged-away id links nothing and writes nothing.
  • Same for a GDPR-anonymized id.
  • Tests under tests/Humans.GoogleIntegration.Tests pin both, asserting the downstream writes were never called.
  • Any other unguarded userId entry point on GoogleAdminService is either fixed or explicitly noted as safe.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsection:google-integrationGoogle Workspace connectors and sync (Humans.GoogleIntegration)section:usersUsers/Profiles section (Humans.Users)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions