Skip to content

Guard scope grant emission behind WillSyncResourceType - #35

Merged
laurenleach merged 1 commit into
mainfrom
lauren/guard-scope-grant-emission
Aug 14, 2026
Merged

Guard scope grant emission behind WillSyncResourceType#35
laurenleach merged 1 commit into
mainfrom
lauren/guard-scope-grant-emission

Conversation

@laurenleach

Copy link
Copy Markdown
Contributor

Gates cross-type grant emission from the user syncer on the customer's sync
filter, so grants aren't emitted for a resource type the sync excludes.
Reference: ConductorOne/baton-linear#55.

Each target is guarded individually in Grants(); when every target is excluded
the user resource type is annotated SkipEntitlementsAndGrants so the SDK skips
the pass entirely.

Flags are named skipResourceType and stored inverted so the zero value
means "sync everything" — main.go registers a zero-value Connector{} as the
capabilities factory, bypassing New.

Build, tests, and golangci-lint (0 issues) pass.

No resource-type annotation here, deliberately. teammateBuilder has
entitlements of its own (access, grantable to subuser) and emits subuser grants
alongside the scope grants, so SkipEntitlements /
SkipEntitlementsAndGrants would suppress real data. Only the scope emission is
gated, and the guard sits before the per-teammate scope lookup so the extra API
call is skipped too.

Gates cross-type grant emission from the user syncer on the customer's sync
filter, so grants aren't emitted for a resource type the sync excludes.
Reference: ConductorOne/baton-linear#55.

Each target is guarded individually in Grants(); when every target is excluded
the user resource type is annotated SkipEntitlementsAndGrants so the SDK skips
the pass entirely.

Flags are named skip<Type>ResourceType and stored inverted so the zero value
means "sync everything" — main.go registers a zero-value Connector{} as the
capabilities factory, bypassing New.

Build, tests, and golangci-lint (0 issues) pass.

**No resource-type annotation here, deliberately.** `teammateBuilder` has
entitlements of its own (`access`, grantable to subuser) and emits subuser grants
alongside the scope grants, so `SkipEntitlements` /
`SkipEntitlementsAndGrants` would suppress real data. Only the scope emission is
gated, and the guard sits before the per-teammate scope lookup so the extra API
call is skipped too.

// New returns a new instance of the connector.
func New(ctx context.Context, sgClient SendGridClient, ignoreSubusers bool) (*Connector, error) {
func New(ctx context.Context, sgClient SendGridClient, ignoreSubusers bool, skipScopeResourceType bool) (*Connector, error) {

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.

🟡 Suggestion: New now takes two adjacent, same-typed bool params (ignoreSubusers, skipScopeResourceType). Callers can silently swap them with no compile error, and this list will keep growing as more resource types get gated. Consider a small options struct (or variadic Opt funcs) so each flag is named at the call site.

if opts.PageToken.Token == "" {
// Scope grants — only on the first (and only) page to avoid duplicate API
// calls, and only when scope is in the sync filter.
if opts.PageToken.Token == "" && !u.skipScopeResourceType {

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.

🟡 Suggestion: the scope target is now gated, but the other cross-type emission in this same method — GetTeammatesSubAccess + createGrantSubuserFromTeammate (lines 231–247), whose grant principal is a subuser resource — is not. If subuser is excluded from the sync filter (or ignore_subusers is set, where subuserBuilder.List returns nothing), those grants still reference principals that were never synced. Pre-existing for the ignore_subusers path, but the same WillSyncResourceType(subuserResourceType.Id) guard would make this method internally consistent and skip the per-teammate subuser-access call too.

Comment on lines +362 to +366
inScope := &scopeCallRecorder{}
tb = newTeammateBuilder(inScope, false)
_, _, err = tb.Grants(ctx, res, rs.SyncOpAttrs{})
require.NoError(t, err)
require.True(t, inScope.called, "scope lookup must run when scope is in the sync filter")

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.

🟡 Suggestion: the positive branch discards grants and only asserts inScope.called. That passes even if the emission loop stopped producing scope grants entirely (e.g. a regression in the SendGridScopes lookup or scopeResource). Capture the grants and assert at least one has GetEntitlement().GetResource().GetId().GetResourceType() == scopeResourceType.Id"mail.send" is a real entry in SendGridScopes, so exactly one scope grant is expected here.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: Guard scope grant emission behind WillSyncResourceType

Blocking Issues: 0 | Suggestions: 3 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base c185ab8228ec.
Review mode: full
View review run

Review Summary

Scanned the full PR diff (connector.go, teammates.go, teammates_test.go) for security and correctness. The gating logic is correct: WillSyncResourceType returns true when no filter is set (verified in vendor/.../pkg/cli/cli.go:105-114), the skip* inversion keeps the zero-value Connector{} capabilities factory in cmd/baton-sendgrid/main.go behaving as "sync everything", and the guard sits before the per-teammate GetSpecificTeammate call so the extra API request is skipped too. No dependency changes, no new config fields, and no docs/connector.mdx staleness — the flag is derived from the SDK sync filter, not connector config. Three non-blocking suggestions below.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/connector/connector.go:127New now takes two adjacent same-typed bool params; swappable at the call site with no compile error.
  • pkg/connector/teammates.go:251 — the sibling cross-type emission in the same method (subuser-access grants) is not gated the same way, so it stays inconsistent with the change's stated goal.
  • pkg/connector/teammates_test.go:362-366 — the positive branch of the new test discards grants and only asserts the lookup ran, so it would not catch a regression that stops emitting scope grants.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/connector/connector.go`:
- Around line 127: `New(ctx, sgClient, ignoreSubusers, skipScopeResourceType)` has two
  adjacent bool parameters that callers can silently transpose, and the list will grow as
  more resource types are gated. Replace the trailing bools with a named options struct
  (e.g. `type Options struct { IgnoreSubusers bool; SkipScopeResourceType bool }`) or
  variadic functional options, and update `NewLambdaConnector` accordingly so each flag is
  named at the call site.

In `pkg/connector/teammates.go`:
- Around line 251: only the scope target is gated on the sync filter. The subuser-access
  emission earlier in the same `Grants` method (the `GetTeammatesSubAccess` call around
  line 231 and `createGrantSubuserFromTeammate` around line 242) produces grants whose
  principal is a `subuser` resource, and it runs even when `subuser` is excluded from the
  sync filter or `ignore_subusers` is set (in which case `subuserBuilder.List` emits
  nothing). Add a `skipSubuserResourceType` field to `teammateBuilder`, derive it in
  `NewLambdaConnector` with `!opts.WillSyncResourceType(subuserResourceType.Id)`, and skip
  both the `GetTeammatesSubAccess` call and the grant loop when it is set — mirroring the
  scope guard.

In `pkg/connector/teammates_test.go`:
- Around lines 362-366: the `inScope` half of `TestTeammateBuilder_Grants_SkipScopeResourceType`
  assigns the grants to `_` and only asserts `inScope.called`. Capture the returned grants
  and additionally assert that at least one grant has
  `GetEntitlement().GetResource().GetId().GetResourceType() == scopeResourceType.Id`.
  `"mail.send"` is a valid key in `SendGridScopes` (pkg/connector/scopes_static.go:86), so
  exactly one scope grant is expected; without this the test passes even if the emission
  loop stops producing scope grants.

@github-actions github-actions Bot 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.

No blocking issues found.

@laurenleach
laurenleach merged commit 8889a7a into main Aug 14, 2026
11 checks passed
@linear-code

linear-code Bot commented Aug 14, 2026

Copy link
Copy Markdown

CE-1168

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.

1 participant