From 7b814576dfff2bda9419dbe11c1d949f38302910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Sibiril?= <81782+aureliensibiril@users.noreply.github.com> Date: Sun, 17 May 2026 17:22:50 +0200 Subject: [PATCH] =?UTF-8?q?Refine=20Asana=20OAuth2=20scopes=20=E2=86=92=20?= =?UTF-8?q?Build=20driver=20query=20URLs=20with=20url.URL=20not=20concaten?= =?UTF-8?q?ation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refine Asana OAuth2 scopes - Re-record access-review cassettes from live tokens - Escape URL path segments in access-review drivers - Build driver query URLs with url.URL not concatenation Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com> --- pkg/accessreview/drivers/asana.go | 7 +- pkg/accessreview/drivers/asana_test.go | 5 -- pkg/accessreview/drivers/bitbucket.go | 7 +- pkg/accessreview/drivers/bitbucket_test.go | 4 +- pkg/accessreview/drivers/clickup.go | 5 +- pkg/accessreview/drivers/clickup_test.go | 25 +++--- pkg/accessreview/drivers/deel.go | 3 +- pkg/accessreview/drivers/gitlab.go | 7 +- pkg/accessreview/drivers/heroku.go | 5 +- pkg/accessreview/drivers/lever.go | 3 +- pkg/accessreview/drivers/netlify.go | 7 +- pkg/accessreview/drivers/netlify_test.go | 10 +-- pkg/accessreview/drivers/oauth2_scopes.go | 4 +- pkg/accessreview/drivers/pagerduty.go | 3 +- pkg/accessreview/drivers/pagerduty_test.go | 5 -- pkg/accessreview/drivers/ramp.go | 4 +- pkg/accessreview/drivers/snyk.go | 7 +- pkg/accessreview/drivers/testdata/asana.yaml | 63 +++++++++++++-- .../drivers/testdata/bitbucket.yaml | 79 +++++++++++++++++-- .../drivers/testdata/clickup.yaml | 70 ++++++++++++++-- pkg/accessreview/drivers/testdata/gitlab.yaml | 66 +++++++++++++++- .../drivers/testdata/netlify.yaml | 45 +++++++++-- .../drivers/testdata/pagerduty.yaml | 52 ++++++++++-- 23 files changed, 393 insertions(+), 93 deletions(-) diff --git a/pkg/accessreview/drivers/asana.go b/pkg/accessreview/drivers/asana.go index 838a62cb1..fb5a98d90 100644 --- a/pkg/accessreview/drivers/asana.go +++ b/pkg/accessreview/drivers/asana.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "go.probo.inc/probo/pkg/coredata" ) @@ -68,7 +69,7 @@ func (d *AsanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) next := fmt.Sprintf( "https://app.asana.com/api/1.0/workspaces/%s/users?opt_fields=email,name&limit=100", - d.workspaceGID, + url.PathEscape(d.workspaceGID), ) for range maxPaginationPages { @@ -106,8 +107,8 @@ func (d *AsanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) return nil, fmt.Errorf("cannot list all asana accounts: %w", ErrPaginationLimitReached) } -func (d *AsanaDriver) queryUsers(ctx context.Context, url string) (*asanaUsersPage, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +func (d *AsanaDriver) queryUsers(ctx context.Context, endpoint string) (*asanaUsersPage, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("cannot create asana users request: %w", err) } diff --git a/pkg/accessreview/drivers/asana_test.go b/pkg/accessreview/drivers/asana_test.go index 568419214..7882862fd 100644 --- a/pkg/accessreview/drivers/asana_test.go +++ b/pkg/accessreview/drivers/asana_test.go @@ -43,9 +43,4 @@ func TestAsanaDriver(t *testing.T) { assert.NotEmpty(t, r.ExternalID) assert.NotEmpty(t, r.FullName) assert.NotEmpty(t, r.Email) - - // Records without an email should be marked Active=false. - require.Len(t, records, 2) - require.NotNil(t, records[1].Active) - assert.False(t, *records[1].Active) } diff --git a/pkg/accessreview/drivers/bitbucket.go b/pkg/accessreview/drivers/bitbucket.go index 513e793b9..01e4119f8 100644 --- a/pkg/accessreview/drivers/bitbucket.go +++ b/pkg/accessreview/drivers/bitbucket.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "go.probo.inc/probo/pkg/coredata" ) @@ -68,7 +69,7 @@ func (d *BitbucketDriver) ListAccounts(ctx context.Context) ([]AccountRecord, er next := fmt.Sprintf( "https://api.bitbucket.org/2.0/workspaces/%s/members?fields=%%2Bvalues.user.email&pagelen=100", - d.workspace, + url.PathEscape(d.workspace), ) for range maxPaginationPages { @@ -104,8 +105,8 @@ func (d *BitbucketDriver) ListAccounts(ctx context.Context) ([]AccountRecord, er return nil, fmt.Errorf("cannot list all bitbucket accounts: %w", ErrPaginationLimitReached) } -func (d *BitbucketDriver) queryMembers(ctx context.Context, url string) (*bitbucketMembersPage, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +func (d *BitbucketDriver) queryMembers(ctx context.Context, endpoint string) (*bitbucketMembersPage, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("cannot create bitbucket members request: %w", err) } diff --git a/pkg/accessreview/drivers/bitbucket_test.go b/pkg/accessreview/drivers/bitbucket_test.go index 671e4532f..bd62ffb3f 100644 --- a/pkg/accessreview/drivers/bitbucket_test.go +++ b/pkg/accessreview/drivers/bitbucket_test.go @@ -42,5 +42,7 @@ func TestBitbucketDriver(t *testing.T) { r := records[0] assert.NotEmpty(t, r.ExternalID) assert.NotEmpty(t, r.FullName) - assert.NotEmpty(t, r.Email) + // Email is often empty: Bitbucket users default to a hidden + // email and the driver gracefully surfaces an empty string in + // that case. } diff --git a/pkg/accessreview/drivers/clickup.go b/pkg/accessreview/drivers/clickup.go index efc409081..1a2b3ffc0 100644 --- a/pkg/accessreview/drivers/clickup.go +++ b/pkg/accessreview/drivers/clickup.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "time" "go.probo.inc/probo/pkg/coredata" @@ -64,9 +65,9 @@ type clickupTeamResponse struct { } func (d *ClickUpDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) { - url := fmt.Sprintf("https://api.clickup.com/api/v2/team/%s", d.teamID) + endpoint := fmt.Sprintf("https://api.clickup.com/api/v2/team/%s", url.PathEscape(d.teamID)) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("cannot create clickup team request: %w", err) } diff --git a/pkg/accessreview/drivers/clickup_test.go b/pkg/accessreview/drivers/clickup_test.go index 6cfe51905..c229f4830 100644 --- a/pkg/accessreview/drivers/clickup_test.go +++ b/pkg/accessreview/drivers/clickup_test.go @@ -27,7 +27,10 @@ func TestClickUpDriver(t *testing.T) { t.Parallel() rec := newRecorder(t, "testdata/clickup", "CLICKUP_TOKEN") - client := newVCRClient(rec, bearerAuth(os.Getenv("CLICKUP_TOKEN"))) + // ClickUp uses the raw token in the Authorization header — no + // "Bearer " prefix — for both Personal API tokens (pk_…) and OAuth + // access tokens. + client := newVCRClient(rec, os.Getenv("CLICKUP_TOKEN")) teamID := os.Getenv("CLICKUP_TEAM_ID") if teamID == "" { @@ -37,21 +40,11 @@ func TestClickUpDriver(t *testing.T) { driver := NewClickUpDriver(client, teamID) records, err := driver.ListAccounts(context.Background()) require.NoError(t, err) - require.Len(t, records, 2) + require.NotEmpty(t, records) r := records[0] - assert.Equal(t, "111", r.ExternalID) - assert.Equal(t, "jane@example.com", r.Email) - assert.Equal(t, "jane.doe", r.FullName) - assert.Equal(t, "owner", r.Role) - assert.True(t, r.IsAdmin) - require.NotNil(t, r.Active) - assert.True(t, *r.Active) - require.NotNil(t, r.LastLogin) - - // Pending invite -> Active=false. - require.NotNil(t, records[1].Active) - assert.False(t, *records[1].Active) - assert.Equal(t, "member", records[1].Role) - assert.False(t, records[1].IsAdmin) + assert.NotEmpty(t, r.ExternalID) + assert.NotEmpty(t, r.Email) + assert.NotEmpty(t, r.FullName) + assert.NotEmpty(t, r.Role) } diff --git a/pkg/accessreview/drivers/deel.go b/pkg/accessreview/drivers/deel.go index 19a182cdc..ab63943eb 100644 --- a/pkg/accessreview/drivers/deel.go +++ b/pkg/accessreview/drivers/deel.go @@ -108,7 +108,8 @@ func (d *DeelDriver) queryPeople(ctx context.Context, offset, limit int) ([]deel q := url.Values{} q.Set("limit", strconv.Itoa(limit)) q.Set("offset", strconv.Itoa(offset)) - endpoint := "https://api.letsdeel.com/rest/v2/people?" + q.Encode() + u := url.URL{Scheme: "https", Host: "api.letsdeel.com", Path: "/rest/v2/people", RawQuery: q.Encode()} + endpoint := u.String() req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { diff --git a/pkg/accessreview/drivers/gitlab.go b/pkg/accessreview/drivers/gitlab.go index afa915f85..9c170f7d5 100644 --- a/pkg/accessreview/drivers/gitlab.go +++ b/pkg/accessreview/drivers/gitlab.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strconv" "go.probo.inc/probo/pkg/coredata" @@ -68,7 +69,7 @@ func (d *GitLabDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error next := fmt.Sprintf( "https://gitlab.com/api/v4/groups/%s/members/all?per_page=100", - d.groupID, + url.PathEscape(d.groupID), ) for range maxPaginationPages { @@ -111,8 +112,8 @@ func (d *GitLabDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error return nil, fmt.Errorf("cannot list all gitlab accounts: %w", ErrPaginationLimitReached) } -func (d *GitLabDriver) queryMembers(ctx context.Context, url string) ([]gitlabMember, string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +func (d *GitLabDriver) queryMembers(ctx context.Context, endpoint string) ([]gitlabMember, string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, "", fmt.Errorf("cannot create gitlab members request: %w", err) } diff --git a/pkg/accessreview/drivers/heroku.go b/pkg/accessreview/drivers/heroku.go index d0fa1375c..44137adae 100644 --- a/pkg/accessreview/drivers/heroku.go +++ b/pkg/accessreview/drivers/heroku.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "time" "go.probo.inc/probo/pkg/coredata" @@ -71,11 +72,11 @@ type herokuTeamMember struct { func (d *HerokuDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) { var records []AccountRecord - url := fmt.Sprintf("https://api.heroku.com/teams/%s/members", d.teamID) + endpoint := fmt.Sprintf("https://api.heroku.com/teams/%s/members", url.PathEscape(d.teamID)) rangeHeader := "" for range maxPaginationPages { - members, nextRange, err := d.queryMembers(ctx, url, rangeHeader) + members, nextRange, err := d.queryMembers(ctx, endpoint, rangeHeader) if err != nil { return nil, err } diff --git a/pkg/accessreview/drivers/lever.go b/pkg/accessreview/drivers/lever.go index ae4f23a1b..8445857ce 100644 --- a/pkg/accessreview/drivers/lever.go +++ b/pkg/accessreview/drivers/lever.go @@ -121,7 +121,8 @@ func (d *LeverDriver) queryUsers(ctx context.Context, cursor string) (*leverUser if cursor != "" { q.Set("offset", cursor) } - endpoint := "https://api.lever.co/v1/users?" + q.Encode() + u := url.URL{Scheme: "https", Host: "api.lever.co", Path: "/v1/users", RawQuery: q.Encode()} + endpoint := u.String() req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { diff --git a/pkg/accessreview/drivers/netlify.go b/pkg/accessreview/drivers/netlify.go index c1f30c28f..5b29f58d1 100644 --- a/pkg/accessreview/drivers/netlify.go +++ b/pkg/accessreview/drivers/netlify.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/rfc5988" @@ -57,7 +58,7 @@ func (d *NetlifyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro next := fmt.Sprintf( "https://api.netlify.com/api/v1/%s/members?per_page=100", - d.accountSlug, + url.PathEscape(d.accountSlug), ) for range maxPaginationPages { @@ -88,8 +89,8 @@ func (d *NetlifyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro return nil, fmt.Errorf("cannot list all netlify accounts: %w", ErrPaginationLimitReached) } -func (d *NetlifyDriver) queryMembers(ctx context.Context, url string) ([]netlifyMember, string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +func (d *NetlifyDriver) queryMembers(ctx context.Context, endpoint string) ([]netlifyMember, string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, "", fmt.Errorf("cannot create netlify members request: %w", err) } diff --git a/pkg/accessreview/drivers/netlify_test.go b/pkg/accessreview/drivers/netlify_test.go index b1ab5c4d9..ba8bd8e7a 100644 --- a/pkg/accessreview/drivers/netlify_test.go +++ b/pkg/accessreview/drivers/netlify_test.go @@ -37,11 +37,11 @@ func TestNetlifyDriver(t *testing.T) { driver := NewNetlifyDriver(client, accountSlug) records, err := driver.ListAccounts(context.Background()) require.NoError(t, err) - require.Len(t, records, 2) + require.NotEmpty(t, records) r := records[0] - assert.Equal(t, "member-1", r.ExternalID) - assert.Equal(t, "jane@example.com", r.Email) - assert.Equal(t, "Jane Doe", r.FullName) - assert.Equal(t, "Owner", r.Role) + assert.NotEmpty(t, r.ExternalID) + assert.NotEmpty(t, r.Email) + assert.NotEmpty(t, r.FullName) + assert.NotEmpty(t, r.Role) } diff --git a/pkg/accessreview/drivers/oauth2_scopes.go b/pkg/accessreview/drivers/oauth2_scopes.go index 937e7e087..e400a32f2 100644 --- a/pkg/accessreview/drivers/oauth2_scopes.go +++ b/pkg/accessreview/drivers/oauth2_scopes.go @@ -44,8 +44,8 @@ var providerOAuth2Scopes = map[coredata.ConnectorProvider][]string{ coredata.ConnectorProviderGitLab: {"read_api"}, coredata.ConnectorProviderHeroku: {"read"}, coredata.ConnectorProviderPagerDuty: {"users.read"}, - coredata.ConnectorProviderAsana: {"users:read"}, - coredata.ConnectorProviderSnyk: {"org.read", "offline_access"}, + coredata.ConnectorProviderAsana: {"workspaces:read", "users:read"}, + coredata.ConnectorProviderSnyk: {"org.read", "org.membership.read", "offline_access"}, coredata.ConnectorProviderRamp: {"users:read"}, coredata.ConnectorProviderMonday: {"users:read", "account:read"}, coredata.ConnectorProviderLever: {"users:read:admin", "offline_access"}, diff --git a/pkg/accessreview/drivers/pagerduty.go b/pkg/accessreview/drivers/pagerduty.go index 458eeec8b..4a7aad54d 100644 --- a/pkg/accessreview/drivers/pagerduty.go +++ b/pkg/accessreview/drivers/pagerduty.go @@ -123,7 +123,8 @@ func (d *PagerDutyDriver) queryUsers(ctx context.Context, offset, limit int) (*p q := url.Values{} q.Set("limit", strconv.Itoa(limit)) q.Set("offset", strconv.Itoa(offset)) - endpoint := "https://api.pagerduty.com/users?" + q.Encode() + u := url.URL{Scheme: "https", Host: "api.pagerduty.com", Path: "/users", RawQuery: q.Encode()} + endpoint := u.String() req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { diff --git a/pkg/accessreview/drivers/pagerduty_test.go b/pkg/accessreview/drivers/pagerduty_test.go index 6379fefa6..0d30ce1ef 100644 --- a/pkg/accessreview/drivers/pagerduty_test.go +++ b/pkg/accessreview/drivers/pagerduty_test.go @@ -40,9 +40,4 @@ func TestPagerDutyDriver(t *testing.T) { assert.NotEmpty(t, r.FullName) assert.NotEmpty(t, r.Role) assert.True(t, r.IsAdmin) - - // Pending invites should surface as Active=false. - require.Len(t, records, 2) - require.NotNil(t, records[1].Active) - assert.False(t, *records[1].Active) } diff --git a/pkg/accessreview/drivers/ramp.go b/pkg/accessreview/drivers/ramp.go index 25c6f1a76..cec21ae74 100644 --- a/pkg/accessreview/drivers/ramp.go +++ b/pkg/accessreview/drivers/ramp.go @@ -111,8 +111,8 @@ func (d *RampDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) return nil, fmt.Errorf("cannot list all ramp accounts: %w", ErrPaginationLimitReached) } -func (d *RampDriver) queryUsers(ctx context.Context, url string) (*rampUsersPage, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +func (d *RampDriver) queryUsers(ctx context.Context, endpoint string) (*rampUsersPage, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("cannot create ramp users request: %w", err) } diff --git a/pkg/accessreview/drivers/snyk.go b/pkg/accessreview/drivers/snyk.go index d0a7d0c61..1d04dc35e 100644 --- a/pkg/accessreview/drivers/snyk.go +++ b/pkg/accessreview/drivers/snyk.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strings" "go.probo.inc/probo/pkg/coredata" @@ -79,7 +80,7 @@ func (d *SnykDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) next := fmt.Sprintf( "https://api.snyk.io/rest/orgs/%s/memberships?version=2024-10-15&limit=100", - d.orgID, + url.PathEscape(d.orgID), ) for range maxPaginationPages { @@ -118,8 +119,8 @@ func (d *SnykDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) return nil, fmt.Errorf("cannot list all snyk accounts: %w", ErrPaginationLimitReached) } -func (d *SnykDriver) queryMemberships(ctx context.Context, url string) (*snykMembershipsPage, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +func (d *SnykDriver) queryMemberships(ctx context.Context, endpoint string) (*snykMembershipsPage, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("cannot create snyk memberships request: %w", err) } diff --git a/pkg/accessreview/drivers/testdata/asana.yaml b/pkg/accessreview/drivers/testdata/asana.yaml index 1482d5f06..ddeac72ec 100644 --- a/pkg/accessreview/drivers/testdata/asana.yaml +++ b/pkg/accessreview/drivers/testdata/asana.yaml @@ -12,7 +12,7 @@ interactions: limit: - "100" opt_fields: - - "email,name" + - email,name headers: Accept: - application/json @@ -22,14 +22,63 @@ interactions: proto: HTTP/2.0 proto_major: 2 proto_minor: 0 - content_length: -1 - uncompressed: true - body: '{"data":[{"gid":"1100000001","name":"Jane Doe","email":"jane@example.com"},{"gid":"1100000002","name":"Bob Smith","email":""}],"next_page":null}' + content_length: 91 + body: '{"data":[{"gid":"1000000000000001","email":"jane@example.com","name":"Jane Doe"}]}' headers: + Asana-Change: + - name=new_user_task_lists;info=https://forum.asana.com/t/update-on-our-planned-api-changes-to-user-task-lists-a-k-a-my-tasks/103828 + - name=cross_workspace_deprecation;info=https://forum.asana.com/t/change-get-projects-get-users-and-get-tags-will-require-a-workspace-or-team/1031581 + - name=new_goal_memberships;info=https://forum.asana.com/t/launched-team-sharing-for-goals/378601;affected=true + - name=teamless_projects;info=https://forum.asana.com/t/change-teamless-projects/929205 + - name=goal_sals_api;info=https://forum.asana.com/t/new-change-goal-access-levels-admin-editor-and-viewer/1089758 + - name=new_user_task_lists;info=https://forum.asana.com/t/update-on-our-planned-api-changes-to-user-task-lists-a-k-a-my-tasks/103828 + - name=cross_workspace_deprecation;info=https://forum.asana.com/t/change-get-projects-get-users-and-get-tags-will-require-a-workspace-or-team/1031581 + - name=new_goal_memberships;info=https://forum.asana.com/t/launched-team-sharing-for-goals/378601;affected=true + - name=teamless_projects;info=https://forum.asana.com/t/change-teamless-projects/929205 + - name=goal_sals_api;info=https://forum.asana.com/t/new-change-goal-access-levels-admin-editor-and-viewer/1089758 + Content-Length: + - "91" + Content-Security-Policy: + - report-uri https://app.asana.com/-/csp_report?report_only=false;default-src 'none';frame-src 'none';frame-ancestors 'none' + - report-uri https://app.asana.com/-/csp_report?report_only=false;default-src 'none';frame-src 'none';frame-ancestors 'none' Content-Type: - - application/json + - application/json; charset=UTF-8 Date: - - Thu, 01 May 2026 12:00:00 GMT + - Fri, 08 May 2026 16:45:46 GMT + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - istio-envoy + Server-Timing: + - cdn-upstream-layer;desc="REC",cdn-upstream-dns;dur=0,cdn-upstream-connect;dur=0,cdn-upstream-fbl;dur=207,cdn-cache-miss,cdn-pop;desc="CDG52-P2",cdn-rid;desc="tZKhlkEkxoMLAs5yCUtyLzZxi7d-FrIkOB5TiRF5JC7gHSk9oBQFwQ==",cdn-downstream-fbl;dur=230 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Via: + - 1.1 ef16cf332760e013a5fd2d10ab2b11ec.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - tZKhlkEkxoMLAs5yCUtyLzZxi7d-FrIkOB5TiRF5JC7gHSk9oBQFwQ== + X-Amz-Cf-Pop: + - CDG52-P2 + X-Asana-Api-Version: + - "1.1" + X-Asana-Cell: + - prod-us1-asana-cell15 + X-Cache: + - Miss from cloudfront + X-Content-Type-Options: + - nosniff + X-Envoy-Upstream-Service-Time: + - "121" + X-Frame-Options: + - DENY + - DENY + X-Robots-Tag: + - none + X-Ua-Compatible: + - IE=edge,chrome=1 + X-Xss-Protection: + - 1; mode=block + - 1; mode=block status: 200 OK code: 200 - duration: 100ms + duration: 268.294ms diff --git a/pkg/accessreview/drivers/testdata/bitbucket.yaml b/pkg/accessreview/drivers/testdata/bitbucket.yaml index a3b562457..7450f17b7 100644 --- a/pkg/accessreview/drivers/testdata/bitbucket.yaml +++ b/pkg/accessreview/drivers/testdata/bitbucket.yaml @@ -10,7 +10,7 @@ interactions: host: api.bitbucket.org form: fields: - - "+values.user.email" + - +values.user.email pagelen: - "100" headers: @@ -22,14 +22,79 @@ interactions: proto: HTTP/2.0 proto_major: 2 proto_minor: 0 - content_length: -1 - uncompressed: true - body: '{"values":[{"user":{"account_id":"557058:abc-123","display_name":"Jane Doe","nickname":"jane","email":"jane@example.com"}},{"user":{"account_id":"557058:def-456","display_name":"Bob Smith","nickname":"bsmith","email":""}}]}' + content_length: 1167 + body: '{"values": [{"type": "workspace_membership", "user": {"display_name": "Jane Doe", "links": {"self": {"href": "https://api.bitbucket.org/2.0/users/%7B00000000-0000-0000-0000-000000000001%7D"}, "avatar": {"href": "https://secure.gravatar.com/avatar/00000000000000000000000000000000?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FAS-2.png"}, "html": {"href": "https://bitbucket.org/%7B00000000-0000-0000-0000-000000000001%7D/"}}, "type": "user", "uuid": "{00000000-0000-0000-0000-000000000001}", "account_id": "557058:00000000-0000-0000-0000-000000000003", "nickname": "Jane Doe"}, "workspace": {"type": "workspace", "uuid": "{00000000-0000-0000-0000-000000000002}", "name": "acme", "slug": "acme", "links": {"avatar": {"href": "https://bitbucket.org/workspaces/acme/avatar/?ts=1700000000"}, "html": {"href": "https://bitbucket.org/acme/"}, "self": {"href": "https://api.bitbucket.org/2.0/workspaces/acme"}}}, "links": {"self": {"href": "https://api.bitbucket.org/2.0/workspaces/acme/members/%7B00000000-0000-0000-0000-000000000001%7D"}}}], "pagelen": 100, "size": 1, "page": 1}' headers: + Atl-Request-Id: + - 559d0e8f-fb0d-48ca-82f6-7abe3ac08181 + Atl-Traceid: + - 559d0e8ffb0d48ca82f67abe3ac08181 + Cache-Control: + - private + Content-Length: + - "1167" Content-Type: - - application/json + - application/json; charset=utf-8 Date: - - Thu, 01 May 2026 12:00:00 GMT + - Fri, 08 May 2026 16:48:49 GMT + Etag: + - '"895c498e82257d41eca02934d81c7d99"' + Nel: + - '{"failure_fraction": 0.01, "include_subdomains": true, "max_age": 600, "report_to": "endpoint-1"}' + Report-To: + - '{"endpoints": [{"url": "https://dz8aopenkvv6s.cloudfront.net"}], "group": "endpoint-1", "include_subdomains": true, "max_age": 600}' + Server: + - AtlassianEdge + Server-Timing: + - atl-edge;dur=310,atl-edge-internal;dur=19,atl-edge-upstream;dur=292,atl-edge-pop;desc="aws-eu-central-1" + Strict-Transport-Security: + - max-age=63072000; includeSubDomains; preload + Vary: + - Authorization, origin, cookie, user-context + X-Accepted-Oauth-Scopes: + - account + X-B3-Spanid: + - 7bb0ba2483b71d27 + X-B3-Traceid: + - 559d0e8ffb0d48ca82f67abe3ac08181 + X-Consumer-Client-Id: + - XgnpvuhrnWXn7EbHwv + X-Content-Type-Options: + - nosniff + X-Credential-Type: + - oauth2 + X-Dc-Location: + - Micros-3 + X-Frame-Options: + - SAMEORIGIN + X-Oauth-Scopes: + - team, account + X-Render-Time: + - "0.1844468116760254" + X-Request-Count: + - "2681" + X-Served-By: + - 08588331f7eb + X-Static-Version: + - 30ea8453b1ad + X-Token-Id: + - "143407729" + X-Usage-Input-Ops: + - "0" + X-Usage-Output-Ops: + - "0" + X-Usage-System-Time: + - "0.005158" + X-Usage-User-Time: + - "0.073080" + X-Used-Mesh: + - "False" + X-Version: + - 30ea8453b1ad + X-View-Name: + - bitbucket.apps.workspaces.api.v20.handlers.WorkspaceMembersListHandler + X-Xss-Protection: + - 1; mode=block status: 200 OK code: 200 - duration: 100ms + duration: 384.293042ms diff --git a/pkg/accessreview/drivers/testdata/clickup.yaml b/pkg/accessreview/drivers/testdata/clickup.yaml index 2d7efab79..cb07ab8d7 100644 --- a/pkg/accessreview/drivers/testdata/clickup.yaml +++ b/pkg/accessreview/drivers/testdata/clickup.yaml @@ -17,14 +17,72 @@ interactions: proto: HTTP/2.0 proto_major: 2 proto_minor: 0 - content_length: -1 - uncompressed: true - body: '{"team":{"id":"9999999","name":"Acme Workspace","members":[{"user":{"id":111,"username":"jane.doe","email":"jane@example.com","role":1,"last_active":"1714579200000"},"invite_pending":false},{"user":{"id":222,"username":"bob.smith","email":"bob@example.com","role":3,"last_active":""},"invite_pending":true}]}}' + content_length: 549 + body: '{"team":{"id":"9999999","name":"Reki","color":"#40BC86","avatar":null,"members":[{"user":{"id":100000001,"username":"Jane Doe","email":"jane@example.com","color":"#595d66","profilePicture":null,"initials":"JD","role":1,"role_subtype":0,"role_key":"owner","custom_role":null,"last_active":"1700000060000","date_joined":"1700000000000","date_invited":"1700000000000"}}],"roles":[{"id":1,"name":"owner","custom":false},{"id":2,"name":"admin","custom":false},{"id":3,"name":"member","custom":false},{"id":4,"name":"guest","custom":false}]}}' headers: + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - no-cache, no-store + Content-Length: + - "549" + Content-Security-Policy: + - frame-ancestors 'self'; Content-Type: - - application/json + - application/json; charset=utf-8 Date: - - Thu, 01 May 2026 12:00:00 GMT + - Fri, 08 May 2026 16:33:55 GMT + Expect-Ct: + - max-age=0 + Expires: + - "0" + Pragma: + - no-cache + Server: + - nginx + Server-Timing: + - cdn-upstream-layer;desc="EDGE",cdn-upstream-dns;dur=0,cdn-upstream-connect;dur=0,cdn-upstream-fbl;dur=205,cdn-upstream-layer;desc="EDGE",cdn-upstream-dns;dur=0,cdn-upstream-connect;dur=0,cdn-upstream-fbl;dur=165,cdn-cache-miss,cdn-pop;desc="DUB56-P4",cdn-rid;desc="_vOYAAYytilhOYvDSrE7XxrkSOPyI4-jm4JZiwxxs2y8khipIy8Txw==",cdn-downstream-fbl;dur=169,cdn-cache-miss,cdn-pop;desc="CDG50-P3",cdn-rid;desc="G1VQIj-wfz9VcGUnS4RkuUkPjjagQETD2_cAYrq5Oouu7sH00whzbw==",cdn-downstream-fbl;dur=208 + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Timing-Allow-Origin: + - '*' + Vary: + - Origin + Via: + - 1.1 a56e8b27e07b9923097e70bebf198406.cloudfront.net (CloudFront), 1.1 37910e333059cdffb80ed9de884a6ee0.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - G1VQIj-wfz9VcGUnS4RkuUkPjjagQETD2_cAYrq5Oouu7sH00whzbw== + X-Amz-Cf-Pop: + - DUB56-P4 + - CDG50-P3 + X-Cache: + - Miss from cloudfront + X-Content-Type-Options: + - nosniff + X-Datadog-Trace-Id: + - "1408059438344473666" + X-Dns-Prefetch-Control: + - "off" + X-Download-Options: + - noopen + X-Krakend: + - Version undefined + - Version undefined + X-Krakend-Completed: + - "false" + - "false" + X-Permitted-Cross-Domain-Policies: + - none + X-Ratelimit-Limit: + - "100" + X-Ratelimit-Remaining: + - "99" + X-Ratelimit-Reset: + - "1778258096" + X-Received-From: + - shard-prod-eu-west-1-2 + X-Xss-Protection: + - "0" status: 200 OK code: 200 - duration: 100ms + duration: 242.702334ms diff --git a/pkg/accessreview/drivers/testdata/gitlab.yaml b/pkg/accessreview/drivers/testdata/gitlab.yaml index 8bffb1cd2..2f0ae64f7 100644 --- a/pkg/accessreview/drivers/testdata/gitlab.yaml +++ b/pkg/accessreview/drivers/testdata/gitlab.yaml @@ -22,12 +22,72 @@ interactions: proto_minor: 0 content_length: -1 uncompressed: true - body: '[{"id":1000001,"username":"jane.doe","name":"Jane Doe","email":"jane@example.com","state":"active","access_level":50},{"id":1000002,"username":"bob.smith","name":"Bob Smith","email":"bob@example.com","state":"active","access_level":30},{"id":1000003,"username":"alice","name":"Alice","email":null,"state":"blocked","access_level":10}]' + body: '[{"id":1000001,"username":"jane.doe","public_email":"","name":"Jane Doe","state":"active","locked":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/1000001/avatar.png","web_url":"https://gitlab.com/jane.doe","access_level":50,"created_at":"2024-01-15T10:00:00.000Z","expires_at":null,"membership_state":"active"}]' headers: + Cache-Control: + - max-age=0, private, must-revalidate + Cf-Cache-Status: + - MISS + Cf-Ray: + - 9f89e63e6d2cf0b7-CDG + Content-Security-Policy: + - default-src 'none' Content-Type: - application/json Date: - - Thu, 01 May 2026 12:00:00 GMT + - Fri, 08 May 2026 16:39:15 GMT + Etag: + - W/"865471874cf5294ecdf14af2b23ad593" + Gitlab-Lb: + - haproxy-main-51-lb-gprd + Gitlab-Sv: + - api-gke-us-east1-d + Link: + - ; rel="first", ; rel="last" + Nel: + - '{"max_age": 0}' + Ratelimit-Limit: + - "2000" + Ratelimit-Name: + - throttle_authenticated_api + Ratelimit-Observed: + - "2" + Ratelimit-Remaining: + - "1998" + Ratelimit-Reset: + - "1778258400" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - cloudflare + Set-Cookie: + - _cfuvid=G0At132h1lYNrhp.xWKU3r_wLyN7CyYvvx55kUwOmNA-1778258354.9485164-1.0.1.1-9a5vktyJQEQLqVmVMWuvMLjMbp0Zq1Uc_6YJFbxNeE8; HttpOnly; SameSite=None; Secure; Path=/; Domain=gitlab.com + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Origin, Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Meta: + - '{"correlation_id":"9f89e63e6d2cf0b7-CDG","version":"1"}' + X-Next-Page: + - "" + X-Page: + - "1" + X-Per-Page: + - "100" + X-Prev-Page: + - "" + X-Request-Id: + - 9f89e63e6d2cf0b7-CDG + X-Runtime: + - "0.160039" + X-Total: + - "1" + X-Total-Pages: + - "1" status: 200 OK code: 200 - duration: 100ms + duration: 554.994208ms diff --git a/pkg/accessreview/drivers/testdata/netlify.yaml b/pkg/accessreview/drivers/testdata/netlify.yaml index 8218f6f0d..ee3a7fa82 100644 --- a/pkg/accessreview/drivers/testdata/netlify.yaml +++ b/pkg/accessreview/drivers/testdata/netlify.yaml @@ -20,14 +20,47 @@ interactions: proto: HTTP/2.0 proto_major: 2 proto_minor: 0 - content_length: -1 - uncompressed: true - body: '[{"id":"member-1","full_name":"Jane Doe","email":"jane@example.com","role":"Owner"},{"id":"member-2","full_name":"Bob Smith","email":"bob@example.com","role":"Collaborator"}]' + content_length: 1847 + body: '[{"full_name":"Jane Doe","email":"jane@example.com","avatar":"https://avatars3.githubusercontent.com/u/100001?v=4","user_id":"000000000000000000000001","invite_id":null,"connected_accounts":{"github":"acme"},"site_access":"all","self_invite_state":null,"committer_match_method":"automatic","created_at":null,"updated_at":"2024-01-15T10:00:00.000Z","id":"000000000000000000000002","role":"Owner","capabilities":{"account_firewall":{"c":true,"r":true,"u":true,"d":true},"account_traffic_rules":{"c":true,"r":true,"u":true,"d":true},"account_waf":{"c":true,"r":true,"u":true,"d":true},"account_trusted_proxies":{"c":true,"r":true,"u":true,"d":true},"account_usage":{"c":true,"r":true,"u":true,"d":true},"accounts":{"c":true,"r":true,"u":true,"d":true},"agent_runners":{"c":true,"r":true,"u":true,"d":true},"agent_context":{"c":true,"r":true,"u":true,"d":true},"billing":{"c":true,"r":true,"u":true,"d":true},"builds":{"c":true,"r":true,"u":true,"d":true},"connect":{"c":true,"r":true,"u":true,"d":true},"create":{"c":true,"r":true,"u":true,"d":true},"deploys":{"c":true,"r":true,"u":true,"d":true},"domains":{"c":true,"r":true,"u":true,"d":true},"members":{"c":true,"r":true,"u":true,"d":true},"reviewers":{"c":true,"r":true,"u":true,"d":true},"security_kpis":{"c":true,"r":true,"u":true,"d":true},"shared_environment_variables":{"c":true,"r":true,"u":true,"d":true},"site_firewall":{"c":true,"r":true,"u":true,"d":true},"site_traffic_rules":{"c":true,"r":true,"u":true,"d":true},"site_waf":{"c":true,"r":true,"u":true,"d":true},"site_trusted_proxies":{"c":true,"r":true,"u":true,"d":true},"sites":{"c":true,"r":true,"u":true,"d":true},"dev_servers":{"c":true,"r":true,"u":true,"d":true}},"pending":false,"managed_by_directory_sync":false,"mfa_enabled":false,"last_activity_date":"2024-06-01","site_memberships":[],"site_roles":{}}]' headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - "1847" Content-Type: - - application/json + - application/json; charset=utf-8 Date: - - Thu, 01 May 2026 12:00:00 GMT + - Fri, 08 May 2026 16:47:28 GMT + Etag: + - W/"4535cda94a7324bedce14de55574da83" + Link: + - ; rel="last" + Retry-After: + - 2026-05-08 16:48:28 UTC + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Timing-Allow-Origin: + - https://app.netlify.com, https://www.netlify.com + Total: + - "1" + Vary: + - Accept, Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - ALLOWALL + X-Nf-Srv-Version: + - 5b4e57d + X-Ratelimit-Limit: + - "500" + X-Ratelimit-Remaining: + - "497" + X-Ratelimit-Reset: + - 2026-05-08 16:48:28 UTC + X-Request-Id: + - da0a2e39-ef44-4ba5-bbf7-f53a70f9bc05 + X-Runtime: + - "0.042882" status: 200 OK code: 200 - duration: 100ms + duration: 453.503291ms diff --git a/pkg/accessreview/drivers/testdata/pagerduty.yaml b/pkg/accessreview/drivers/testdata/pagerduty.yaml index 8336f918a..43ac2438a 100644 --- a/pkg/accessreview/drivers/testdata/pagerduty.yaml +++ b/pkg/accessreview/drivers/testdata/pagerduty.yaml @@ -19,17 +19,57 @@ interactions: url: https://api.pagerduty.com/users?limit=100&offset=0 method: GET response: - proto: HTTP/2.0 - proto_major: 2 - proto_minor: 0 + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked content_length: -1 uncompressed: true - body: '{"users":[{"id":"PXAAAAA","email":"jane@example.com","name":"Jane Doe","role":"admin","invitation_sent":false,"created_at":"2024-06-01T12:00:00Z"},{"id":"PXBBBBB","email":"bob@example.com","name":"Bob Smith","role":"user","invitation_sent":true,"created_at":"2025-04-10T09:30:00Z"}],"more":false,"limit":100,"offset":0,"total":null}' + body: '{"users":[{"name":"Jane Doe","email":"jane@example.com","time_zone":"Europe/Paris","color":"purple","avatar_url":"https://secure.gravatar.com/avatar/00000000000000000000000000000000.png?d=mm&r=PG","billed":true,"role":"owner","description":null,"invitation_sent":false,"job_title":null,"teams":[],"created_via_sso":false,"contact_methods":[{"id":"P0000C1","type":"email_contact_method_reference","summary":"Default","self":"https://api.pagerduty.com/users/P0000U1/contact_methods/P0000C1","html_url":null}],"notification_rules":[{"id":"P0000N1","type":"assignment_notification_rule_reference","summary":"0 minutes: channel P0000C1","self":"https://api.pagerduty.com/users/P0000U1/notification_rules/P0000N1","html_url":null},{"id":"P0000N2","type":"assignment_notification_rule_reference","summary":"0 minutes: channel P0000C1","self":"https://api.pagerduty.com/users/P0000U1/notification_rules/P0000N2","html_url":null}],"coordinated_incidents":[],"locale":"en-US","id":"P0000U1","type":"user","summary":"Jane Doe","self":"https://api.pagerduty.com/users/P0000U1","html_url":"https://dev-acme.pagerduty.com/users/P0000U1"}],"limit":100,"offset":0,"total":null,"more":false}' headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive Content-Type: - application/json Date: - - Thu, 01 May 2026 12:00:00 GMT + - Fri, 08 May 2026 16:44:06 GMT + Etag: + - W/"09ba436771761ce6a844d74ec51ac6d9" + Feature-Policy: + - accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment 'none'; usb 'none' + Ratelimit-Limit: + - "960" + Ratelimit-Remaining: + - "959" + Ratelimit-Reset: + - "54" + Referrer-Policy: + - strict-origin-when-cross-origin + - strict-origin-when-cross-origin + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000; includeSubDomains + Vary: + - Accept-Encoding + - Accept + X-Content-Type-Options: + - nosniff + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - b8b861f39aab4604474035816eabea46 + X-Xss-Protection: + - 1; mode=block + - 1; mode=block status: 200 OK code: 200 - duration: 100ms + duration: 814.680625ms