From d788784fbbd41ea2e2b200cffcfbbaf050f64a1a Mon Sep 17 00:00:00 2001 From: "c1-dev-bot[bot]" <2740113+c1-dev-bot[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2026 15:50:42 +0000 Subject: [PATCH] Add group membership provisioning (Grant/Revoke) 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 --- baton_capabilities.json | 3 ++- pkg/client/client.go | 36 ++++++++++++++++++++++++++++++++++++ pkg/connector/groups.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/baton_capabilities.json b/baton_capabilities.json index dd275611..45dee49e 100644 --- a/baton_capabilities.json +++ b/baton_capabilities.json @@ -23,7 +23,8 @@ ] }, "capabilities": [ - "CAPABILITY_SYNC" + "CAPABILITY_SYNC", + "CAPABILITY_PROVISION" ], "permissions": {} }, diff --git a/pkg/client/client.go b/pkg/client/client.go index b32cacc1..41325364 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -19,6 +19,8 @@ const ( groupsEP = "v2/orgs/%s/directories/-/groups" usersRoleAssignmentEP = "v2/orgs/%s/directories/-/users/%s/role-assignments" groupsRoleAssignmentEP = "v2/orgs/%s/directories/-/groups/%s/role-assignments" + groupMembershipsEP = "v2/orgs/%s/directories/-/groups/%s/memberships" + groupMembershipEP = "v2/orgs/%s/directories/-/groups/%s/memberships/%s" userAssignRolesEP = "v1/orgs/%s/users/%s/roles/assign" userRevokeRolesEP = "v1/orgs/%s/users/%s/roles/revoke" @@ -306,6 +308,40 @@ func (c *AtlassianClient) GetGroupRoleAssignments(ctx context.Context, pageToken return roleAssignmentsResponse.Data, nextPageToken, nil } +func (c *AtlassianClient) AddUserToGroup(ctx context.Context, groupID, accountID string) error { + requestBody := struct { + AccountId string `json:"accountId"` + }{ + AccountId: accountID, + } + + requestURL, err := url.JoinPath(c.getBaseURL(), fmt.Sprintf(groupMembershipsEP, c.config.organizationID, groupID)) + if err != nil { + return err + } + + _, err = c.doRequest(ctx, http.MethodPost, requestURL, nil, requestBody) + if err != nil { + return err + } + + return nil +} + +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 +} + func (c *AtlassianClient) AssignRoleToUser(ctx context.Context, userID, workspaceID, roleID string) error { requestBody := RoleAssignmentBody{ Role: roleID, diff --git a/pkg/connector/groups.go b/pkg/connector/groups.go index 00b0a92d..d88317c4 100644 --- a/pkg/connector/groups.go +++ b/pkg/connector/groups.go @@ -198,6 +198,38 @@ func (b *groupBuilder) Grants(ctx context.Context, resource *v2.Resource, pToken return grantResources, "", nil, nil } +func (b *groupBuilder) Grant(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) { + if principal.Id.ResourceType != userResourceType.Id { + return nil, fmt.Errorf("baton-atlassian: only users can be granted group membership") + } + + groupID := entitlement.Resource.Id.Resource + userID := principal.Id.Resource + + 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 +} + +func (b *groupBuilder) Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) { + if grant.Principal.Id.ResourceType != userResourceType.Id { + return nil, fmt.Errorf("baton-atlassian: only users can be removed from group membership") + } + + groupID := grant.Entitlement.Resource.Id.Resource + userID := grant.Principal.Id.Resource + + 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 +} + func parseIntoGroupResource(group client.Group) (*v2.Resource, error) { profile := map[string]interface{}{ "name": group.Name,