From a542851e8c4f967cdbfd68829639dc00614a78ff Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Thu, 11 Jun 2026 15:59:10 +0200 Subject: [PATCH] Fix Cursor driver inactive account detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cursor's Admin API exposes two independent removal signals: the isRemoved boolean and a role value of "removed". They are not always consistent — a member can carry role "removed" while isRemoved is still false, a known gap documented on the Cursor community forum. Previously Active was derived from isRemoved alone, so a member with role "removed" but isRemoved=false was incorrectly reported as active. Now either signal is sufficient to mark the account inactive. Add a cassette entry and test case covering the inconsistent state (role "removed", isRemoved false) to prevent regression. Signed-off-by: Bryan Frimin --- pkg/accessreview/drivers/cursor.go | 10 ++++++---- pkg/accessreview/drivers/cursor_test.go | 11 ++++++++++- pkg/accessreview/drivers/testdata/cursor.yaml | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pkg/accessreview/drivers/cursor.go b/pkg/accessreview/drivers/cursor.go index 00cf96866..49a836a5d 100644 --- a/pkg/accessreview/drivers/cursor.go +++ b/pkg/accessreview/drivers/cursor.go @@ -86,10 +86,12 @@ func (d *CursorDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error continue } - // isRemoved is Cursor's only account-status signal, so Active is + // Cursor exposes two removal signals that are not always + // consistent: the isRemoved soft-delete flag and a role of + // "removed". Either one marks the member inactive, so Active is // always populated (never nil): a removed member is reported // inactive rather than dropped, per the AccountRecord contract. - active := !m.IsRemoved + active := !m.IsRemoved && m.Role != "removed" records = append(records, AccountRecord{ Email: m.Email, @@ -98,8 +100,8 @@ func (d *CursorDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error Active: &active, IsAdmin: cursorIsAdmin(m.Role), MFAStatus: coredata.MFAStatusUnknown, - AuthMethod: coredata.AccessEntryAuthMethodUnknown, - AccountType: coredata.AccessEntryAccountTypeUser, + AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown, + AccountType: coredata.AccessReviewEntryAccountTypeUser, ExternalID: m.ID, }) } diff --git a/pkg/accessreview/drivers/cursor_test.go b/pkg/accessreview/drivers/cursor_test.go index 809b179b1..4c2fb7305 100644 --- a/pkg/accessreview/drivers/cursor_test.go +++ b/pkg/accessreview/drivers/cursor_test.go @@ -35,7 +35,7 @@ func TestCursorDriver(t *testing.T) { driver := NewCursorDriver(client) records, err := driver.ListAccounts(context.Background()) require.NoError(t, err) - require.Len(t, records, 3) + require.Len(t, records, 4) member := records[0] assert.Equal(t, "jane@example.com", member.Email) @@ -61,6 +61,15 @@ func TestCursorDriver(t *testing.T) { assert.False(t, removed.IsAdmin) require.NotNil(t, removed.Active) assert.False(t, *removed.Active) + + // Cursor's two removal signals are not always consistent: a member can + // carry role "removed" while isRemoved is still false. The role alone + // must mark the account inactive. + removedByRole := records[3] + assert.Equal(t, "Removed", removedByRole.Role) + assert.False(t, removedByRole.IsAdmin) + require.NotNil(t, removedByRole.Active) + assert.False(t, *removedByRole.Active) } func TestCursorRole(t *testing.T) { diff --git a/pkg/accessreview/drivers/testdata/cursor.yaml b/pkg/accessreview/drivers/testdata/cursor.yaml index ca3a59728..632ce9952 100644 --- a/pkg/accessreview/drivers/testdata/cursor.yaml +++ b/pkg/accessreview/drivers/testdata/cursor.yaml @@ -19,7 +19,7 @@ interactions: proto_minor: 0 content_length: -1 uncompressed: true - body: '{"teamMembers":[{"id":"10000001","name":"Jane Doe","email":"jane@example.com","role":"member","isRemoved":false},{"id":"10000002","name":"Alex Martin","email":"alex@example.com","role":"owner","isRemoved":false},{"id":"10000003","name":"John Smith","email":"john@example.com","role":"removed","isRemoved":true}]}' + body: '{"teamMembers":[{"id":"10000001","name":"Jane Doe","email":"jane@example.com","role":"member","isRemoved":false},{"id":"10000002","name":"Alex Martin","email":"alex@example.com","role":"owner","isRemoved":false},{"id":"10000003","name":"John Smith","email":"john@example.com","role":"removed","isRemoved":true},{"id":"10000004","name":"Mary Jones","email":"mary@example.com","role":"removed","isRemoved":false}]}' headers: Content-Type: - application/json