diff --git a/pkg/connector/client/helpers.go b/pkg/connector/client/helpers.go index 2f305e88..debb5891 100644 --- a/pkg/connector/client/helpers.go +++ b/pkg/connector/client/helpers.go @@ -16,6 +16,7 @@ import ( "github.com/slack-go/slack" "go.uber.org/zap" "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -88,6 +89,11 @@ func WrapError(err error, contextMsg string, annos *annotations.Annotations) err return uhttp.WrapErrors(grpcCode, contextMsg, err) } + // Preserve the gRPC code if the error already has one (e.g. from uhttp/businessPlusClient). + // Only fall back to Unknown if no code is set. + if code := status.Code(err); code != codes.OK { + return uhttp.WrapErrors(code, contextMsg, err) + } return uhttp.WrapErrors(codes.Unknown, contextMsg, err) } diff --git a/pkg/connector/user_group.go b/pkg/connector/user_group.go index 04066c31..5c84d6cc 100644 --- a/pkg/connector/user_group.go +++ b/pkg/connector/user_group.go @@ -3,6 +3,7 @@ package connector import ( "context" "fmt" + "strconv" v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" "github.com/conductorone/baton-sdk/pkg/annotations" @@ -14,6 +15,8 @@ import ( "github.com/slack-go/slack" ) +const userGroupGrantPageSize = 20 + type userGroupResourceType struct { resourceType *v2.ResourceType client *slack.Client @@ -129,7 +132,7 @@ func (o *userGroupResourceType) Entitlements( func (o *userGroupResourceType) Grants( ctx context.Context, res *v2.Resource, - _ resource.SyncOpAttrs, + attrs resource.SyncOpAttrs, ) ( []*v2.Grant, *resource.SyncOpResults, @@ -141,8 +144,26 @@ func (o *userGroupResourceType) Grants( return nil, &resource.SyncOpResults{Annotations: outputAnnotations}, client.WrapError(err, fmt.Sprintf("fetching user group members for group %s", res.Id.Resource), &outputAnnotations) } + // Parse the page offset from the token. On the first call this is 0. + offset := 0 + if attrs.PageToken.Token != "" { + offset, err = strconv.Atoi(attrs.PageToken.Token) + if err != nil { + return nil, nil, fmt.Errorf("baton-slack: parsing page token: %w", err) + } + } + + // Group membership may have changed between pages. + if offset >= len(groupMembers) { + return nil, &resource.SyncOpResults{Annotations: outputAnnotations}, nil + } + + // Slice the member list for this page. + end := min(offset+userGroupGrantPageSize, len(groupMembers)) + page := groupMembers[offset:end] + var rv []*v2.Grant - for _, member := range groupMembers { + for _, member := range page { user, err := o.client.GetUserInfoContext(ctx, member) if err != nil { return nil, &resource.SyncOpResults{Annotations: outputAnnotations}, client.WrapError(err, fmt.Sprintf("fetching user info for member %s", member), &outputAnnotations) @@ -156,5 +177,11 @@ func (o *userGroupResourceType) Grants( rv = append(rv, grant) } - return rv, &resource.SyncOpResults{Annotations: outputAnnotations}, nil + // If there are more members, return a token so the SDK calls us again. + var nextPageToken string + if end < len(groupMembers) { + nextPageToken = strconv.Itoa(end) + } + + return rv, &resource.SyncOpResults{NextPageToken: nextPageToken, Annotations: outputAnnotations}, nil }