diff --git a/pkg/accessreview/drivers/brex.go b/pkg/accessreview/drivers/brex.go index a286d34ab..5f799ad7f 100644 --- a/pkg/accessreview/drivers/brex.go +++ b/pkg/accessreview/drivers/brex.go @@ -68,7 +68,7 @@ func (d *BrexDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) Email: u.Email, FullName: u.FirstName + " " + u.LastName, Role: u.Role, - Active: u.Status == "ACTIVE", + Active: new(u.Status == "ACTIVE"), IsAdmin: false, ExternalID: u.ID, MFAStatus: coredata.MFAStatusUnknown, diff --git a/pkg/accessreview/drivers/cloudflare.go b/pkg/accessreview/drivers/cloudflare.go index 9d5da2829..a0a721dbc 100644 --- a/pkg/accessreview/drivers/cloudflare.go +++ b/pkg/accessreview/drivers/cloudflare.go @@ -184,7 +184,7 @@ func (d *CloudflareDriver) queryAllMembers(ctx context.Context, accountID string Email: m.User.Email, FullName: m.User.FirstName + " " + m.User.LastName, Role: role, - Active: m.Status == "accepted", + Active: new(m.Status == "accepted"), IsAdmin: isAdmin, ExternalID: m.ID, MFAStatus: mfaStatus, diff --git a/pkg/accessreview/drivers/csv.go b/pkg/accessreview/drivers/csv.go index d36f54e38..3e0945e95 100644 --- a/pkg/accessreview/drivers/csv.go +++ b/pkg/accessreview/drivers/csv.go @@ -88,7 +88,7 @@ func (d *CSVDriver) ListAccounts(_ context.Context) ([]AccountRecord, error) { record.IsAdmin = strings.TrimSpace(strings.ToLower(row[idx])) == "true" } if idx, ok := colIndex["active"]; ok && idx < len(row) { - record.Active = strings.TrimSpace(strings.ToLower(row[idx])) == "true" + record.Active = new(strings.TrimSpace(strings.ToLower(row[idx])) == "true") } if idx, ok := colIndex["external_id"]; ok && idx < len(row) { record.ExternalID = strings.TrimSpace(row[idx]) diff --git a/pkg/accessreview/drivers/docusign.go b/pkg/accessreview/drivers/docusign.go index f98c9be50..b4e2008b1 100644 --- a/pkg/accessreview/drivers/docusign.go +++ b/pkg/accessreview/drivers/docusign.go @@ -93,7 +93,7 @@ func (d *DocuSignDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err FullName: u.UserName, Role: u.PermissionProfileName, JobTitle: u.JobTitle, - Active: strings.EqualFold(u.UserStatus, "active"), + Active: new(strings.EqualFold(u.UserStatus, "active")), IsAdmin: strings.EqualFold(u.IsAdmin, "True"), ExternalID: u.UserID, MFAStatus: coredata.MFAStatusUnknown, diff --git a/pkg/accessreview/drivers/driver.go b/pkg/accessreview/drivers/driver.go index ccc1f7407..95b135ac3 100644 --- a/pkg/accessreview/drivers/driver.go +++ b/pkg/accessreview/drivers/driver.go @@ -23,13 +23,23 @@ import ( ) // AccountRecord represents a single account from an access source or identity -// source. All fields are best-effort; sources populate what they can. +// source. All fields are best-effort; sources populate what they can. Drivers +// must return ALL accounts the source exposes (including inactive / suspended +// / deleted); classification is the job of the reviewer or of an agent run +// against the campaign, not of the fetch pipeline. +// +// Active is three-valued: nil means the source API has no explicit +// account-status signal for this account (the driver cannot tell), a non-nil +// pointer means the driver observed an explicit signal (true = active at +// source, false = deactivated / suspended / deleted). Drivers whose API does +// not distinguish active from deactivated accounts must leave Active nil +// rather than fabricate a value. type AccountRecord struct { Email string FullName string Role string // system role/permission (e.g. "Admin", "Viewer") JobTitle string // HR job title / department (e.g. "Software Engineer") - Active bool + Active *bool IsAdmin bool MFAStatus coredata.MFAStatus AuthMethod coredata.AccessEntryAuthMethod diff --git a/pkg/accessreview/drivers/github.go b/pkg/accessreview/drivers/github.go index 01530eca4..9bb5e42bd 100644 --- a/pkg/accessreview/drivers/github.go +++ b/pkg/accessreview/drivers/github.go @@ -119,7 +119,7 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error Email: profile.Email, FullName: fullName, Role: membership.Role, - Active: membership.State == "active", + Active: new(membership.State == "active"), IsAdmin: membership.Role == "admin", MFAStatus: mfaStatus, AuthMethod: coredata.AccessEntryAuthMethodUnknown, diff --git a/pkg/accessreview/drivers/google_workspace.go b/pkg/accessreview/drivers/google_workspace.go index ad679e5a8..d57e2fea0 100644 --- a/pkg/accessreview/drivers/google_workspace.go +++ b/pkg/accessreview/drivers/google_workspace.go @@ -109,7 +109,7 @@ func (d *GoogleWorkspaceDriver) ListAccounts(ctx context.Context) ([]AccountReco rec := AccountRecord{ Email: u.PrimaryEmail, FullName: u.Name.FullName, - Active: !u.Suspended && !u.Archived, + Active: new(!u.Suspended && !u.Archived), IsAdmin: u.IsAdmin, ExternalID: u.Id, MFAStatus: coredata.MFAStatusUnknown, diff --git a/pkg/accessreview/drivers/hubspot.go b/pkg/accessreview/drivers/hubspot.go index 2e1f8aa52..e81c23a5f 100644 --- a/pkg/accessreview/drivers/hubspot.go +++ b/pkg/accessreview/drivers/hubspot.go @@ -99,7 +99,6 @@ func (d *HubSpotDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro Email: u.Email, FullName: fullName, Role: role, - Active: true, IsAdmin: u.SuperAdmin, ExternalID: u.ID, MFAStatus: coredata.MFAStatusUnknown, diff --git a/pkg/accessreview/drivers/intercom.go b/pkg/accessreview/drivers/intercom.go index 8c5135302..465343b3b 100644 --- a/pkg/accessreview/drivers/intercom.go +++ b/pkg/accessreview/drivers/intercom.go @@ -67,7 +67,6 @@ func (d *IntercomDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err FullName: a.Name, Role: intercomRole(a.HasInboxSeat), JobTitle: a.JobTitle, - Active: true, IsAdmin: false, // Intercom API does not expose admin role information ExternalID: a.ID, MFAStatus: coredata.MFAStatusUnknown, diff --git a/pkg/accessreview/drivers/linear.go b/pkg/accessreview/drivers/linear.go index 92d6b929b..ce774aa21 100644 --- a/pkg/accessreview/drivers/linear.go +++ b/pkg/accessreview/drivers/linear.go @@ -97,7 +97,7 @@ func (d *LinearDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error Email: u.Email, FullName: u.Name, Role: linearRole(u.Admin, u.Guest), - Active: u.Active, + Active: new(u.Active), IsAdmin: u.Admin, ExternalID: u.ID, MFAStatus: coredata.MFAStatusUnknown, diff --git a/pkg/accessreview/drivers/notion.go b/pkg/accessreview/drivers/notion.go index 3f5d978d2..4f8a96230 100644 --- a/pkg/accessreview/drivers/notion.go +++ b/pkg/accessreview/drivers/notion.go @@ -81,7 +81,6 @@ func (d *NotionDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error Email: email, FullName: u.Name, Role: "Member", - Active: true, IsAdmin: false, ExternalID: u.ID, MFAStatus: coredata.MFAStatusUnknown, diff --git a/pkg/accessreview/drivers/onepassword.go b/pkg/accessreview/drivers/onepassword.go index 74ba415ab..054398672 100644 --- a/pkg/accessreview/drivers/onepassword.go +++ b/pkg/accessreview/drivers/onepassword.go @@ -93,7 +93,7 @@ func (d *OnePasswordDriver) ListAccounts(ctx context.Context) ([]AccountRecord, record := AccountRecord{ Email: email, FullName: u.DisplayName, - Active: u.Active, + Active: new(u.Active), ExternalID: u.ID, MFAStatus: coredata.MFAStatusUnknown, AuthMethod: coredata.AccessEntryAuthMethodUnknown, diff --git a/pkg/accessreview/drivers/onepassword_users_api.go b/pkg/accessreview/drivers/onepassword_users_api.go index e259da6b9..273f87fb1 100644 --- a/pkg/accessreview/drivers/onepassword_users_api.go +++ b/pkg/accessreview/drivers/onepassword_users_api.go @@ -86,7 +86,7 @@ func (d *OnePasswordUsersAPIDriver) ListAccounts(ctx context.Context) ([]Account record := AccountRecord{ Email: u.Email, FullName: u.DisplayName, - Active: u.State == "ACTIVE", + Active: new(u.State == "ACTIVE"), ExternalID: u.ID, MFAStatus: coredata.MFAStatusUnknown, AuthMethod: coredata.AccessEntryAuthMethodUnknown, diff --git a/pkg/accessreview/drivers/openai.go b/pkg/accessreview/drivers/openai.go index 00db43d67..2dee0a4ce 100644 --- a/pkg/accessreview/drivers/openai.go +++ b/pkg/accessreview/drivers/openai.go @@ -68,7 +68,7 @@ func (d *OpenAIDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error Email: u.Email, FullName: u.Name, Role: openaiRole(u.Role), - Active: !u.Disabled, + Active: new(!u.Disabled), IsAdmin: u.Role == "owner", ExternalID: u.ID, MFAStatus: coredata.MFAStatusUnknown, diff --git a/pkg/accessreview/drivers/probo_memberships.go b/pkg/accessreview/drivers/probo_memberships.go index dc857d5a9..96f77a0e3 100644 --- a/pkg/accessreview/drivers/probo_memberships.go +++ b/pkg/accessreview/drivers/probo_memberships.go @@ -69,7 +69,7 @@ func (d *ProboMembershipsDriver) ListAccounts(ctx context.Context) ([]AccountRec Email: account.Email, FullName: account.FullName, Role: role, - Active: account.State == string(coredata.ProfileStateActive), + Active: new(account.State == string(coredata.ProfileStateActive)), IsAdmin: isAdmin, ExternalID: account.ID.String(), CreatedAt: &createdAt, diff --git a/pkg/accessreview/drivers/resend.go b/pkg/accessreview/drivers/resend.go index 6bcf294b1..d25482e4b 100644 --- a/pkg/accessreview/drivers/resend.go +++ b/pkg/accessreview/drivers/resend.go @@ -57,7 +57,6 @@ func (d *ResendDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error for _, k := range resp.Data { record := AccountRecord{ FullName: k.Name, - Active: true, IsAdmin: false, ExternalID: k.ID, MFAStatus: coredata.MFAStatusUnknown, diff --git a/pkg/accessreview/drivers/sentry.go b/pkg/accessreview/drivers/sentry.go index 6255ed7da..443151f21 100644 --- a/pkg/accessreview/drivers/sentry.go +++ b/pkg/accessreview/drivers/sentry.go @@ -144,7 +144,7 @@ func (d *SentryDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error Email: m.Email, FullName: fullName, Role: m.OrgRole, - Active: active, + Active: new(active), IsAdmin: isAdmin, ExternalID: m.ID, MFAStatus: mfaStatus, diff --git a/pkg/accessreview/drivers/slack.go b/pkg/accessreview/drivers/slack.go index 5a460cf05..7fdfe18ef 100644 --- a/pkg/accessreview/drivers/slack.go +++ b/pkg/accessreview/drivers/slack.go @@ -101,7 +101,7 @@ func (d *SlackDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) FullName: m.RealName, JobTitle: m.Profile.Title, Role: slackRole(m), - Active: !m.Deleted, + Active: new(!m.Deleted), IsAdmin: m.IsAdmin || m.IsOwner || m.IsPrimaryOwner, ExternalID: m.ID, MFAStatus: slackMFAStatus(m.Has2FA), diff --git a/pkg/accessreview/drivers/supabase.go b/pkg/accessreview/drivers/supabase.go index f31960ff4..2ead919e3 100644 --- a/pkg/accessreview/drivers/supabase.go +++ b/pkg/accessreview/drivers/supabase.go @@ -65,7 +65,6 @@ func (d *SupabaseDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err Email: m.Email, FullName: m.UserName, Role: m.RoleName, - Active: true, IsAdmin: isAdmin, ExternalID: m.UserID, MFAStatus: mfaStatus, diff --git a/pkg/accessreview/drivers/tally.go b/pkg/accessreview/drivers/tally.go index 8434ae9ac..a4d5d8d1c 100644 --- a/pkg/accessreview/drivers/tally.go +++ b/pkg/accessreview/drivers/tally.go @@ -115,7 +115,7 @@ func (d *TallyDriver) listUsers(ctx context.Context) ([]AccountRecord, error) { record := AccountRecord{ Email: u.Email, FullName: u.FullName, - Active: !u.IsDeleted, + Active: new(!u.IsDeleted), ExternalID: u.ID, MFAStatus: mfaStatus, AuthMethod: coredata.AccessEntryAuthMethodUnknown, @@ -169,7 +169,7 @@ func (d *TallyDriver) listInvites(ctx context.Context) ([]AccountRecord, error) for _, inv := range invites { record := AccountRecord{ Email: inv.Email, - Active: false, + Active: new(false), ExternalID: inv.ID, MFAStatus: coredata.MFAStatusUnknown, AuthMethod: coredata.AccessEntryAuthMethodUnknown, diff --git a/pkg/accessreview/review_engine_test.go b/pkg/accessreview/review_engine_test.go index 49e8008f2..bb5c1b95f 100644 --- a/pkg/accessreview/review_engine_test.go +++ b/pkg/accessreview/review_engine_test.go @@ -14,7 +14,9 @@ package accessreview -import "testing" +import ( + "testing" +) func TestNormalizeAccountKey(t *testing.T) { t.Parallel()