Skip to content

Guard role grant emission behind WillSyncResourceType - #23

Merged
laurenleach merged 3 commits into
mainfrom
lauren.leach/guard-role-grant-emission
Aug 7, 2026
Merged

Guard role grant emission behind WillSyncResourceType#23
laurenleach merged 3 commits into
mainfrom
lauren.leach/guard-role-grant-emission

Conversation

@laurenleach

Copy link
Copy Markdown
Contributor

Summary

  • userBuilder.Grants emits a role grant on roleBuilder's behalf (a permanent no-op delegate, see role.go) as a sync optimization — the Notion user API response already includes the workspace role.
  • That emission was unconditional: if a customer's sync filter excluded the role resource type, roleBuilder never lists role resources, but userBuilder still emitted a grant referencing roleResourceType — a dangling reference to a type that was never synced.
  • Gates this behind cli.ConnectorOpts.WillSyncResourceType("role"), following ConductorOne/baton-linear#55. WillSyncResourceType returns true when the type is explicitly selected or when no filter is configured at all, so default/local runs are unaffected.
  • Approach: gating lives at the ResourceType() annotation level, not as a runtime check inside Grants(). userBuilder.ResourceType() now returns a clone of userResourceType carrying SkipEntitlements when role is being synced (user has no entitlements of its own, unchanged from before), or SkipEntitlementsAndGrants when it isn't — which tells the SDK to skip calling Entitlements/Grants for user resources entirely, so Grants() itself stays unconditional and simple.
  • Scope note: group was also named as a target to gate, but it doesn't apply here — groupBuilder.Grants computes its own membership grants independently via GetGroup, it's not a delegate of userBuilder. Only role has a cross-type emission in this connector.
  • baton_capabilities.json / config_schema.json / docs/connector.mdx are unchanged — verified by rebuilding the binary and diffing: with no sync filter (the case capabilities/config run under), WillSyncResourceType returns true for everything, so the annotation computes to the same SkipEntitlements that was already statically present.

Test plan

  • go build ./...
  • go vet ./...
  • go test ./...
  • pkg/connector/user_test.go:
    • TestUserBuilder_Grants_AlwaysEmitsRoleGrantGrants() emits the role grant unconditionally regardless of syncRoles (gating moved out of Grants).
    • TestUserBuilder_ResourceType_AnnotationssyncRoles=trueSkipEntitlements only; syncRoles=falseSkipEntitlementsAndGrants only.
    • TestUserBuilder_ResourceType_DoesNotMutatePackageVar — repeated calls with different syncRoles never leak annotations onto the shared package-level userResourceType var.
    • TestNewUserBuilder_SyncRolesWiring — constructor wiring (nil opts, empty filter, filter including/excluding "role") unchanged.

🤖 Generated with Claude Code

@laurenleach
laurenleach requested a review from a team July 24, 2026 20:14
@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: 0 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 03f1828632de.
Review mode: incremental since d653009
View review run: https://github.com/ConductorOne/baton-notion/actions/runs/31209132455

Review Summary

The new commit only drops the unreachable comma-ok fallback on proto.Clone(userResourceType).(*v2.ResourceType) in pkg/connector/user.go:63. Since userResourceType is a non-nil package-level *v2.ResourceType, proto.Clone always returns that same concrete type, so the bare type assertion cannot panic and this is a safe simplification. The full PR diff was re-scanned for security and correctness, including dependency manifests (go.mod and go.sum are unchanged), and no new issues were found. The two prior findings — the WillSyncResourceType gate not seeing C1 task-driven SyncResourceTypeIds, and the inert resource-type-level SkipGrants on roleResourceType — are unchanged in the current tree and remain open rather than being re-reported here.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

None.

@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 and others added 2 commits August 7, 2026 11:16
userBuilder.Grants emits role grants on roleBuilder's behalf (roleBuilder.Grants
is a no-op delegate) as a sync optimization, since the user API response
already includes the workspace role. This emission was unconditional, so a
customer whose sync filter excluded "role" would still get grants referencing
a resource type that was never synced.

Gate the emission on cli.ConnectorOpts.WillSyncResourceType("role"), following
the pattern in ConductorOne/baton-linear#55. No group-grant cross-emission
exists in this connector -- groupBuilder.Grants computes its own membership
grants independently via GetGroup, so only role needed gating.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
…heck

Replace the in-Grants() early-return guard (behind syncRoles) with the
approach from ConductorOne/baton-linear#55: move the gate into
userBuilder.ResourceType(), which now returns a clone of userResourceType
carrying SkipEntitlements when "role" is being synced (matching the
prior static behavior) or SkipEntitlementsAndGrants when it is not. The
SDK decides whether to call Entitlements()/Grants() for a resource type
from these resource-type-level annotations (verified against
baton-sdk@v0.20.2's pkg/sync/syncer.go shouldSkipEntitlements /
shouldSkipEntitlementsAndGrants), so gating there means Grants() is
simply never invoked when "role" is excluded from the sync -- no
runtime check needed inside Grants() itself, which goes back to its
original unconditional body.

userResourceType's static Annotations field is removed from
resource_types.go since annotations are now computed dynamically per
syncRoles. ResourceType() always returns a proto.Clone of the package
var so repeated/differing calls can't leak annotations onto the shared
pointer; this is covered by a new test alongside tests for the
annotation behavior and Grants()'s now-unconditional emission.

Metadata regeneration: ran `capabilities`/`config` against the built
binary and diffed against baton_capabilities.json/config_schema.json --
byte-identical after JSON canonicalization, so neither file nor
docs/connector.mdx needed updates. This matches expectations: with no
sync filter (the case `capabilities`/`config` run under),
WillSyncResourceType defaults true, so syncRoles is true and the
annotation is SkipEntitlements -- identical to the previous static
value.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
@laurenleach
laurenleach force-pushed the lauren.leach/guard-role-grant-emission branch from dd4025f to d653009 Compare August 7, 2026 18:21
Comment thread pkg/connector/user.go
Comment on lines +316 to +319
syncRoles := true
if connectorOpts != nil {
syncRoles = connectorOpts.WillSyncResourceType(roleResourceType.Id)
}

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: cli.ConnectorOpts.SyncResourceTypeIDs is only ever populated from the local --sync-resource-types flag/config (see pkg/cli/commands.go:464,670 in the SDK). In the C1 service path, the authoritative filter is the task's GetSyncFull().GetSyncResourceTypeIds(), which takes precedence over local config in pkg/tasks/c1api/full_sync.go:243-251 and is never surfaced through ConnectorOpts. So when C1/the UI narrows the sync to exclude role, WillSyncResourceType("role") still returns true here and the dangling role grant is emitted anyway — the gate only covers locally-configured filters. Worth noting in the PR description (or raising with the SDK) so the coverage limit is explicit rather than implied.

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

proto.Clone of a *v2.ResourceType always yields a *v2.ResourceType, so the
type assertion cannot fail. Use the plain assertion, matching the other
connectors.

@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 b393863 into main Aug 7, 2026
11 checks passed
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