Skip to content

Guard role grant emission behind WillSyncResourceType - #31

Open
laurenleach wants to merge 2 commits into
mainfrom
lauren.leach/guard-role-grant-emission
Open

Guard role grant emission behind WillSyncResourceType#31
laurenleach wants to merge 2 commits into
mainfrom
lauren.leach/guard-role-grant-emission

Conversation

@laurenleach

Copy link
Copy Markdown

Summary

  • userResourceType.Grants() (pkg/connector/users.go) emits role grants as a sync optimization since the JumpCloud user API response already includes the admin user's role — but it did so unconditionally, even when a customer's sync filter excludes the role resource type.
  • Gate the emission on cli.ConnectorOpts.WillSyncResourceType("role"), threaded from NewLambdaConnectorConnector.syncRolesnewUserBuilder. NewLambdaConnector already received *cli.ConnectorOpts but never used it.
  • When roles aren't synced, also mark the user resource type with SkipEntitlementsAndGrants (the user builder has no entitlements/grants of its own besides the gated role grants).
  • Follows the pattern from Only emit user grants (where we emit the role grants) if role resource type is enabled  baton-linear#55.

Test plan

  • go build ./...
  • go test ./...
  • Added pkg/connector/users_test.go:
    • TestUserGrants_RoleSyncFilter — asserts no grants (and no API call) when roles are filtered out, and a correct role grant when roles are synced (via an httptest.Server mocking the JumpCloud user-get endpoint).
    • TestNewUserBuilder_ResourceTypeAnnotations — asserts SkipEntitlementsAndGrants is set on the user resource type only when roles are filtered out.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

userResourceType.Grants() emits role grants as a sync optimization
(the user API response already includes role membership), but did so
unconditionally. When a customer's sync filter excludes roles, the
connector still emitted grants referencing an unsynced resource type.

Gate the emission on cli.ConnectorOpts.WillSyncResourceType("role"),
threaded from NewLambdaConnector through the Connector struct into the
user builder. Also mark the user resource type with
SkipEntitlementsAndGrants when roles aren't synced, since the user
builder has no entitlements/grants of its own otherwise.

Follows the pattern from ConductorOne/baton-linear#55.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@laurenleach
laurenleach requested a review from a team July 24, 2026 20:10
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: Guard role grant emission behind WillSyncResourceType

Blocking Issues: 0 | Suggestions: 3 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base de099c1275eb.
Review mode: incremental since 8c02568d
View review run

Review Summary

The new commit removes the syncRoles field and the if !o.syncRoles early return from userResourceType.Grants, relying instead on the SkipEntitlementsAndGrants annotation that newUserBuilder puts on the user resource type, and drops the corresponding test case. I scanned the full PR diff for security and correctness and verified the commit's central claim against baton-sdk v0.20.2: shouldSkipGrantsshouldSkipEntitlementsAndGrants (pkg/sync/syncer.go:1263, :2093) does read the resource type annotation, so the guard is genuinely unreachable during a normal sync. No blocking issues; the suggestions cover the one path that annotation does not cover (the ListGrants gRPC entrypoint) plus comment/test staleness left behind by the removal.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/connector/users.go:55-59 — the "SDK never calls Grants()" justification holds for the syncer but not for the ListGrants RPC, which calls rb.Grants(...) with no annotation check (baton-sdk/pkg/connectorbuilder/resource_syncer.go:303); the removed guard was defense-in-depth for that path.
  • pkg/connector/users.go:35-37 — comment still says the role grant is "gated below", but the gate no longer exists.
  • pkg/connector/users_test.go:19-22 — doc comment cites ConductorOne/baton-linear#55 (wrong connector) and TestUserGrants_RoleSyncFilter no longer tests the filter it is named for.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/connector/users.go`:
- Around line 55-59: The comment above Grants() justifies removing the syncRoles guard by
  claiming the SDK never calls Grants() when SkipEntitlementsAndGrants is set. That is true
  for the syncer (baton-sdk pkg/sync/syncer.go shouldSkipGrants ->
  shouldSkipEntitlementsAndGrants), but NOT for the ListGrants gRPC entrypoint: baton-sdk
  pkg/connectorbuilder/resource_syncer.go ListGrants resolves the syncer by resource type and
  calls rb.Grants(...) unconditionally, with no annotation check. Prefer restoring the cheap
  guard: re-add the `syncRoles bool` field to userResourceType, set it in newUserBuilder, and
  early-return `nil, nil, nil` from Grants() when it is false. If you keep the guard removed,
  update the comment so it does not claim the guard would be unreachable in all callers.
