Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions pkg/connector/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,14 +405,14 @@ func getTokenValue(pToken *pagination.Token) (int, error) {
return value, nil
}

// onBehalfOfOpts builds the optional on-behalf-of header request option.
// Returns nil (no extra options) when onBehalfOf is empty, so the same call
// path works for parent-scope and subuser-scoped requests.
// onBehalfOfOpts always disables caching: uhttp's cache key ignores the
// on-behalf-of header, so parent- and subuser-scoped calls would otherwise collide.
func onBehalfOfOpts(onBehalfOf OnBehalfOf) []uhttp.RequestOption {
if onBehalfOf == "" {
return nil
opts := []uhttp.RequestOption{uhttp.WithNoCache()}
if onBehalfOf != "" {
opts = append(opts, uhttp.WithHeader(OnBehalfOfHeaderName, string(onBehalfOf)))
}
return []uhttp.RequestOption{uhttp.WithHeader(OnBehalfOfHeaderName, string(onBehalfOf))}
return opts
}
Comment thread
JavierCarnelli-ConductorOne marked this conversation as resolved.

func (h *SendGridClient) doRequest(
Expand Down
16 changes: 11 additions & 5 deletions pkg/connector/teammates.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,21 @@ func (u *teammateBuilder) Grants(ctx context.Context, resource *v2.Resource, opt
return nil, nil, err
}

// Subuser access grants.
logger := ctxzap.Extract(ctx)

// Subuser access grants. SendGrid returns 403 here for a teammate that
// only exists inside a subuser, by design, so treat it as "no access to
// report" rather than a fatal error.
access, nextToken, err := u.client.GetTeammatesSubAccess(ctx, sgclient.Username(username), &opts.PageToken, sgclient.OnBehalfOf(onBehalfOf))
if err != nil {
return nil, nil, fmt.Errorf("baton-sendgrid: failed to get teammate subuser access for %s: %w", username, err)
if status.Code(err) == codes.PermissionDenied {
logger.Debug("baton-sendgrid: subuser_access forbidden for subuser-scoped teammate, skipping", zap.String("username", username), zap.Error(err))
access, nextToken = nil, ""
} else {
return nil, nil, fmt.Errorf("baton-sendgrid: failed to get teammate subuser access for %s: %w", username, err)
}
}

logger := ctxzap.Extract(ctx)
logger.Info("Teammate grants", zap.String("username", username), zap.Any("COUNT", access))

for _, subAccess := range access {
grants, err := createGrantSubuserFromTeammate(resource, subAccess)
if err != nil {
Expand Down
69 changes: 68 additions & 1 deletion pkg/connector/teammates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ type fakeSendGridClient struct {
subusers []models.Subuser
// subuserTeammates maps subuser username -> teammates visible to it via on-behalf-of.
subuserTeammates map[string][]*models.Teammate

subAccess []*models.TeammateSubuser
subAccessErr error
}

func (f *fakeSendGridClient) GetTeammatesSubAccess(_ context.Context, _ sgclient.Username, pToken *pagination.Token, _ sgclient.OnBehalfOf) ([]*models.TeammateSubuser, string, error) {
if f.subAccessErr != nil {
return nil, "", f.subAccessErr
}
return pageOneAtATime(f.subAccess, pToken)
}

func (f *fakeSendGridClient) GetTeammates(_ context.Context, pToken *pagination.Token, onBehalfOf sgclient.OnBehalfOf) ([]*models.Teammate, string, error) {
Expand Down Expand Up @@ -110,12 +120,17 @@ func (f *fakeSendGridClient) GetSubuserUsernameByID(_ context.Context, subuserID
// GetSpecificTeammate backs isParentScopeTeammate's dedup check: onBehalfOf
// "" means "does this username exist at parent scope", answered against
// globalTeammates, mirroring the real API's 404-for-missing behavior.
func (f *fakeSendGridClient) GetSpecificTeammate(_ context.Context, username sgclient.Username, _ sgclient.OnBehalfOf) (*models.TeammateScope, error) {
func (f *fakeSendGridClient) GetSpecificTeammate(_ context.Context, username sgclient.Username, onBehalfOf sgclient.OnBehalfOf) (*models.TeammateScope, error) {
for _, tm := range f.globalTeammates {
if tm.Username == string(username) {
return &models.TeammateScope{Teammate: *tm}, nil
}
}
for _, tm := range f.subuserTeammates[string(onBehalfOf)] {
if tm.Username == string(username) {
return &models.TeammateScope{Teammate: *tm}, nil
}
}
return nil, status.Error(codes.NotFound, "teammate does not exist")
}

Expand Down Expand Up @@ -263,3 +278,55 @@ func TestTeammateBuilder_List_TeammateRestrictedToMultipleSubusers(t *testing.T)

require.Empty(t, sub2Resources, "restricted-1 must not be re-emitted under a second subuser with a conflicting ParentResourceId")
}

func TestTeammateBuilder_Grants_SubuserAccessForbidden(t *testing.T) {
client := &fakeSendGridClient{
subusers: []models.Subuser{
{Id: 1, Username: "sub1", Email: "sub1@example.com"},
},
subuserTeammates: map[string][]*models.Teammate{
"sub1": {
{Username: "local-1", Email: "local-1@example.com"},
},
},
subAccessErr: status.Error(codes.PermissionDenied, "403 Forbidden"),
}

sub1ResourceID, err := rs.NewResourceID(subuserResourceType, 1)
require.NoError(t, err)

resource, err := teammateResource(&models.Teammate{Username: "local-1", Email: "local-1@example.com"}, sub1ResourceID, "sub1")
require.NoError(t, err)

tb := newTeammateBuilder(client)
grants, results, err := tb.Grants(context.Background(), resource, rs.SyncOpAttrs{PageToken: pagination.Token{}})

require.NoError(t, err, "a 403 from subuser_access must not abort Grants for a subuser-only teammate")
require.Empty(t, grants)
require.Equal(t, "", results.NextPageToken)
}

func TestTeammateBuilder_Grants_SubuserAccessOtherErrorPropagates(t *testing.T) {
client := &fakeSendGridClient{
subusers: []models.Subuser{
{Id: 1, Username: "sub1", Email: "sub1@example.com"},
},
subuserTeammates: map[string][]*models.Teammate{
"sub1": {
{Username: "local-1", Email: "local-1@example.com"},
},
},
subAccessErr: status.Error(codes.Unavailable, "upstream unavailable"),
}

sub1ResourceID, err := rs.NewResourceID(subuserResourceType, 1)
require.NoError(t, err)

resource, err := teammateResource(&models.Teammate{Username: "local-1", Email: "local-1@example.com"}, sub1ResourceID, "sub1")
require.NoError(t, err)

tb := newTeammateBuilder(client)
_, _, err = tb.Grants(context.Background(), resource, rs.SyncOpAttrs{PageToken: pagination.Token{}})

require.Error(t, err, "only PermissionDenied should be tolerated, other errors must still propagate")
}
Loading