Add wsl linter and fix

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-19 14:51:08 +04:00
parent eedfdcecc8
commit 9156d6a16a
882 changed files with 6068 additions and 574 deletions

View File

@@ -102,13 +102,16 @@ func (s AccessEntryService) RecordDecision(
entry.DecisionNote = req.DecisionNote
entry.DecidedBy = req.DecidedByID
entry.DecidedAt = &now
entry.UpdatedAt = now
if entry.Flags == nil {
entry.Flags = []coredata.AccessEntryFlag{}
}
if entry.FlagReasons == nil {
entry.FlagReasons = []string{}
}
if req.Decision == coredata.AccessEntryDecisionRevoke || req.Decision == coredata.AccessEntryDecisionEscalate {
if len(entry.Flags) == 0 {
entry.Flags = []coredata.AccessEntryFlag{coredata.AccessEntryFlagExcessive}
@@ -156,6 +159,7 @@ func (s AccessEntryService) RecordDecisions(
if d.Decision == coredata.AccessEntryDecisionPending {
return nil, fmt.Errorf("cannot bulk decide access entries: invalid decision %q", d.Decision)
}
if d.Decision != coredata.AccessEntryDecisionApproved {
if d.DecisionNote == nil || strings.TrimSpace(*d.DecisionNote) == "" {
return nil, fmt.Errorf(
@@ -189,9 +193,11 @@ func (s AccessEntryService) RecordDecisions(
if err := campaign.LoadByID(ctx, conn, s.scope, entry.AccessReviewCampaignID); err != nil {
return fmt.Errorf("cannot load campaign: %w", err)
}
if campaign.Status != coredata.AccessReviewCampaignStatusPendingActions {
return fmt.Errorf("cannot decide access entry: campaign status is %s, expected PENDING_ACTIONS", campaign.Status)
}
verifiedCampaigns[entry.AccessReviewCampaignID] = true
}
@@ -200,13 +206,16 @@ func (s AccessEntryService) RecordDecisions(
entry.DecisionNote = d.DecisionNote
entry.DecidedBy = d.DecidedByID
entry.DecidedAt = &now
entry.UpdatedAt = now
if entry.Flags == nil {
entry.Flags = []coredata.AccessEntryFlag{}
}
if entry.FlagReasons == nil {
entry.FlagReasons = []string{}
}
if d.Decision == coredata.AccessEntryDecisionRevoke || d.Decision == coredata.AccessEntryDecisionEscalate {
if len(entry.Flags) == 0 {
entry.Flags = []coredata.AccessEntryFlag{coredata.AccessEntryFlagExcessive}
@@ -245,6 +254,7 @@ func (s AccessEntryService) RecordDecisions(
if err != nil {
return nil, fmt.Errorf("cannot reload access entry %s: %w", id, err)
}
entries[i] = entry
}
@@ -274,14 +284,17 @@ func (s AccessEntryService) FlagEntry(
}
now := time.Now()
entry.Flags = req.Flags
if entry.Flags == nil {
entry.Flags = []coredata.AccessEntryFlag{}
}
entry.FlagReasons = req.FlagReasons
if entry.FlagReasons == nil {
entry.FlagReasons = []string{}
}
entry.UpdatedAt = now
return entry.UpdateFlags(ctx, conn, s.scope)
@@ -348,10 +361,12 @@ func (s AccessEntryService) CountForCampaignID(
ctx,
func(ctx context.Context, conn pg.Querier) (err error) {
entries := coredata.AccessEntries{}
count, err = entries.CountByCampaignID(ctx, conn, s.scope, campaignID, filter)
if err != nil {
return fmt.Errorf("cannot count access entries by campaign: %w", err)
}
return nil
},
)
@@ -374,10 +389,12 @@ func (s AccessEntryService) CountForCampaignIDAndSourceID(
ctx,
func(ctx context.Context, conn pg.Querier) (err error) {
entries := coredata.AccessEntries{}
count, err = entries.CountByCampaignIDAndSourceID(ctx, conn, s.scope, campaignID, sourceID, filter)
if err != nil {
return fmt.Errorf("cannot count access entries by campaign and source: %w", err)
}
return nil
},
)
@@ -398,10 +415,12 @@ func (s AccessEntryService) CountPendingForCampaignID(
ctx,
func(ctx context.Context, conn pg.Querier) (err error) {
entries := coredata.AccessEntries{}
count, err = entries.CountPendingByCampaignID(ctx, conn, s.scope, campaignID)
if err != nil {
return fmt.Errorf("cannot count pending access entries: %w", err)
}
return nil
},
)

View File

@@ -188,6 +188,7 @@ func (s AccessSourceService) Update(
return fmt.Errorf("cannot load connector: %w", err)
}
}
source.ConnectorID = *req.ConnectorID
}
@@ -256,6 +257,7 @@ func (s AccessSourceService) CountForOrganizationID(
func(ctx context.Context, conn pg.Querier) (err error) {
sources := coredata.AccessSources{}
count, err = sources.CountByOrganizationID(ctx, conn, s.scope, organizationID)
return err
},
)
@@ -300,6 +302,7 @@ func (s AccessSourceService) ConnectorHTTPClient(
if err := dbConnector.LoadByID(ctx, conn, s.scope, connectorID, s.encryptionKey); err != nil {
return fmt.Errorf("cannot load connector: %w", err)
}
return nil
},
)
@@ -308,16 +311,19 @@ func (s AccessSourceService) ConnectorHTTPClient(
}
var tokenBefore string
oauth2Conn, isOAuth2 := dbConnector.Connection.(*connector.OAuth2Connection)
if isOAuth2 {
tokenBefore = oauth2Conn.AccessToken
}
var httpClient *http.Client
if isOAuth2 && s.connectorRegistry != nil {
refreshCfg := s.connectorRegistry.GetOAuth2RefreshConfig(string(dbConnector.Provider))
if refreshCfg != nil {
var err error
httpClient, err = oauth2Conn.RefreshableClient(ctx, *refreshCfg)
if err != nil {
return nil, nil, fmt.Errorf("cannot create refreshable HTTP client: %w", err)
@@ -327,6 +333,7 @@ func (s AccessSourceService) ConnectorHTTPClient(
if httpClient == nil {
var err error
httpClient, err = dbConnector.Connection.Client(ctx)
if err != nil {
return nil, nil, fmt.Errorf("cannot create HTTP client: %w", err)
@@ -336,6 +343,7 @@ func (s AccessSourceService) ConnectorHTTPClient(
// Persist refreshed token if it changed.
if isOAuth2 && oauth2Conn.AccessToken != tokenBefore {
dbConnector.UpdatedAt = time.Now()
if err := s.pg.WithTx(
ctx,
func(ctx context.Context, tx pg.Tx) error {

View File

@@ -105,6 +105,7 @@ func (s *CampaignService) Get(
if err := campaign.LoadByID(ctx, conn, s.scope, campaignID); err != nil {
return fmt.Errorf("cannot load campaign: %w", err)
}
return nil
},
)
@@ -192,6 +193,7 @@ func (s *CampaignService) Delete(
if err := campaign.Delete(ctx, conn, s.scope); err != nil {
return fmt.Errorf("cannot delete campaign: %w", err)
}
return nil
},
)
@@ -359,6 +361,7 @@ func (s *CampaignService) Close(
}
entries := coredata.AccessEntries{}
pendingCount, err := entries.CountPendingByCampaignID(ctx, conn, s.scope, campaignID)
if err != nil {
return fmt.Errorf("cannot count pending entries: %w", err)
@@ -392,6 +395,7 @@ func lockCampaignForUpdate(ctx context.Context, tx pg.Tx, scope coredata.Scoper,
if err := c.LockForUpdate(ctx, tx, scope); err != nil {
return fmt.Errorf("cannot lock campaign for update: %w", err)
}
return nil
}
@@ -402,6 +406,7 @@ func (s *CampaignService) enqueueSourceFetches(
sources coredata.AccessSources,
) error {
now := time.Now()
for _, source := range sources {
fetch := &coredata.AccessReviewCampaignSourceFetch{
AccessReviewCampaignID: campaignID,
@@ -469,6 +474,7 @@ func (s *CampaignService) ListForOrganizationID(
if err := campaigns.LoadByOrganizationID(ctx, conn, s.scope, organizationID, cursor); err != nil {
return fmt.Errorf("cannot load campaigns by organization: %w", err)
}
return nil
},
)
@@ -491,6 +497,7 @@ func (s *CampaignService) ListSourceFetches(
if err := fetches.LoadByCampaignID(ctx, conn, s.scope, campaignID); err != nil {
return fmt.Errorf("cannot load source fetches by campaign: %w", err)
}
return nil
},
)
@@ -511,6 +518,7 @@ func (s *CampaignService) CountForOrganizationID(
ctx,
func(ctx context.Context, conn pg.Querier) (err error) {
campaigns := coredata.AccessReviewCampaigns{}
count, err = campaigns.CountByOrganizationID(ctx, conn, s.scope, organizationID)
if err != nil {
return fmt.Errorf("cannot count campaigns by organization: %w", err)

View File

@@ -98,6 +98,7 @@ func (d *AsanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error)
if page.NextPage == nil || page.NextPage.URI == "" {
return records, nil
}
next = page.NextPage.URI
}
@@ -109,12 +110,14 @@ func (d *AsanaDriver) queryUsers(ctx context.Context, endpoint string) (*asanaUs
if err != nil {
return nil, fmt.Errorf("cannot create asana users request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot execute asana users request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {

View File

@@ -110,12 +110,14 @@ func (d *BitbucketDriver) queryMembers(ctx context.Context, endpoint string) (*b
if err != nil {
return nil, fmt.Errorf("cannot create bitbucket members request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot execute bitbucket members request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {

View File

@@ -84,6 +84,7 @@ func (d *BrexDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error)
if resp.NextCursor == "" {
return records, nil
}
nextCursor := resp.NextCursor
cursor = &nextCursor
}
@@ -110,6 +111,7 @@ func (d *BrexDriver) queryUsers(ctx context.Context, cursor *string) (*brexUsers
if err != nil {
return nil, fmt.Errorf("cannot execute brex users request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -82,6 +82,7 @@ func TestCassettesUseSyntheticEmails(t *testing.T) {
if seen[email] {
continue
}
seen[email] = true
domain := email[strings.IndexByte(email, '@')+1:]
@@ -90,6 +91,7 @@ func TestCassettesUseSyntheticEmails(t *testing.T) {
}
ok := false
for _, suffix := range allowedDomainSuffixes {
if strings.HasSuffix("."+domain, suffix) || domain == strings.TrimPrefix(suffix, ".") {
ok = true

View File

@@ -77,12 +77,14 @@ func (d *ClickUpDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
if err != nil {
return nil, fmt.Errorf("cannot create clickup team request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot execute clickup team request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -159,5 +161,6 @@ func parseClickUpTime(raw string) (time.Time, error) {
if err != nil {
return time.Time{}, fmt.Errorf("cannot parse clickup time %q: %w", raw, err)
}
return time.UnixMilli(ms).UTC(), nil
}

View File

@@ -131,6 +131,7 @@ func (d *CloudflareDriver) queryAccounts(ctx context.Context, page int) (*cloudf
if err != nil {
return nil, fmt.Errorf("cannot execute cloudflare accounts request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -168,6 +169,7 @@ func (d *CloudflareDriver) queryAllMembers(ctx context.Context, accountID string
}
isAdmin := false
for _, r := range m.Roles {
if r.Name == "Super Administrator - All Privileges" || r.Name == "Administrator" {
isAdmin = true
@@ -224,6 +226,7 @@ func (d *CloudflareDriver) queryMembers(ctx context.Context, accountID string, p
if err != nil {
return nil, fmt.Errorf("cannot execute cloudflare members request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -51,6 +51,7 @@ func (d *CSVDriver) ListAccounts(_ context.Context) ([]AccountRecord, error) {
for i, col := range header {
colIndex[strings.TrimSpace(strings.ToLower(col))] = i
}
if _, ok := colIndex["email"]; !ok {
return nil, fmt.Errorf("cannot parse CSV: missing required column email")
}
@@ -62,6 +63,7 @@ func (d *CSVDriver) ListAccounts(_ context.Context) ([]AccountRecord, error) {
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("cannot read CSV row: %w", err)
}
@@ -75,24 +77,31 @@ func (d *CSVDriver) ListAccounts(_ context.Context) ([]AccountRecord, error) {
if idx, ok := colIndex["email"]; ok && idx < len(row) {
record.Email = strings.TrimSpace(row[idx])
}
if idx, ok := colIndex["full_name"]; ok && idx < len(row) {
record.FullName = strings.TrimSpace(row[idx])
}
if idx, ok := colIndex["role"]; ok && idx < len(row) {
record.Role = strings.TrimSpace(row[idx])
}
if idx, ok := colIndex["job_title"]; ok && idx < len(row) {
record.JobTitle = strings.TrimSpace(row[idx])
}
if idx, ok := colIndex["is_admin"]; ok && idx < len(row) {
record.IsAdmin = strings.TrimSpace(strings.ToLower(row[idx])) == "true"
}
if idx, ok := colIndex["active"]; ok && idx < len(row) {
record.Active = new(strings.TrimSpace(strings.ToLower(row[idx])) == "true")
}
if idx, ok := colIndex["external_id"]; ok && idx < len(row) {
record.ExternalID = strings.TrimSpace(row[idx])
}
if idx, ok := colIndex["account_type"]; ok && idx < len(row) {
if strings.TrimSpace(strings.ToUpper(row[idx])) == "SERVICE_ACCOUNT" {
record.AccountType = coredata.AccessEntryAccountTypeServiceAccount

View File

@@ -24,6 +24,7 @@ func TestCSVDriverRequiresEmailHeader(t *testing.T) {
t.Parallel()
driver := NewCSVDriver(strings.NewReader("full_name,role\nJane Doe,Admin\n"))
_, err := driver.ListAccounts(context.Background())
if err == nil {
t.Fatalf("expected error when email header is missing")
@@ -36,16 +37,20 @@ func TestCSVDriverParsesRequiredAndOptionalColumns(t *testing.T) {
driver := NewCSVDriver(strings.NewReader(
"email,full_name,role,external_id\njane@example.com,Jane Doe,Admin,42\n",
))
records, err := driver.ListAccounts(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(records) != 1 {
t.Fatalf("expected 1 record, got %d", len(records))
}
if records[0].Email != "jane@example.com" {
t.Fatalf("unexpected email: %s", records[0].Email)
}
if records[0].ExternalID != "42" {
t.Fatalf("unexpected external id: %s", records[0].ExternalID)
}

View File

@@ -79,6 +79,7 @@ func (d *DocuSignDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err
}
var records []AccountRecord
startPosition := 0
for range maxPaginationPages {
@@ -143,12 +144,14 @@ func (d *DocuSignDriver) discoverAccount(ctx context.Context) (accountID string,
if err != nil {
return "", "", fmt.Errorf("cannot create docusign userinfo request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return "", "", fmt.Errorf("cannot execute docusign userinfo request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -183,12 +186,14 @@ func (d *DocuSignDriver) queryUsers(ctx context.Context, baseURI string, account
if err != nil {
return nil, fmt.Errorf("cannot create docusign users request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot execute docusign users request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -85,6 +85,7 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
d.logger.WarnCtx(ctx, "cannot fetch github membership, skipping member",
log.Error(err),
)
continue
}
@@ -93,6 +94,7 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
d.logger.WarnCtx(ctx, "cannot fetch github user profile, skipping member",
log.Error(err),
)
continue
}
@@ -107,6 +109,7 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
}
mfaStatus := coredata.MFAStatusUnknown
if no2FASet != nil {
if no2FASet[m.Login] {
mfaStatus = coredata.MFAStatusDisabled
@@ -158,6 +161,7 @@ func (d *GitHubDriver) fetchAllMembers(ctx context.Context) ([]githubMember, err
if nextURL == "" {
return members, nil
}
url = nextURL
}
@@ -176,6 +180,7 @@ func (d *GitHubDriver) fetchMembersPage(ctx context.Context, url string) ([]gith
if err != nil {
return nil, "", fmt.Errorf("cannot execute github members request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -215,6 +220,7 @@ func (d *GitHubDriver) fetchAll2FADisabledLogins(ctx context.Context) (map[strin
if nextURL == "" {
return set, nil
}
url = nextURL
}
@@ -239,6 +245,7 @@ func (d *GitHubDriver) fetchMembership(ctx context.Context, login string) (*gith
if err != nil {
return nil, fmt.Errorf("cannot execute github membership request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -269,6 +276,7 @@ func (d *GitHubDriver) fetchUserProfile(ctx context.Context, login string) (*git
if err != nil {
return nil, fmt.Errorf("cannot execute github user profile request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -117,12 +117,14 @@ func (d *GitLabDriver) queryMembers(ctx context.Context, endpoint string) ([]git
if err != nil {
return nil, "", fmt.Errorf("cannot create gitlab members request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, "", fmt.Errorf("cannot execute gitlab members request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {

View File

@@ -59,6 +59,7 @@ func (rt *retryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error
}
var lastResp *http.Response
for attempt := range rt.maxRetries {
resp, err := transport.RoundTrip(req)
if err != nil {
@@ -94,6 +95,7 @@ func (d *GoogleWorkspaceDriver) ListAccounts(ctx context.Context) ([]AccountReco
}
var records []AccountRecord
pageToken := ""
for range maxPaginationPages {

View File

@@ -124,6 +124,7 @@ func (d *HerokuDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
if nextRange == "" {
return records, nil
}
rangeHeader = nextRange
}
@@ -135,7 +136,9 @@ func (d *HerokuDriver) queryMembers(ctx context.Context, endpoint, rangeHeader s
if err != nil {
return nil, "", fmt.Errorf("cannot create heroku members request: %w", err)
}
req.Header.Set("Accept", "application/vnd.heroku+json; version=3")
if rangeHeader != "" {
req.Header.Set("Range", rangeHeader)
}
@@ -144,6 +147,7 @@ func (d *HerokuDriver) queryMembers(ctx context.Context, endpoint, rangeHeader s
if err != nil {
return nil, "", fmt.Errorf("cannot execute heroku members request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
// Heroku returns 206 Partial Content for ranged responses with more

View File

@@ -83,6 +83,7 @@ func (d *HubSpotDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
for _, u := range resp.Results {
role := "User"
if roleMap != nil && u.RoleID != "" {
if name, ok := roleMap[u.RoleID]; ok {
role = name
@@ -114,6 +115,7 @@ func (d *HubSpotDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro
if resp.Paging == nil || resp.Paging.Next == nil || resp.Paging.Next.After == "" {
return records, nil
}
after = resp.Paging.Next.After
}
@@ -128,9 +130,11 @@ func (d *HubSpotDriver) fetchUsers(ctx context.Context, after string) (*hubspotU
q := req.URL.Query()
q.Set("limit", "100")
if after != "" {
q.Set("after", after)
}
req.URL.RawQuery = q.Encode()
req.Header.Set("Accept", "application/json")
@@ -139,6 +143,7 @@ func (d *HubSpotDriver) fetchUsers(ctx context.Context, after string) (*hubspotU
if err != nil {
return nil, fmt.Errorf("cannot execute hubspot users request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -167,6 +172,7 @@ func (d *HubSpotDriver) fetchRoles(ctx context.Context) (map[string]string, erro
if err != nil {
return nil, fmt.Errorf("cannot execute hubspot roles request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -61,6 +61,7 @@ func (d *IntercomDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err
}
var records []AccountRecord
for _, a := range resp.Admins {
record := AccountRecord{
Email: a.Email,
@@ -95,6 +96,7 @@ func (d *IntercomDriver) fetchAdmins(ctx context.Context) (*intercomAdminsRespon
if err != nil {
return nil, fmt.Errorf("cannot execute intercom admins request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -119,5 +121,6 @@ func intercomRole(hasInboxSeat bool) string {
if hasInboxSeat {
return "Agent"
}
return "Viewer"
}

View File

@@ -125,6 +125,7 @@ func (d *LinearDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
if !resp.Data.Users.PageInfo.HasNextPage || resp.Data.Users.PageInfo.EndCursor == "" {
return records, nil
}
nextCursor := resp.Data.Users.PageInfo.EndCursor
after = &nextCursor
}
@@ -170,6 +171,7 @@ query AccessReviewLinearUsers($after: String) {
if err != nil {
return nil, fmt.Errorf("cannot create linear users request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
@@ -177,6 +179,7 @@ query AccessReviewLinearUsers($after: String) {
if err != nil {
return nil, fmt.Errorf("cannot execute linear users request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -189,6 +192,7 @@ query AccessReviewLinearUsers($after: String) {
if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
return nil, fmt.Errorf("cannot decode linear users response: %w", err)
}
if len(resp.Errors) > 0 {
return nil, fmt.Errorf("linear graphql error: %s", resp.Errors[0].Message)
}

View File

@@ -120,15 +120,18 @@ func (d *Microsoft365Driver) ListAccounts(ctx context.Context) ([]AccountRecord,
}
rolesByUser := make(map[string][]string)
for _, role := range roles {
members, err := d.listRoleMembers(ctx, role.ID)
if err != nil {
return nil, fmt.Errorf("cannot list members of role %q: %w", role.DisplayName, err)
}
for _, m := range members {
if m.ODataType != "" && m.ODataType != "#microsoft.graph.user" {
continue
}
rolesByUser[m.ID] = append(rolesByUser[m.ID], role.DisplayName)
}
}
@@ -147,6 +150,7 @@ func (d *Microsoft365Driver) ListAccounts(ctx context.Context) ([]AccountRecord,
userRoles := rolesByUser[u.ID]
isAdmin := false
for _, r := range userRoles {
if adminRoleDisplayNames[r] {
isAdmin = true
@@ -214,6 +218,7 @@ func pickHighestRole(roles []string) string {
if len(roles) > 0 {
return roles[0]
}
return ""
}
@@ -224,15 +229,18 @@ func (d *Microsoft365Driver) listUsers(ctx context.Context) ([]microsoft365User,
}
var all []microsoft365User
for range microsoft365MaxPaginationOK {
var page microsoft365UsersPage
if err := d.fetchJSON(ctx, pageURL, &page); err != nil {
return nil, err
}
all = append(all, page.Value...)
if page.NextLink == "" {
return all, nil
}
pageURL = page.NextLink
}
@@ -258,15 +266,18 @@ func (d *Microsoft365Driver) listDirectoryRoles(ctx context.Context) ([]microsof
url := fmt.Sprintf("%s/directoryRoles", microsoft365GraphBaseURL)
var all []microsoft365DirectoryRole
for range microsoft365MaxPaginationOK {
var page microsoft365RolesPage
if err := d.fetchJSON(ctx, url, &page); err != nil {
return nil, err
}
all = append(all, page.Value...)
if page.NextLink == "" {
return all, nil
}
url = page.NextLink
}
@@ -277,15 +288,18 @@ func (d *Microsoft365Driver) listRoleMembers(ctx context.Context, roleID string)
url := fmt.Sprintf("%s/directoryRoles/%s/members", microsoft365GraphBaseURL, roleID)
var all []microsoft365RoleMember
for range microsoft365MaxPaginationOK {
var page microsoft365MembersPage
if err := d.fetchJSON(ctx, url, &page); err != nil {
return nil, err
}
all = append(all, page.Value...)
if page.NextLink == "" {
return all, nil
}
url = page.NextLink
}
@@ -297,12 +311,14 @@ func (d *Microsoft365Driver) fetchJSON(ctx context.Context, url string, dst any)
if err != nil {
return fmt.Errorf("cannot create graph request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := d.httpClient.Do(req)
if err != nil {
return fmt.Errorf("cannot execute graph request: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {

View File

@@ -144,6 +144,7 @@ func (d *MondayDriver) queryUsers(ctx context.Context, page int) ([]mondayUser,
if err != nil {
return nil, fmt.Errorf("cannot create monday users request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
@@ -151,6 +152,7 @@ func (d *MondayDriver) queryUsers(ctx context.Context, page int) ([]mondayUser,
if err != nil {
return nil, fmt.Errorf("cannot execute monday users request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {

View File

@@ -69,6 +69,7 @@ func ProviderDisplayName(provider coredata.ConnectorProvider) string {
if name, ok := providerDisplayNames[provider]; ok {
return name
}
return string(provider)
}
@@ -91,6 +92,7 @@ func (r *slackNameResolver) ResolveInstanceName(ctx context.Context) (string, er
if err != nil {
return "", fmt.Errorf("cannot execute slack auth.test request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
var resp struct {
@@ -156,6 +158,7 @@ func (r *linearNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot create linear organization request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
@@ -163,6 +166,7 @@ func (r *linearNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot execute linear organization request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -182,6 +186,7 @@ func (r *linearNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
return "", fmt.Errorf("cannot decode linear organization response: %w", err)
}
if len(resp.Errors) > 0 {
return "", fmt.Errorf("linear graphql error: %s", resp.Errors[0].Message)
}
@@ -208,12 +213,14 @@ func (r *cloudflareNameResolver) ResolveInstanceName(ctx context.Context) (strin
if err != nil {
return "", fmt.Errorf("cannot create cloudflare accounts request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute cloudflare accounts request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -255,12 +262,14 @@ func (r *brexNameResolver) ResolveInstanceName(ctx context.Context) (string, err
if err != nil {
return "", fmt.Errorf("cannot create brex company request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute brex company request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -297,12 +306,14 @@ func (r *tallyNameResolver) ResolveInstanceName(ctx context.Context) (string, er
if err != nil {
return "", fmt.Errorf("cannot create tally organization request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute tally organization request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -338,12 +349,14 @@ func (r *hubspotNameResolver) ResolveInstanceName(ctx context.Context) (string,
if err != nil {
return "", fmt.Errorf("cannot create hubspot account-info request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute hubspot account-info request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -375,12 +388,14 @@ func (r *docusignNameResolver) ResolveInstanceName(ctx context.Context) (string,
if err != nil {
return "", fmt.Errorf("cannot create docusign userinfo request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute docusign userinfo request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -429,12 +444,14 @@ func (r *openaiNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot create openai organization request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute openai organization request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -473,12 +490,14 @@ func (r *sentryNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot create sentry organization request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute sentry organization request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -512,12 +531,14 @@ func (r *githubNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot create github organization request: %w", err)
}
req.Header.Set("Accept", "application/vnd.github+json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute github organization request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -565,6 +586,7 @@ func (r *intercomNameResolver) ResolveInstanceName(ctx context.Context) (string,
if err != nil {
return "", fmt.Errorf("cannot create intercom me request: %w", err)
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Intercom-Version", "2.11")
@@ -572,6 +594,7 @@ func (r *intercomNameResolver) ResolveInstanceName(ctx context.Context) (string,
if err != nil {
return "", fmt.Errorf("cannot execute intercom me request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -622,12 +645,14 @@ func (r *gitlabNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot create gitlab group request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute gitlab group request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -645,6 +670,7 @@ func (r *gitlabNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if resp.Name != "" {
return resp.Name, nil
}
return resp.FullPath, nil
}
@@ -669,12 +695,14 @@ func (r *bitbucketNameResolver) ResolveInstanceName(ctx context.Context) (string
if err != nil {
return "", fmt.Errorf("cannot create bitbucket workspace request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute bitbucket workspace request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -692,6 +720,7 @@ func (r *bitbucketNameResolver) ResolveInstanceName(ctx context.Context) (string
if resp.Name != "" {
return resp.Name, nil
}
return resp.Slug, nil
}
@@ -716,12 +745,14 @@ func (r *herokuNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot create heroku team request: %w", err)
}
req.Header.Set("Accept", "application/vnd.heroku+json; version=3")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute heroku team request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -774,12 +805,14 @@ func (r *asanaNameResolver) ResolveInstanceName(ctx context.Context) (string, er
if err != nil {
return "", fmt.Errorf("cannot create asana workspace request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute asana workspace request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -819,12 +852,14 @@ func (r *netlifyNameResolver) ResolveInstanceName(ctx context.Context) (string,
if err != nil {
return "", fmt.Errorf("cannot create netlify account request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute netlify account request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -862,12 +897,14 @@ func (r *clickupNameResolver) ResolveInstanceName(ctx context.Context) (string,
if err != nil {
return "", fmt.Errorf("cannot create clickup team request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute clickup team request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -905,16 +942,19 @@ func (r *vercelNameResolver) ResolveInstanceName(ctx context.Context) (string, e
}
teamURL := fmt.Sprintf("https://api.vercel.com/v2/teams/%s", url.PathEscape(r.teamID))
teamReq, err := http.NewRequestWithContext(ctx, http.MethodGet, teamURL, nil)
if err != nil {
return "", fmt.Errorf("cannot create vercel team request: %w", err)
}
teamReq.Header.Set("Accept", "application/json")
teamResp, err := r.httpClient.Do(teamReq)
if err != nil {
return "", fmt.Errorf("cannot execute vercel team request: %w", err)
}
defer func() { _ = teamResp.Body.Close() }()
if teamResp.StatusCode == http.StatusOK {
@@ -925,9 +965,11 @@ func (r *vercelNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err := json.NewDecoder(teamResp.Body).Decode(&body); err != nil {
return "", fmt.Errorf("cannot decode vercel team response: %w", err)
}
if body.Name != "" {
return body.Name, nil
}
return body.Slug, nil
}
@@ -941,9 +983,11 @@ func (r *vercelNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", err
}
if user.Username != "" {
return user.Username, nil
}
return user.Name, nil
}
@@ -972,6 +1016,7 @@ func (r *mondayNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot create monday account request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
@@ -979,6 +1024,7 @@ func (r *mondayNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot execute monday account request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -1023,6 +1069,7 @@ func (r *notionNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot create notion users/me request: %w", err)
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Notion-Version", notionAPIVersion)
@@ -1030,6 +1077,7 @@ func (r *notionNameResolver) ResolveInstanceName(ctx context.Context) (string, e
if err != nil {
return "", fmt.Errorf("cannot execute notion users/me request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -1068,12 +1116,14 @@ func (r *microsoft365NameResolver) ResolveInstanceName(ctx context.Context) (str
if err != nil {
return "", fmt.Errorf("cannot create microsoft 365 organization request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute microsoft 365 organization request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
@@ -1101,13 +1151,16 @@ func (r *microsoft365NameResolver) ResolveInstanceName(ctx context.Context) (str
if org.DisplayName != "" {
return org.DisplayName, nil
}
for _, d := range org.VerifiedDomains {
if d.IsDefault {
return d.Name, nil
}
}
if len(org.VerifiedDomains) > 0 {
return org.VerifiedDomains[0].Name, nil
}
return "", nil
}

View File

@@ -37,9 +37,11 @@ func (h *hostRewriter) RoundTrip(r *http.Request) (*http.Response, error) {
if err != nil {
return nil, err
}
r2 := r.Clone(r.Context())
r2.URL.Scheme = u.Scheme
r2.URL.Host = u.Host
return http.DefaultTransport.RoundTrip(r2)
}
@@ -88,11 +90,13 @@ func TestNotionNameResolver(t *testing.T) {
defer srv.Close()
client := &http.Client{Transport: &hostRewriter{target: srv.URL}}
got, err := NewNotionNameResolver(client).ResolveInstanceName(context.Background())
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})

View File

@@ -94,12 +94,14 @@ func (d *NetlifyDriver) queryMembers(ctx context.Context, endpoint string) ([]ne
if err != nil {
return nil, "", fmt.Errorf("cannot create netlify members request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, "", fmt.Errorf("cannot execute netlify members request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {

View File

@@ -96,6 +96,7 @@ func (d *NotionDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
if !resp.HasMore || resp.NextCursor == "" {
return records, nil
}
nextCursor := resp.NextCursor
startCursor = &nextCursor
}
@@ -114,15 +115,18 @@ func (d *NotionDriver) queryUsers(ctx context.Context, startCursor *string) (*no
q := req.URL.Query()
q.Set("page_size", "100")
if startCursor != nil {
q.Set("start_cursor", *startCursor)
}
req.URL.RawQuery = q.Encode()
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot execute notion users request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -71,6 +71,7 @@ func NewOnePasswordDriver(httpClient *http.Client, baseURL string) *OnePasswordD
func (d *OnePasswordDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
var records []AccountRecord
startIndex := 1
for range maxPaginationPages {
@@ -103,6 +104,7 @@ func (d *OnePasswordDriver) ListAccounts(ctx context.Context) ([]AccountRecord,
if record.FullName == "" && u.Name.Formatted != "" {
record.FullName = u.Name.Formatted
}
if record.FullName == "" && (u.Name.GivenName != "" || u.Name.FamilyName != "") {
record.FullName = u.Name.GivenName + " " + u.Name.FamilyName
}
@@ -128,6 +130,7 @@ func (d *OnePasswordDriver) ListAccounts(ctx context.Context) ([]AccountRecord,
if len(resp.Resources) == 0 || resp.ItemsPerPage <= 0 || startIndex+resp.ItemsPerPage > resp.TotalResults {
return records, nil
}
startIndex += resp.ItemsPerPage
}
@@ -139,6 +142,7 @@ func (d *OnePasswordDriver) queryUsers(ctx context.Context, startIndex int) (*on
if err != nil {
return nil, fmt.Errorf("cannot parse 1password base url: %w", err)
}
u = u.JoinPath("scim", "v2", "Users")
q := u.Query()
q.Set("startIndex", strconv.Itoa(startIndex))
@@ -149,12 +153,14 @@ func (d *OnePasswordDriver) queryUsers(ctx context.Context, startIndex int) (*on
if err != nil {
return nil, fmt.Errorf("cannot create 1password users request: %w", err)
}
req.Header.Set("Accept", "application/scim+json")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot execute 1password users request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -107,6 +107,7 @@ func (d *OnePasswordUsersAPIDriver) ListAccounts(ctx context.Context) ([]Account
if resp.NextPageToken == "" {
return records, nil
}
pageToken = resp.NextPageToken
}
@@ -118,6 +119,7 @@ func (d *OnePasswordUsersAPIDriver) queryUsers(ctx context.Context, pageToken st
if err != nil {
return nil, fmt.Errorf("cannot parse 1password users api base url: %w", err)
}
u = u.JoinPath("v1beta1", "accounts", d.accountID, "users")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
@@ -127,9 +129,11 @@ func (d *OnePasswordUsersAPIDriver) queryUsers(ctx context.Context, pageToken st
q := req.URL.Query()
q.Set("max_page_size", "100")
if pageToken != "" {
q.Set("page_token", pageToken)
}
req.URL.RawQuery = q.Encode()
req.Header.Set("Accept", "application/json")
@@ -138,6 +142,7 @@ func (d *OnePasswordUsersAPIDriver) queryUsers(ctx context.Context, pageToken st
if err != nil {
return nil, fmt.Errorf("cannot execute 1password users api request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -89,6 +89,7 @@ func (d *OpenAIDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
if !resp.HasMore || resp.LastID == "" {
return records, nil
}
after = resp.LastID
}
@@ -103,9 +104,11 @@ func (d *OpenAIDriver) fetchUsers(ctx context.Context, after string) (*openaiUse
q := req.URL.Query()
q.Set("limit", "100")
if after != "" {
q.Set("after", after)
}
req.URL.RawQuery = q.Encode()
req.Header.Set("Accept", "application/json")
@@ -114,6 +117,7 @@ func (d *OpenAIDriver) fetchUsers(ctx context.Context, after string) (*openaiUse
if err != nil {
return nil, fmt.Errorf("cannot execute openai users request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -38,12 +38,14 @@ func ListGitHubOrganizations(ctx context.Context, httpClient *http.Client) ([]Or
if err != nil {
return nil, fmt.Errorf("cannot create github organizations request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot fetch github organizations: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
@@ -64,8 +66,10 @@ func ListGitHubOrganizations(ctx context.Context, httpClient *http.Client) ([]Or
if displayName == "" {
displayName = org.Login
}
result[i] = Organization{Slug: org.Login, DisplayName: displayName}
}
return result, nil
}
@@ -81,12 +85,14 @@ func ListSentryOrganizations(ctx context.Context, httpClient *http.Client) ([]Or
if err != nil {
return nil, fmt.Errorf("cannot create sentry organizations request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot fetch sentry organizations: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
@@ -107,8 +113,10 @@ func ListSentryOrganizations(ctx context.Context, httpClient *http.Client) ([]Or
if displayName == "" {
displayName = org.Slug
}
result[i] = Organization{Slug: org.Slug, DisplayName: displayName}
}
return result, nil
}
@@ -125,12 +133,14 @@ func ListGitLabOrganizations(ctx context.Context, httpClient *http.Client) ([]Or
if err != nil {
return nil, fmt.Errorf("cannot create gitlab organizations request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot fetch gitlab organizations: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
@@ -152,11 +162,13 @@ func ListGitLabOrganizations(ctx context.Context, httpClient *http.Client) ([]Or
if displayName == "" {
displayName = g.FullPath
}
result[i] = Organization{
Slug: strconv.FormatInt(g.ID, 10),
DisplayName: displayName,
}
}
return result, nil
}
@@ -174,6 +186,7 @@ func ListBitbucketOrganizations(ctx context.Context, httpClient *http.Client) ([
if err != nil {
return nil, fmt.Errorf("cannot create bitbucket organizations request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := httpClient.Do(req)
@@ -203,6 +216,7 @@ func ListBitbucketOrganizations(ctx context.Context, httpClient *http.Client) ([
_ = resp.Body.Close()
return nil, fmt.Errorf("cannot decode bitbucket organizations response: %w", err)
}
_ = resp.Body.Close()
for _, v := range body.Values {
@@ -211,18 +225,22 @@ func ListBitbucketOrganizations(ctx context.Context, httpClient *http.Client) ([
slug = v.Workspace.Slug
name = v.Workspace.Name
}
displayName := name
if displayName == "" {
displayName = slug
}
result = append(result, Organization{Slug: slug, DisplayName: displayName})
}
if body.Next == "" {
return result, nil
}
pageURL = body.Next
}
return nil, fmt.Errorf("cannot list all bitbucket organizations: %w", ErrPaginationLimitReached)
}
@@ -233,12 +251,14 @@ func ListHerokuOrganizations(ctx context.Context, httpClient *http.Client) ([]Or
if err != nil {
return nil, fmt.Errorf("cannot create heroku organizations request: %w", err)
}
req.Header.Set("Accept", "application/vnd.heroku+json; version=3")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot fetch heroku organizations: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
@@ -259,8 +279,10 @@ func ListHerokuOrganizations(ctx context.Context, httpClient *http.Client) ([]Or
if displayName == "" {
displayName = t.ID
}
result[i] = Organization{Slug: t.ID, DisplayName: displayName}
}
return result, nil
}
@@ -276,12 +298,14 @@ func ListAsanaOrganizations(ctx context.Context, httpClient *http.Client) ([]Org
if err != nil {
return nil, fmt.Errorf("cannot create asana organizations request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot fetch asana organizations: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
@@ -304,8 +328,10 @@ func ListAsanaOrganizations(ctx context.Context, httpClient *http.Client) ([]Org
if displayName == "" {
displayName = w.GID
}
result[i] = Organization{Slug: w.GID, DisplayName: displayName}
}
return result, nil
}
@@ -321,12 +347,14 @@ func ListNetlifyOrganizations(ctx context.Context, httpClient *http.Client) ([]O
if err != nil {
return nil, fmt.Errorf("cannot create netlify organizations request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot fetch netlify organizations: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
@@ -348,8 +376,10 @@ func ListNetlifyOrganizations(ctx context.Context, httpClient *http.Client) ([]O
if displayName == "" {
displayName = a.Slug
}
result[i] = Organization{Slug: a.Slug, DisplayName: displayName}
}
return result, nil
}
@@ -365,12 +395,14 @@ func ListClickUpOrganizations(ctx context.Context, httpClient *http.Client) ([]O
if err != nil {
return nil, fmt.Errorf("cannot create clickup organizations request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot fetch clickup organizations: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
@@ -393,7 +425,9 @@ func ListClickUpOrganizations(ctx context.Context, httpClient *http.Client) ([]O
if displayName == "" {
displayName = t.ID
}
result[i] = Organization{Slug: t.ID, DisplayName: displayName}
}
return result, nil
}

View File

@@ -65,6 +65,7 @@ func (d *PagerDutyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, er
var records []AccountRecord
const limit = 100
offset := 0
for range maxPaginationPages {
@@ -113,6 +114,7 @@ func (d *PagerDutyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, er
if pageSize <= 0 {
pageSize = limit
}
offset += pageSize
}
@@ -130,12 +132,14 @@ func (d *PagerDutyDriver) queryUsers(ctx context.Context, offset, limit int) (*p
if err != nil {
return nil, fmt.Errorf("cannot create pagerduty users request: %w", err)
}
req.Header.Set("Accept", "application/vnd.pagerduty+json;version=2")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot execute pagerduty users request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {

View File

@@ -54,6 +54,7 @@ func (d *ResendDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
}
var records []AccountRecord
for _, k := range resp.Data {
record := AccountRecord{
FullName: k.Name,
@@ -96,6 +97,7 @@ func (d *ResendDriver) fetchAPIKeys(ctx context.Context) (*resendAPIKeysResponse
if err != nil {
return nil, fmt.Errorf("cannot execute resend api-keys request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -67,12 +67,14 @@ func (d *SentryDriver) resolveOrgSlug(ctx context.Context) (string, error) {
if err != nil {
return "", fmt.Errorf("cannot create sentry organizations request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := d.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot fetch sentry organizations: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
@@ -100,6 +102,7 @@ func (d *SentryDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
if err != nil {
return nil, fmt.Errorf("cannot resolve sentry organization slug: %w", err)
}
orgSlug = slug
}
@@ -130,6 +133,7 @@ func (d *SentryDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
isAdmin := m.OrgRole == "admin" || m.OrgRole == "owner"
mfaStatus := coredata.MFAStatusUnknown
if m.User != nil {
if m.User.Has2FA {
mfaStatus = coredata.MFAStatusEnabled
@@ -188,6 +192,7 @@ func (d *SentryDriver) queryMembers(ctx context.Context, url string) ([]sentryMe
if err != nil {
return nil, "", fmt.Errorf("cannot execute sentry members request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -221,8 +226,10 @@ func sentryAuthMethod(flags map[string]bool, user *sentryUser) coredata.AccessEn
if flags["sso:linked"] {
return coredata.AccessEntryAuthMethodSSO
}
if user != nil && user.HasPasswordAuth {
return coredata.AccessEntryAuthMethodPassword
}
return coredata.AccessEntryAuthMethodUnknown
}

View File

@@ -120,6 +120,7 @@ func (d *SlackDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error)
if resp.ResponseMetadata.NextCursor == "" {
return records, nil
}
cursor = resp.ResponseMetadata.NextCursor
}
@@ -134,15 +135,18 @@ func (d *SlackDriver) queryUsers(ctx context.Context, cursor string) (*slackUser
q := req.URL.Query()
q.Set("limit", "200")
if cursor != "" {
q.Set("cursor", cursor)
}
req.URL.RawQuery = q.Encode()
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot execute slack users.list request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -180,5 +184,6 @@ func slackMFAStatus(has2FA bool) coredata.MFAStatus {
if has2FA {
return coredata.MFAStatusEnabled
}
return coredata.MFAStatusDisabled
}

View File

@@ -36,12 +36,14 @@ func TestSlackDriver(t *testing.T) {
// Find the first human user (bots may not have email).
var r AccountRecord
for _, rec := range records {
if rec.Email != "" {
r = rec
break
}
}
require.NotEmpty(t, r.Email, "expected at least one record with an email")
assert.NotEmpty(t, r.ExternalID)
assert.NotEmpty(t, r.Role)

View File

@@ -53,6 +53,7 @@ func (d *SupabaseDriver) ListAccounts(ctx context.Context) ([]AccountRecord, err
}
var records []AccountRecord
for _, m := range members {
mfaStatus := coredata.MFAStatusDisabled
if m.MFAEnabled {
@@ -96,6 +97,7 @@ func (d *SupabaseDriver) queryMembers(ctx context.Context) ([]supabaseMember, er
if err != nil {
return nil, fmt.Errorf("cannot execute supabase members request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()

View File

@@ -89,6 +89,7 @@ func (d *TallyDriver) listUsers(ctx context.Context) ([]AccountRecord, error) {
if err != nil {
return nil, fmt.Errorf("cannot execute tally users request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -106,6 +107,7 @@ func (d *TallyDriver) listUsers(ctx context.Context) ([]AccountRecord, error) {
}
var records []AccountRecord
for _, u := range users {
mfaStatus := coredata.MFAStatusDisabled
if u.HasTwoFactorEnabled {
@@ -149,6 +151,7 @@ func (d *TallyDriver) listInvites(ctx context.Context) ([]AccountRecord, error)
if err != nil {
return nil, fmt.Errorf("cannot execute tally invites request: %w", err)
}
defer func() {
_ = httpResp.Body.Close()
}()
@@ -166,6 +169,7 @@ func (d *TallyDriver) listInvites(ctx context.Context) ([]AccountRecord, error)
}
var records []AccountRecord
for _, inv := range invites {
record := AccountRecord{
Email: inv.Email,

View File

@@ -57,6 +57,7 @@ func newRecorder(t *testing.T, cassettePath string, envVar string) *recorder.Rec
if mode == recorder.ModeReplayOnly {
t.Skipf("cassette not found (record with %s env var): %v", envVar, err)
}
t.Fatalf("cannot create vcr recorder: %v", err)
}
@@ -81,6 +82,7 @@ func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
if rt.authValue != "" {
req.Header.Set("Authorization", rt.authValue)
}
return rt.transport.RoundTrip(req)
}
@@ -89,6 +91,7 @@ func bearerAuth(token string) string {
if token == "" {
return ""
}
return "Bearer " + token
}
@@ -104,5 +107,6 @@ func newVCRClient(rec *recorder.Recorder, authValue string) *http.Client {
transport: transport,
}
}
return &http.Client{Transport: transport}
}

View File

@@ -111,6 +111,7 @@ func (d *VercelDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
if page.Pagination.Next == nil {
return records, nil
}
cursor = strconv.FormatInt(*page.Pagination.Next, 10)
}
@@ -120,9 +121,11 @@ func (d *VercelDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
func (d *VercelDriver) queryMembers(ctx context.Context, cursor string) (*vercelMembersPage, error) {
q := url.Values{}
q.Set("limit", "100")
if cursor != "" {
q.Set("until", cursor)
}
u := url.URL{
Scheme: "https",
Host: "api.vercel.com",
@@ -134,12 +137,14 @@ func (d *VercelDriver) queryMembers(ctx context.Context, cursor string) (*vercel
if err != nil {
return nil, fmt.Errorf("cannot create vercel members request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := d.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot execute vercel members request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {

View File

@@ -80,11 +80,13 @@ func (e *ReviewEngine) FetchSource(
if err := source.LoadByID(ctx, tx, e.scope, sourceID); err != nil {
return fmt.Errorf("cannot load access source %s: %w", sourceID, err)
}
if source.OrganizationID != campaign.OrganizationID {
return fmt.Errorf("cannot process access source: %s does not belong to campaign organization", sourceID)
}
var err error
driver, err = e.resolveDriver(ctx, tx, source)
if err != nil {
return fmt.Errorf("cannot resolve driver for source %s: %w", source.Name, err)
@@ -97,6 +99,7 @@ func (e *ReviewEngine) FetchSource(
}
} else {
entries := &coredata.AccessEntries{}
baseline, err = entries.LoadBaselineBySourceID(ctx, tx, e.scope, lastCompletedCampaign.ID, sourceID)
if err != nil {
return fmt.Errorf("cannot load baseline entries by source: %w", err)
@@ -117,10 +120,13 @@ func (e *ReviewEngine) FetchSource(
sourceCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
accounts, err := driver.ListAccounts(sourceCtx)
cancel()
if err != nil {
return 0, fmt.Errorf("cannot list accounts from source %s: %w", source.Name, err)
}
fetchedCount = len(accounts)
err = e.pg.WithTx(
@@ -132,6 +138,7 @@ func (e *ReviewEngine) FetchSource(
for _, account := range accounts {
accountKey := normalizeAccountKey(account.Email, account.ExternalID)
seenAccountKeys[accountKey] = struct{}{}
incrementalTag := coredata.AccessEntryIncrementalTagNew
if _, ok := previousByAccountKey[accountKey]; ok {
incrementalTag = coredata.AccessEntryIncrementalTagUnchanged
@@ -210,6 +217,7 @@ func (e *ReviewEngine) FetchSource(
func normalizeAccountKey(email, externalID string) string {
emailKey := strings.ToLower(strings.TrimSpace(email))
externalID = strings.TrimSpace(externalID)
if externalID != "" {
return emailKey + "|" + externalID
@@ -231,6 +239,7 @@ func (e *ReviewEngine) oauthClient(
return conn.RefreshableClient(ctx, *refreshCfg)
}
}
return conn.Client(ctx)
}
@@ -245,6 +254,7 @@ func (e *ReviewEngine) connectorHTTPClient(
if oauth2Conn, ok := dbConnector.Connection.(*connector.OAuth2Connection); ok {
return e.oauthClient(ctx, oauth2Conn, dbConnector.Provider)
}
return dbConnector.Connection.Client(ctx)
}
@@ -312,15 +322,19 @@ func (e *ReviewEngine) resolveDriver(
if err != nil {
return nil, fmt.Errorf("cannot read 1password users api settings: %w", err)
}
return drivers.NewOnePasswordUsersAPIDriver(httpClient, settings.AccountID, settings.Region), nil
}
onePasswordSettings, err := coredata.ConnectorSettings[coredata.OnePasswordConnectorSettings](dbConnector)
if err != nil {
return nil, fmt.Errorf("cannot read 1password connector settings: %w", err)
}
if onePasswordSettings.SCIMBridgeURL == "" {
return nil, fmt.Errorf("1password connector requires scim_bridge_url in settings")
}
return drivers.NewOnePasswordDriver(httpClient, onePasswordSettings.SCIMBridgeURL), nil
case coredata.ConnectorProviderHubSpot:
return drivers.NewHubSpotDriver(httpClient), nil
@@ -335,9 +349,11 @@ func (e *ReviewEngine) resolveDriver(
if err != nil {
return nil, fmt.Errorf("cannot read tally connector settings: %w", err)
}
if tallySettings.OrganizationID == "" {
return nil, fmt.Errorf("tally connector requires organization_id in settings")
}
return drivers.NewTallyDriver(httpClient, tallySettings.OrganizationID), nil
case coredata.ConnectorProviderCloudflare:
return drivers.NewCloudflareDriver(httpClient), nil
@@ -348,6 +364,7 @@ func (e *ReviewEngine) resolveDriver(
if err != nil {
return nil, fmt.Errorf("cannot read sentry connector settings: %w", err)
}
// OrganizationSlug may be empty for OAuth connections; the driver auto-discovers it.
return drivers.NewSentryDriver(httpClient, sentrySettings.OrganizationSlug), nil
case coredata.ConnectorProviderSupabase:
@@ -355,18 +372,22 @@ func (e *ReviewEngine) resolveDriver(
if err != nil {
return nil, fmt.Errorf("cannot read supabase connector settings: %w", err)
}
if supabaseSettings.OrganizationSlug == "" {
return nil, fmt.Errorf("supabase connector requires organization_slug in settings")
}
return drivers.NewSupabaseDriver(httpClient, supabaseSettings.OrganizationSlug), nil
case coredata.ConnectorProviderGitHub:
githubSettings, err := coredata.ConnectorSettings[coredata.GitHubConnectorSettings](dbConnector)
if err != nil {
return nil, fmt.Errorf("cannot read github connector settings: %w", err)
}
if githubSettings.Organization == "" {
return nil, fmt.Errorf("github connector requires organization in settings")
}
return drivers.NewGitHubDriver(httpClient, githubSettings.Organization, e.logger.Named("github")), nil
case coredata.ConnectorProviderIntercom:
return drivers.NewIntercomDriver(httpClient), nil
@@ -379,27 +400,33 @@ func (e *ReviewEngine) resolveDriver(
if err != nil {
return nil, fmt.Errorf("cannot read gitlab connector settings: %w", err)
}
if gitlabSettings.GroupID == "" {
return nil, fmt.Errorf("gitlab connector requires group_id in settings")
}
return drivers.NewGitLabDriver(httpClient, gitlabSettings.GroupID), nil
case coredata.ConnectorProviderBitbucket:
bitbucketSettings, err := coredata.ConnectorSettings[coredata.BitbucketConnectorSettings](dbConnector)
if err != nil {
return nil, fmt.Errorf("cannot read bitbucket connector settings: %w", err)
}
if bitbucketSettings.Workspace == "" {
return nil, fmt.Errorf("bitbucket connector requires workspace in settings")
}
return drivers.NewBitbucketDriver(httpClient, bitbucketSettings.Workspace), nil
case coredata.ConnectorProviderHeroku:
herokuSettings, err := coredata.ConnectorSettings[coredata.HerokuConnectorSettings](dbConnector)
if err != nil {
return nil, fmt.Errorf("cannot read heroku connector settings: %w", err)
}
if herokuSettings.TeamID == "" {
return nil, fmt.Errorf("heroku connector requires team_id in settings")
}
return drivers.NewHerokuDriver(httpClient, herokuSettings.TeamID), nil
case coredata.ConnectorProviderPagerDuty:
// PagerDuty's REST API uses the regional api.pagerduty.com host;
@@ -413,36 +440,44 @@ func (e *ReviewEngine) resolveDriver(
if err != nil {
return nil, fmt.Errorf("cannot read asana connector settings: %w", err)
}
if asanaSettings.WorkspaceGID == "" {
return nil, fmt.Errorf("asana connector requires workspace_gid in settings")
}
return drivers.NewAsanaDriver(httpClient, asanaSettings.WorkspaceGID), nil
case coredata.ConnectorProviderNetlify:
netlifySettings, err := coredata.ConnectorSettings[coredata.NetlifyConnectorSettings](dbConnector)
if err != nil {
return nil, fmt.Errorf("cannot read netlify connector settings: %w", err)
}
if netlifySettings.AccountSlug == "" {
return nil, fmt.Errorf("netlify connector requires account_slug in settings")
}
return drivers.NewNetlifyDriver(httpClient, netlifySettings.AccountSlug), nil
case coredata.ConnectorProviderClickUp:
clickupSettings, err := coredata.ConnectorSettings[coredata.ClickUpConnectorSettings](dbConnector)
if err != nil {
return nil, fmt.Errorf("cannot read clickup connector settings: %w", err)
}
if clickupSettings.TeamID == "" {
return nil, fmt.Errorf("clickup connector requires team_id in settings")
}
return drivers.NewClickUpDriver(httpClient, clickupSettings.TeamID), nil
case coredata.ConnectorProviderVercel:
vercelSettings, err := coredata.ConnectorSettings[coredata.VercelConnectorSettings](dbConnector)
if err != nil {
return nil, fmt.Errorf("cannot read vercel connector settings: %w", err)
}
if vercelSettings.TeamID == "" {
return nil, fmt.Errorf("vercel connector requires team_id in settings")
}
return drivers.NewVercelDriver(httpClient, vercelSettings.TeamID), nil
case coredata.ConnectorProviderMonday:
return drivers.NewMondayDriver(httpClient), nil

View File

@@ -78,6 +78,7 @@ func NewService(
} else {
fetchWorkerOpts = append(fetchWorkerOpts, worker.WithInterval(30*time.Second))
}
fetchWorkerOpts = append(fetchWorkerOpts, worker.WithMaxConcurrency(20))
s.fetchWorker = NewSourceFetchWorker(
@@ -137,11 +138,14 @@ func (s *Service) ResolveEntryOrganizationID(ctx context.Context, entryID gid.GI
ctx,
func(ctx context.Context, conn pg.Querier) error {
var err error
entry := &coredata.AccessEntry{}
organizationID, err = entry.LoadOrganizationID(ctx, conn, entryID)
if err != nil {
return fmt.Errorf("cannot load organization id: %w", err)
}
return nil
},
)

View File

@@ -79,6 +79,7 @@ func (h *sourceNameHandler) Claim(ctx context.Context) (coredata.AccessSource, e
if errors.Is(err, coredata.ErrNoAccessSourceNameSyncAvailable) {
return coredata.AccessSource{}, worker.ErrNoTask
}
return coredata.AccessSource{}, err
}
@@ -128,6 +129,7 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
}
resolver = h.buildResolver(&dbConnector, httpClient)
return nil
},
)
@@ -136,6 +138,7 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
log.String("source_id", source.ID.String()),
log.Error(err),
)
return nil
}
@@ -144,6 +147,7 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
log.String("source_id", source.ID.String()),
log.String("provider", dbConnector.Provider.String()),
)
return h.markNameSynced(ctx, &source)
}
@@ -157,6 +161,7 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
log.String("provider", dbConnector.Provider.String()),
log.Error(err),
)
return fmt.Errorf("cannot resolve instance name for source %s: %w", source.ID, err)
}
@@ -165,6 +170,7 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
log.String("source_id", source.ID.String()),
log.String("provider", dbConnector.Provider.String()),
)
return h.markNameSynced(ctx, &source)
}
@@ -178,6 +184,7 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
)
source.Name = newName
return h.markNameSynced(ctx, &source)
}
@@ -247,6 +254,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read tally connector settings", log.Error(err))
return nil
}
return drivers.NewTallyNameResolver(httpClient, tallySettings.OrganizationID)
case coredata.ConnectorProviderHubSpot:
return drivers.NewHubSpotNameResolver(httpClient)
@@ -260,6 +268,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read sentry connector settings", log.Error(err))
return nil
}
return drivers.NewSentryNameResolver(httpClient, sentrySettings.OrganizationSlug)
case coredata.ConnectorProviderGitHub:
githubSettings, err := coredata.ConnectorSettings[coredata.GitHubConnectorSettings](dbConnector)
@@ -267,6 +276,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read github connector settings", log.Error(err))
return nil
}
return drivers.NewGitHubNameResolver(httpClient, githubSettings.Organization)
case coredata.ConnectorProviderSupabase:
supabaseSettings, err := coredata.ConnectorSettings[coredata.SupabaseConnectorSettings](dbConnector)
@@ -274,6 +284,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read supabase connector settings", log.Error(err))
return nil
}
return drivers.NewSupabaseNameResolver(supabaseSettings.OrganizationSlug)
case coredata.ConnectorProviderIntercom:
return drivers.NewIntercomNameResolver(httpClient)
@@ -289,6 +300,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read gitlab connector settings", log.Error(err))
return nil
}
return drivers.NewGitLabNameResolver(httpClient, gitlabSettings.GroupID)
case coredata.ConnectorProviderBitbucket:
bitbucketSettings, err := coredata.ConnectorSettings[coredata.BitbucketConnectorSettings](dbConnector)
@@ -296,6 +308,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read bitbucket connector settings", log.Error(err))
return nil
}
return drivers.NewBitbucketNameResolver(httpClient, bitbucketSettings.Workspace)
case coredata.ConnectorProviderHeroku:
herokuSettings, err := coredata.ConnectorSettings[coredata.HerokuConnectorSettings](dbConnector)
@@ -303,6 +316,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read heroku connector settings", log.Error(err))
return nil
}
return drivers.NewHerokuNameResolver(httpClient, herokuSettings.TeamID)
case coredata.ConnectorProviderPagerDuty:
pdSettings, err := coredata.ConnectorSettings[coredata.PagerDutyConnectorSettings](dbConnector)
@@ -310,6 +324,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read pagerduty connector settings", log.Error(err))
return nil
}
return drivers.NewPagerDutyNameResolver(pdSettings.Subdomain)
case coredata.ConnectorProviderAsana:
asanaSettings, err := coredata.ConnectorSettings[coredata.AsanaConnectorSettings](dbConnector)
@@ -317,6 +332,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read asana connector settings", log.Error(err))
return nil
}
return drivers.NewAsanaNameResolver(httpClient, asanaSettings.WorkspaceGID)
case coredata.ConnectorProviderNetlify:
netlifySettings, err := coredata.ConnectorSettings[coredata.NetlifyConnectorSettings](dbConnector)
@@ -324,6 +340,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read netlify connector settings", log.Error(err))
return nil
}
return drivers.NewNetlifyNameResolver(httpClient, netlifySettings.AccountSlug)
case coredata.ConnectorProviderClickUp:
clickupSettings, err := coredata.ConnectorSettings[coredata.ClickUpConnectorSettings](dbConnector)
@@ -331,6 +348,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read clickup connector settings", log.Error(err))
return nil
}
return drivers.NewClickUpNameResolver(httpClient, clickupSettings.TeamID)
case coredata.ConnectorProviderVercel:
vercelSettings, err := coredata.ConnectorSettings[coredata.VercelConnectorSettings](dbConnector)
@@ -338,6 +356,7 @@ func (h *sourceNameHandler) buildResolver(
h.logger.Error("cannot read vercel connector settings", log.Error(err))
return nil
}
return drivers.NewVercelNameResolver(httpClient, vercelSettings.TeamID)
case coredata.ConnectorProviderMonday:
return drivers.NewMondayNameResolver(httpClient)

View File

@@ -77,12 +77,14 @@ func (h *sourceFetchHandler) Claim(ctx context.Context) (coredata.AccessReviewCa
if err := sourceFetch.Update(ctx, tx, scope); err != nil {
return fmt.Errorf("cannot update source fetch status: %w", err)
}
return nil
},
); err != nil {
if errors.Is(err, coredata.ErrNoAccessReviewCampaignSourceFetchAvailable) {
return coredata.AccessReviewCampaignSourceFetch{}, worker.ErrNoTask
}
return coredata.AccessReviewCampaignSourceFetch{}, fmt.Errorf("cannot claim source fetch: %w", err)
}
@@ -101,6 +103,7 @@ func (h *sourceFetchHandler) RecoverStale(ctx context.Context) error {
ctx,
func(ctx context.Context, tx pg.Tx) error {
var fetches coredata.AccessReviewCampaignSourceFetches
count, err := fetches.RecoverStale(ctx, tx, staleThreshold, now)
if err != nil {
return fmt.Errorf("cannot recover stale source fetches: %w", err)
@@ -135,6 +138,7 @@ func (h *sourceFetchHandler) handle(
if commitErr != nil {
return fmt.Errorf("cannot load campaign: %w, and cannot commit failed source fetch: %w", err, commitErr)
}
return fmt.Errorf("cannot load campaign: %w", err)
}
@@ -148,6 +152,7 @@ func (h *sourceFetchHandler) handle(
if finalizeErr := h.finalizeCampaignFetchLifecycle(ctx, sourceFetch.TenantID, sourceFetch.AccessReviewCampaignID); finalizeErr != nil {
return fmt.Errorf("cannot finalize campaign after failed source fetch: %w", finalizeErr)
}
return fmt.Errorf("cannot fetch source: %w", err)
}
@@ -250,6 +255,7 @@ func (h *sourceFetchHandler) finalizeCampaignFetchLifecycle(
campaign.Status = coredata.AccessReviewCampaignStatusPendingActions
campaign.UpdatedAt = time.Now()
return campaign.Update(ctx, tx, scope)
},
)