- Around line 35-37: The comment inside newUserBuilder says the cross-type role grant is
  "gated below", but this commit removed the `if !o.syncRoles` gate from Grants(). Reword so it
  states the annotation is the gate (or restore the guard, which makes the wording correct again).

In `pkg/connector/users_test.go`:
- Around line 19-22: The doc comment on TestUserGrants_RoleSyncFilter references
  ConductorOne/baton-linear#55, which belongs to a different connector. Point it at the
  jumpcloud issue or drop the reference. Also, the test now contains only the
  "role type synced -> role grant emitted" subtest, so its name and the sentence "must not do
  so when the customer's sync filter excludes the role resource type" no longer describe what
  it asserts (that behavior is covered by TestNewUserBuilder_ResourceTypeAnnotations). Rename to
  something like TestUserGrants_RoleGrantEmitted and update the comment.

@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.

newUserBuilder already annotates the user resource type
SkipEntitlementsAndGrants when roles are excluded from the sync filter,
and the SDK honors that before ever calling Grants (shouldSkipGrants ->
shouldSkipEntitlementsAndGrants in pkg/sync/syncer.go), so the early
return was unreachable during a sync and duplicated the condition.

With the guard gone the syncRoles struct field was write-only, so it is
removed too; the constructor parameter stays, since it is what selects
the annotation. Dropped the subtest that existed solely to pin the early
return -- TestNewUserBuilder_ResourceTypeAnnotations already covers the
annotation, which is the real mechanism.
@linear-code

linear-code Bot commented Aug 15, 2026

Copy link
Copy Markdown

CE-1159

Comment thread pkg/connector/users.go
Comment on lines +55 to +59
// Grants emits the cross-type role grant. There is no syncRoles guard here:
// when roles aren't being synced, newUserBuilder annotates the user resource
// type SkipEntitlementsAndGrants and the SDK never calls Grants() at all
// (shouldSkipGrants -> shouldSkipEntitlementsAndGrants in the SDK's
// pkg/sync/syncer.go), so a guard would be unreachable.

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 claim holds for the syncer path — I verified shouldSkipGrantsshouldSkipEntitlementsAndGrants in baton-sdk v0.20.2 (pkg/sync/syncer.go:1263 and :2093) does gate on the resource type annotation — but it does not hold for the ListGrants gRPC entrypoint: pkg/connectorbuilder/resource_syncer.go:303 looks up the syncer by resource type and calls rb.Grants(...) unconditionally, with no annotation check. Any direct ListGrants call for a user resource will now hit GetUserByID and emit a grant referencing the unsynced role type. Keeping the cheap syncRoles guard was defense-in-depth for exactly that; consider restoring it rather than coupling correctness to SDK-internal syncer behavior.

Comment thread pkg/connector/users.go
Comment on lines +35 to +37
// The user builder has no entitlements or grants of its own -- its only
// Grants() output is the cross-type role grant gated below. When roles
// aren't being synced, skip entitlement/grant discovery for users entirely.

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: This comment is now stale — it says the role grant is "gated below", but this commit removed the if !o.syncRoles gate from Grants(). Reword to say the gating is done entirely by this annotation (or restore the guard and keep the wording).

Comment on lines +19 to +22
// TestUserGrants_RoleSyncFilter covers ConductorOne/baton-linear#55: the user
// builder emits role grants as a sync optimization, but must not do so when
// the customer's sync filter excludes the role resource type.
func TestUserGrants_RoleSyncFilter(t *testing.T) {

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: Two staleness nits after the guard removal. The doc comment references ConductorOne/baton-linear#55, which is a different connector's issue — it should point at the jumpcloud issue (or drop the reference). And TestUserGrants_RoleSyncFilter now contains only the positive case, so its name and "must not do so when the customer's sync filter excludes the role resource type" no longer describe what it asserts; the filter behavior lives in TestNewUserBuilder_ResourceTypeAnnotations. Renaming to something like TestUserGrants_RoleGrantEmitted would keep the intent clear.

@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.

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.

2 participants