Skip to content

Add group membership provisioning (Grant/Revoke) - #33

Open
c1-dev-bot[bot] wants to merge 1 commit into
mainfrom
feat/group-membership-provisioning
Open

Add group membership provisioning (Grant/Revoke)#33
c1-dev-bot[bot] wants to merge 1 commit into
mainfrom
feat/group-membership-provisioning

Conversation

@c1-dev-bot

@c1-dev-bot c1-dev-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown

Summary

  • Add Grant() and Revoke() methods to groupBuilder to support provisioning users into and out of Atlassian groups via C1 access requests
  • Add AddUserToGroup and RemoveUserFromGroup client methods using the Atlassian Admin v2 group membership API endpoints (/v2/orgs/{orgId}/directories/-/groups/{groupId}/memberships)
  • Register CAPABILITY_PROVISION for the group resource type in baton_capabilities.json

Previously, attempting to provision a user into a group entitlement returned resource type group does not have provisioner configured. This change enables group membership management through C1's provisioning system.

Implementation Details

The implementation follows the existing workspace role provisioning pattern in workspaces.go:

  • Grant() validates the principal is a user, extracts group and user IDs, and calls the Atlassian API to add the user to the group
  • Revoke() extracts IDs from the grant and calls the API to remove the user from the group
  • Uses v2 API endpoints with the - directory wildcard, consistent with existing group/user sync endpoints

Test Plan

  • Build passes (go build ./...)
  • go vet passes
  • Manual testing: configure connector with Atlassian org, verify Grant adds user to group
  • Manual testing: verify Revoke removes user from group
  • Verify SCIM-provisioned groups are handled gracefully (Atlassian API rejects modifications)

Fixes: CXH-1928


Automated PR Notice

This PR was automatically created by c1-dev-bot as a potential implementation.

This code requires:

  • Human review of the implementation approach
  • Manual testing to verify correctness
  • Approval from the appropriate team before merging

Add Grant() and Revoke() methods to groupBuilder to support provisioning
users into and out of Atlassian groups via access requests.

Changes:
- Add AddUserToGroup and RemoveUserFromGroup client methods using the
  Atlassian Admin v2 group membership API endpoints
- Add Grant() method to groupBuilder for adding users to groups
- Add Revoke() method to groupBuilder for removing users from groups
- Register CAPABILITY_PROVISION for the group resource type in
  baton_capabilities.json

Fixes: CXH-1928
@c1-dev-bot
c1-dev-bot Bot requested a review from a team June 29, 2026 15:51
@linear-code

linear-code Bot commented Jun 29, 2026

Copy link
Copy Markdown

CXH-1928

Comment thread pkg/connector/groups.go
Comment on lines +209 to +214
err := b.client.AddUserToGroup(ctx, groupID, userID)
if err != nil {
return nil, fmt.Errorf("baton-atlassian: failed to add user to group: %w", err)
}

return nil, nil

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: Grant is not idempotent. If the user is already a member, Atlassian's membership POST typically returns 409, which surfaces here as an error and fails the access request. Consider detecting the already-exists case and returning annotations.New(&v2.GrantAlreadyExists{}) with a nil error (see CLAUDE.md "Grant Idempotency"). (confidence: medium)

Comment thread pkg/connector/groups.go
Comment on lines +225 to +230
err := b.client.RemoveUserFromGroup(ctx, groupID, userID)
if err != nil {
return nil, fmt.Errorf("baton-atlassian: failed to remove user from group: %w", err)
}

return nil, nil

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: Revoke is not idempotent. If the membership no longer exists, the DELETE likely returns 404 and this returns an error rather than treating it as already-revoked. Consider detecting not-found and returning annotations.New(&v2.GrantAlreadyRevoked{}) with a nil error. (confidence: medium)

Comment thread pkg/client/client.go
Comment on lines +331 to +343
func (c *AtlassianClient) RemoveUserFromGroup(ctx context.Context, groupID, accountID string) error {
requestURL, err := url.JoinPath(c.getBaseURL(), fmt.Sprintf(groupMembershipEP, c.config.organizationID, groupID, accountID))
if err != nil {
return err
}

_, err = c.doRequest(ctx, http.MethodDelete, requestURL, nil, nil)
if err != nil {
return err
}

return nil
}

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 deletes the membership by treating accountID as the final path segment of .../memberships/{id}. Confirm the Atlassian Admin v2 membership API keys the DELETE on the user's accountId rather than a distinct membership ID — if it expects a membership ID, revokes will 404. The PR notes manual testing is still pending, so please verify this against a live tenant. (confidence: low)

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: Add group membership provisioning (Grant/Revoke)

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

Review Summary

Scanned the full PR diff for security and correctness. The change adds Grant/Revoke to groupBuilder, two client methods (AddUserToGroup/RemoveUserFromGroup), and registers CAPABILITY_PROVISION for groups. Entity sources are correct (WHO = principal.Id.Resource, WHAT = entitlement.Resource.Id.Resource; Revoke uses the grant principal/entitlement), and the membership accountId matches the user IDs emitted during sync. No blocking issues found; the suggestions below concern idempotency, an unverified API path key, and stale docs.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/connector/groups.go:209-214 — Grant is not idempotent: an already-existing membership (likely 409) surfaces as an error instead of a GrantAlreadyExists annotation.
  • pkg/connector/groups.go:225-230 — Revoke is not idempotent: a missing membership (likely 404) surfaces as an error instead of a GrantAlreadyRevoked annotation.
  • pkg/client/client.go:331-343 — Confirm the v2 membership DELETE is keyed on the user's accountId and not a distinct membership ID; otherwise revokes may 404. Manual testing is still pending per the PR.
  • docs/connector.mdx:15 — Capabilities table still shows the Groups "Provision" column empty; update it to reflect the new group-membership provisioning support (D1).
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In pkg/connector/groups.go:
- Around line 209-214 (Grant): The Atlassian membership POST returns an error when the
  user is already a member (typically HTTP 409). Detect that case from the client error
  and return a GrantAlreadyExists annotation with a nil error instead of propagating the
  failure, so re-grants succeed idempotently.
- Around line 225-230 (Revoke): The membership DELETE returns an error when the user is
  not a member (typically HTTP 404). Detect not-found and return a GrantAlreadyRevoked
  annotation with a nil error instead of propagating it.

In pkg/client/client.go:
- Around line 331-343 (RemoveUserFromGroup): The DELETE URL puts accountID as the final
  path segment of the memberships path. Verify against the Atlassian Admin v2 API that
  membership deletion is keyed on the user's accountId rather than a separate membership
  ID. If a membership ID is required, look it up (or use the correct endpoint) before
  deleting, otherwise revokes will fail with 404.

In docs/connector.mdx:
- Around line 15: The capabilities table Groups row has an empty Provision column. Add the
  provision checkmark icon (matching the Accounts/Workspaces rows) now that group
  membership provisioning is supported.

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

0 participants