diff --git a/dump.dump b/dump.dump new file mode 100644 index 000000000..64f6238e8 Binary files /dev/null and b/dump.dump differ diff --git a/pkg/accessreview/access_source_service.go b/pkg/accessreview/access_source_service.go index d119c83c7..ee3ddeec4 100644 --- a/pkg/accessreview/access_source_service.go +++ b/pkg/accessreview/access_source_service.go @@ -385,51 +385,67 @@ func (s AccessSourceService) ConfigureAccessSource( switch dbConnector.Provider { case coredata.ConnectorProviderGitHub: - if err := dbConnector.SetSettings(&coredata.GitHubConnectorSettings{ - Organization: req.OrganizationSlug, - }); err != nil { + if err := dbConnector.SetSettings( + &coredata.GitHubConnectorSettings{ + Organization: req.OrganizationSlug, + }, + ); err != nil { return fmt.Errorf("cannot set github settings: %w", err) } case coredata.ConnectorProviderSentry: - if err := dbConnector.SetSettings(&coredata.SentryConnectorSettings{ - OrganizationSlug: req.OrganizationSlug, - }); err != nil { + if err := dbConnector.SetSettings( + &coredata.SentryConnectorSettings{ + OrganizationSlug: req.OrganizationSlug, + }, + ); err != nil { return fmt.Errorf("cannot set sentry settings: %w", err) } case coredata.ConnectorProviderGitLab: - if err := dbConnector.SetSettings(&coredata.GitLabConnectorSettings{ - GroupID: req.OrganizationSlug, - }); err != nil { + if err := dbConnector.SetSettings( + &coredata.GitLabConnectorSettings{ + GroupID: req.OrganizationSlug, + }, + ); err != nil { return fmt.Errorf("cannot set gitlab settings: %w", err) } case coredata.ConnectorProviderBitbucket: - if err := dbConnector.SetSettings(&coredata.BitbucketConnectorSettings{ - Workspace: req.OrganizationSlug, - }); err != nil { + if err := dbConnector.SetSettings( + &coredata.BitbucketConnectorSettings{ + Workspace: req.OrganizationSlug, + }, + ); err != nil { return fmt.Errorf("cannot set bitbucket settings: %w", err) } case coredata.ConnectorProviderHeroku: - if err := dbConnector.SetSettings(&coredata.HerokuConnectorSettings{ - TeamID: req.OrganizationSlug, - }); err != nil { + if err := dbConnector.SetSettings( + &coredata.HerokuConnectorSettings{ + TeamID: req.OrganizationSlug, + }, + ); err != nil { return fmt.Errorf("cannot set heroku settings: %w", err) } case coredata.ConnectorProviderAsana: - if err := dbConnector.SetSettings(&coredata.AsanaConnectorSettings{ - WorkspaceGID: req.OrganizationSlug, - }); err != nil { + if err := dbConnector.SetSettings( + &coredata.AsanaConnectorSettings{ + WorkspaceGID: req.OrganizationSlug, + }, + ); err != nil { return fmt.Errorf("cannot set asana settings: %w", err) } case coredata.ConnectorProviderNetlify: - if err := dbConnector.SetSettings(&coredata.NetlifyConnectorSettings{ - AccountSlug: req.OrganizationSlug, - }); err != nil { + if err := dbConnector.SetSettings( + &coredata.NetlifyConnectorSettings{ + AccountSlug: req.OrganizationSlug, + }, + ); err != nil { return fmt.Errorf("cannot set netlify settings: %w", err) } case coredata.ConnectorProviderClickUp: - if err := dbConnector.SetSettings(&coredata.ClickUpConnectorSettings{ - TeamID: req.OrganizationSlug, - }); err != nil { + if err := dbConnector.SetSettings( + &coredata.ClickUpConnectorSettings{ + TeamID: req.OrganizationSlug, + }, + ); err != nil { return fmt.Errorf("cannot set clickup settings: %w", err) } default: diff --git a/pkg/accessreview/drivers/asana.go b/pkg/accessreview/drivers/asana.go index 1b4f3374e..944b5e583 100644 --- a/pkg/accessreview/drivers/asana.go +++ b/pkg/accessreview/drivers/asana.go @@ -67,10 +67,21 @@ type asanaUsersPage struct { func (d *AsanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) { var records []AccountRecord - next := fmt.Sprintf( - "https://app.asana.com/api/1.0/workspaces/%s/users?opt_fields=email,name&limit=100", - url.PathEscape(d.workspaceGID), - ) + u, err := url.JoinPath("https://app.asana.com", "api", "1.0", "workspaces", d.workspaceGID, "users") + if err != nil { + return nil, fmt.Errorf("cannot build asana users URL: %w", err) + } + + parsed, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("cannot parse asana users URL: %w", err) + } + + q := parsed.Query() + q.Set("opt_fields", "email,name") + q.Set("limit", "100") + parsed.RawQuery = q.Encode() + next := parsed.String() for range maxPaginationPages { page, err := d.queryUsers(ctx, next) @@ -85,14 +96,17 @@ func (d *AsanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) // Active=false from any of those would fabricate state, so // leave Active nil (unknown) and let downstream review surface // the gap honestly. - records = append(records, AccountRecord{ - Email: u.Email, - FullName: u.Name, - ExternalID: u.GID, - MFAStatus: coredata.MFAStatusUnknown, - AuthMethod: coredata.AccessEntryAuthMethodUnknown, - AccountType: coredata.AccessEntryAccountTypeUser, - }) + records = append( + records, + AccountRecord{ + Email: u.Email, + FullName: u.Name, + ExternalID: u.GID, + MFAStatus: coredata.MFAStatusUnknown, + AuthMethod: coredata.AccessEntryAuthMethodUnknown, + AccountType: coredata.AccessEntryAccountTypeUser, + }, + ) } if page.NextPage == nil || page.NextPage.URI == "" { diff --git a/pkg/accessreview/drivers/bitbucket.go b/pkg/accessreview/drivers/bitbucket.go index 7f0bbdd97..3236527ea 100644 --- a/pkg/accessreview/drivers/bitbucket.go +++ b/pkg/accessreview/drivers/bitbucket.go @@ -67,10 +67,21 @@ type bitbucketMembersPage struct { func (d *BitbucketDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) { var records []AccountRecord - next := fmt.Sprintf( - "https://api.bitbucket.org/2.0/workspaces/%s/members?fields=%%2Bvalues.user.email&pagelen=100", - url.PathEscape(d.workspace), - ) + u, err := url.JoinPath("https://api.bitbucket.org", "2.0", "workspaces", d.workspace, "members") + if err != nil { + return nil, fmt.Errorf("cannot build bitbucket members URL: %w", err) + } + + parsed, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("cannot parse bitbucket members URL: %w", err) + } + + q := parsed.Query() + q.Set("fields", "+values.user.email") + q.Set("pagelen", "100") + parsed.RawQuery = q.Encode() + next := parsed.String() for range maxPaginationPages { page, err := d.queryMembers(ctx, next) diff --git a/pkg/accessreview/drivers/clickup.go b/pkg/accessreview/drivers/clickup.go index 6d2b41f6e..529408c7e 100644 --- a/pkg/accessreview/drivers/clickup.go +++ b/pkg/accessreview/drivers/clickup.go @@ -71,7 +71,10 @@ type clickupTeamResponse struct { } func (d *ClickUpDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) { - endpoint := fmt.Sprintf("https://api.clickup.com/api/v2/team/%s", url.PathEscape(d.teamID)) + endpoint, err := url.JoinPath("https://api.clickup.com", "api", "v2", "team", d.teamID) + if err != nil { + return nil, fmt.Errorf("cannot build clickup team URL: %w", err) + } req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { diff --git a/pkg/accessreview/drivers/cloudflare.go b/pkg/accessreview/drivers/cloudflare.go index f0dbda8d3..d43d727ef 100644 --- a/pkg/accessreview/drivers/cloudflare.go +++ b/pkg/accessreview/drivers/cloudflare.go @@ -19,6 +19,8 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "strconv" "strings" "go.probo.inc/probo/pkg/coredata" @@ -114,12 +116,17 @@ func (d *CloudflareDriver) queryAllAccounts(ctx context.Context) ([]cloudflareAc } func (d *CloudflareDriver) queryAccounts(ctx context.Context, page int) (*cloudflareListAccountsResponse, error) { - url := fmt.Sprintf( - "https://api.cloudflare.com/client/v4/accounts?page=%d&per_page=50", - page, - ) + parsed, err := url.Parse("https://api.cloudflare.com/client/v4/accounts") + if err != nil { + return nil, fmt.Errorf("cannot parse cloudflare accounts URL: %w", err) + } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + q := parsed.Query() + q.Set("page", strconv.Itoa(page)) + q.Set("per_page", "50") + parsed.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil) if err != nil { return nil, fmt.Errorf("cannot create cloudflare accounts request: %w", err) } @@ -208,13 +215,22 @@ func (d *CloudflareDriver) queryAllMembers(ctx context.Context, accountID string } func (d *CloudflareDriver) queryMembers(ctx context.Context, accountID string, page int) (*cloudflareListMembersResponse, error) { - url := fmt.Sprintf( - "https://api.cloudflare.com/client/v4/accounts/%s/members?page=%d&per_page=50", - accountID, - page, - ) + u, err := url.JoinPath("https://api.cloudflare.com", "client", "v4", "accounts", accountID, "members") + if err != nil { + return nil, fmt.Errorf("cannot build cloudflare members URL: %w", err) + } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + parsed, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("cannot parse cloudflare members URL: %w", err) + } + + q := parsed.Query() + q.Set("page", strconv.Itoa(page)) + q.Set("per_page", "50") + parsed.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil) if err != nil { return nil, fmt.Errorf("cannot create cloudflare members request: %w", err) } diff --git a/pkg/accessreview/drivers/docusign.go b/pkg/accessreview/drivers/docusign.go index aa16b211c..d20df6530 100644 --- a/pkg/accessreview/drivers/docusign.go +++ b/pkg/accessreview/drivers/docusign.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strconv" "strings" "time" @@ -179,10 +180,23 @@ func (d *DocuSignDriver) discoverAccount(ctx context.Context) (accountID string, } func (d *DocuSignDriver) queryUsers(ctx context.Context, baseURI string, accountID string, startPosition int) (*docusignUsersResponse, error) { - url := fmt.Sprintf("%s/restapi/v2.1/accounts/%s/users?additional_info=true&count=%d&start_position=%d", - baseURI, accountID, docusignUsersPageSize, startPosition) + u, err := url.JoinPath(baseURI, "restapi", "v2.1", "accounts", accountID, "users") + if err != nil { + return nil, fmt.Errorf("cannot build docusign users URL: %w", err) + } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + parsed, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("cannot parse docusign users URL: %w", err) + } + + q := parsed.Query() + q.Set("additional_info", "true") + q.Set("count", strconv.Itoa(docusignUsersPageSize)) + q.Set("start_position", strconv.Itoa(startPosition)) + parsed.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil) if err != nil { return nil, fmt.Errorf("cannot create docusign users request: %w", err) } diff --git a/pkg/accessreview/drivers/github.go b/pkg/accessreview/drivers/github.go index 7c5c25487..53f9b78f1 100644 --- a/pkg/accessreview/drivers/github.go +++ b/pkg/accessreview/drivers/github.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strconv" "time" @@ -82,7 +83,9 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error for _, m := range members { membership, err := d.fetchMembership(ctx, m.Login) if err != nil { - d.logger.WarnCtx(ctx, "cannot fetch github membership, skipping member", + d.logger.WarnCtx( + ctx, + "cannot fetch github membership, skipping member", log.Error(err), ) @@ -91,7 +94,9 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error profile, err := d.fetchUserProfile(ctx, m.Login) if err != nil { - d.logger.WarnCtx(ctx, "cannot fetch github user profile, skipping member", + d.logger.WarnCtx( + ctx, + "cannot fetch github user profile, skipping member", log.Error(err), ) @@ -145,13 +150,23 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error func (d *GitHubDriver) fetchAllMembers(ctx context.Context) ([]githubMember, error) { var members []githubMember - url := fmt.Sprintf( - "https://api.github.com/orgs/%s/members?per_page=100", - d.org, - ) + u, err := url.JoinPath("https://api.github.com", "orgs", d.org, "members") + if err != nil { + return nil, fmt.Errorf("cannot build github members URL: %w", err) + } + + parsed, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("cannot parse github members URL: %w", err) + } + + q := parsed.Query() + q.Set("per_page", "100") + parsed.RawQuery = q.Encode() + endpoint := parsed.String() for range maxPaginationPages { - page, nextURL, err := d.fetchMembersPage(ctx, url) + page, nextURL, err := d.fetchMembersPage(ctx, endpoint) if err != nil { return nil, err } @@ -162,7 +177,7 @@ func (d *GitHubDriver) fetchAllMembers(ctx context.Context) ([]githubMember, err return members, nil } - url = nextURL + endpoint = nextURL } return nil, fmt.Errorf("cannot list all github members: %w", ErrPaginationLimitReached) @@ -202,13 +217,24 @@ func (d *GitHubDriver) fetchMembersPage(ctx context.Context, url string) ([]gith func (d *GitHubDriver) fetchAll2FADisabledLogins(ctx context.Context) (map[string]bool, error) { set := make(map[string]bool) - url := fmt.Sprintf( - "https://api.github.com/orgs/%s/members?filter=2fa_disabled&per_page=100", - d.org, - ) + u, err := url.JoinPath("https://api.github.com", "orgs", d.org, "members") + if err != nil { + return nil, fmt.Errorf("cannot build github 2fa-disabled URL: %w", err) + } + + parsed, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("cannot parse github 2fa-disabled URL: %w", err) + } + + q := parsed.Query() + q.Set("filter", "2fa_disabled") + q.Set("per_page", "100") + parsed.RawQuery = q.Encode() + endpoint := parsed.String() for range maxPaginationPages { - page, nextURL, err := d.fetchMembersPage(ctx, url) + page, nextURL, err := d.fetchMembersPage(ctx, endpoint) if err != nil { return nil, err } @@ -221,20 +247,19 @@ func (d *GitHubDriver) fetchAll2FADisabledLogins(ctx context.Context) (map[strin return set, nil } - url = nextURL + endpoint = nextURL } return nil, fmt.Errorf("cannot list all github 2fa-disabled members: %w", ErrPaginationLimitReached) } func (d *GitHubDriver) fetchMembership(ctx context.Context, login string) (*githubMembership, error) { - url := fmt.Sprintf( - "https://api.github.com/orgs/%s/memberships/%s", - d.org, - login, - ) + endpoint, err := url.JoinPath("https://api.github.com", "orgs", d.org, "memberships", login) + if err != nil { + return nil, fmt.Errorf("cannot build github membership URL: %w", err) + } - 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 github membership request: %w", err) } @@ -263,9 +288,12 @@ func (d *GitHubDriver) fetchMembership(ctx context.Context, login string) (*gith } func (d *GitHubDriver) fetchUserProfile(ctx context.Context, login string) (*githubUserProfile, error) { - url := fmt.Sprintf("https://api.github.com/users/%s", login) + endpoint, err := url.JoinPath("https://api.github.com", "users", login) + if err != nil { + return nil, fmt.Errorf("cannot build github user profile URL: %w", err) + } - 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 github user profile request: %w", err) } diff --git a/pkg/accessreview/drivers/gitlab.go b/pkg/accessreview/drivers/gitlab.go index 0c24fd516..53430ed87 100644 --- a/pkg/accessreview/drivers/gitlab.go +++ b/pkg/accessreview/drivers/gitlab.go @@ -67,10 +67,20 @@ type gitlabMember struct { func (d *GitLabDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) { var records []AccountRecord - next := fmt.Sprintf( - "https://gitlab.com/api/v4/groups/%s/members/all?per_page=100", - url.PathEscape(d.groupID), - ) + u, err := url.JoinPath("https://gitlab.com", "api", "v4", "groups", d.groupID, "members", "all") + if err != nil { + return nil, fmt.Errorf("cannot build gitlab members URL: %w", err) + } + + parsed, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("cannot parse gitlab members URL: %w", err) + } + + q := parsed.Query() + q.Set("per_page", "100") + parsed.RawQuery = q.Encode() + next := parsed.String() for range maxPaginationPages { members, linkHeader, err := d.queryMembers(ctx, next) diff --git a/pkg/accessreview/drivers/gitlab_test.go b/pkg/accessreview/drivers/gitlab_test.go index b546cda48..4f9b80beb 100644 --- a/pkg/accessreview/drivers/gitlab_test.go +++ b/pkg/accessreview/drivers/gitlab_test.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.probo.inc/probo/pkg/coredata" ) diff --git a/pkg/accessreview/drivers/google_workspace.go b/pkg/accessreview/drivers/google_workspace.go index 6f681ca3f..e21e9a3f3 100644 --- a/pkg/accessreview/drivers/google_workspace.go +++ b/pkg/accessreview/drivers/google_workspace.go @@ -22,10 +22,9 @@ import ( "net/http" "time" + "go.probo.inc/probo/pkg/coredata" admin "google.golang.org/api/admin/directory/v1" "google.golang.org/api/option" - - "go.probo.inc/probo/pkg/coredata" ) // GoogleWorkspaceDriver fetches user accounts from Google Workspace diff --git a/pkg/accessreview/drivers/heroku.go b/pkg/accessreview/drivers/heroku.go index fe9937774..70ee30c99 100644 --- a/pkg/accessreview/drivers/heroku.go +++ b/pkg/accessreview/drivers/heroku.go @@ -72,7 +72,10 @@ type herokuTeamMember struct { func (d *HerokuDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) { var records []AccountRecord - endpoint := fmt.Sprintf("https://api.heroku.com/teams/%s/members", url.PathEscape(d.teamID)) + endpoint, err := url.JoinPath("https://api.heroku.com", "teams", d.teamID, "members") + if err != nil { + return nil, fmt.Errorf("cannot build heroku members URL: %w", err) + } rangeHeader := "" for range maxPaginationPages { diff --git a/pkg/accessreview/drivers/heroku_test.go b/pkg/accessreview/drivers/heroku_test.go index 17f97c6aa..78d7bbf7a 100644 --- a/pkg/accessreview/drivers/heroku_test.go +++ b/pkg/accessreview/drivers/heroku_test.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.probo.inc/probo/pkg/coredata" ) diff --git a/pkg/accessreview/drivers/microsoft_365.go b/pkg/accessreview/drivers/microsoft_365.go index 220b0287f..46ffc475a 100644 --- a/pkg/accessreview/drivers/microsoft_365.go +++ b/pkg/accessreview/drivers/microsoft_365.go @@ -248,7 +248,12 @@ func (d *Microsoft365Driver) listUsers(ctx context.Context) ([]microsoft365User, } func buildMicrosoft365UsersURL() (string, error) { - u, err := url.Parse(microsoft365GraphBaseURL + "/users") + endpoint, err := url.JoinPath(microsoft365GraphBaseURL, "users") + if err != nil { + return "", fmt.Errorf("cannot build graph users URL: %w", err) + } + + u, err := url.Parse(endpoint) if err != nil { return "", fmt.Errorf("cannot parse graph users URL: %w", err) } @@ -263,13 +268,16 @@ func buildMicrosoft365UsersURL() (string, error) { } func (d *Microsoft365Driver) listDirectoryRoles(ctx context.Context) ([]microsoft365DirectoryRole, error) { - url := fmt.Sprintf("%s/directoryRoles", microsoft365GraphBaseURL) + endpoint, err := url.JoinPath(microsoft365GraphBaseURL, "directoryRoles") + if err != nil { + return nil, fmt.Errorf("cannot build graph directory roles URL: %w", err) + } var all []microsoft365DirectoryRole for range microsoft365MaxPaginationOK { var page microsoft365RolesPage - if err := d.fetchJSON(ctx, url, &page); err != nil { + if err := d.fetchJSON(ctx, endpoint, &page); err != nil { return nil, err } @@ -278,20 +286,23 @@ func (d *Microsoft365Driver) listDirectoryRoles(ctx context.Context) ([]microsof return all, nil } - url = page.NextLink + endpoint = page.NextLink } return nil, fmt.Errorf("cannot list all microsoft 365 directory roles: %w", ErrPaginationLimitReached) } func (d *Microsoft365Driver) listRoleMembers(ctx context.Context, roleID string) ([]microsoft365RoleMember, error) { - url := fmt.Sprintf("%s/directoryRoles/%s/members", microsoft365GraphBaseURL, roleID) + endpoint, err := url.JoinPath(microsoft365GraphBaseURL, "directoryRoles", roleID, "members") + if err != nil { + return nil, fmt.Errorf("cannot build graph role members URL: %w", err) + } var all []microsoft365RoleMember for range microsoft365MaxPaginationOK { var page microsoft365MembersPage - if err := d.fetchJSON(ctx, url, &page); err != nil { + if err := d.fetchJSON(ctx, endpoint, &page); err != nil { return nil, err } @@ -300,7 +311,7 @@ func (d *Microsoft365Driver) listRoleMembers(ctx context.Context, roleID string) return all, nil } - url = page.NextLink + endpoint = page.NextLink } return nil, fmt.Errorf("cannot list all members of role %q: %w", roleID, ErrPaginationLimitReached) diff --git a/pkg/accessreview/drivers/name_resolver.go b/pkg/accessreview/drivers/name_resolver.go index eb4310125..3d4726ddf 100644 --- a/pkg/accessreview/drivers/name_resolver.go +++ b/pkg/accessreview/drivers/name_resolver.go @@ -22,11 +22,10 @@ import ( "net/http" "net/url" - admin "google.golang.org/api/admin/directory/v1" - "google.golang.org/api/option" - "go.probo.inc/probo/pkg/connector" "go.probo.inc/probo/pkg/coredata" + admin "google.golang.org/api/admin/directory/v1" + "google.golang.org/api/option" ) // NameResolver fetches the human-readable instance name from a provider @@ -204,12 +203,17 @@ func NewCloudflareNameResolver(httpClient *http.Client) NameResolver { } func (r *cloudflareNameResolver) ResolveInstanceName(ctx context.Context) (string, error) { - req, err := http.NewRequestWithContext( - ctx, - http.MethodGet, - "https://api.cloudflare.com/client/v4/accounts?page=1&per_page=1", - nil, - ) + cfURL, err := url.Parse("https://api.cloudflare.com/client/v4/accounts") + if err != nil { + return "", fmt.Errorf("cannot parse cloudflare accounts URL: %w", err) + } + + q := cfURL.Query() + q.Set("page", "1") + q.Set("per_page", "1") + cfURL.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, cfURL.String(), nil) if err != nil { return "", fmt.Errorf("cannot create cloudflare accounts request: %w", err) } @@ -300,9 +304,12 @@ func NewTallyNameResolver(httpClient *http.Client, organizationID string) NameRe } func (r *tallyNameResolver) ResolveInstanceName(ctx context.Context) (string, error) { - url := fmt.Sprintf("https://api.tally.so/organizations/%s", r.organizationID) + endpoint, err := url.JoinPath("https://api.tally.so", "organizations", r.organizationID) + if err != nil { + return "", fmt.Errorf("cannot build tally organization URL: %w", err) + } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return "", fmt.Errorf("cannot create tally organization request: %w", err) } @@ -484,9 +491,12 @@ func (r *sentryNameResolver) ResolveInstanceName(ctx context.Context) (string, e return "", nil } - url := fmt.Sprintf("https://sentry.io/api/0/organizations/%s/", r.orgSlug) + endpoint, err := url.JoinPath("https://sentry.io", "api", "0", "organizations", r.orgSlug) + if err != nil { + return "", fmt.Errorf("cannot build sentry organization URL: %w", err) + } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return "", fmt.Errorf("cannot create sentry organization request: %w", err) } @@ -525,9 +535,12 @@ func NewGitHubNameResolver(httpClient *http.Client, org string) NameResolver { } func (r *githubNameResolver) ResolveInstanceName(ctx context.Context) (string, error) { - url := fmt.Sprintf("https://api.github.com/orgs/%s", r.org) + endpoint, err := url.JoinPath("https://api.github.com", "orgs", r.org) + if err != nil { + return "", fmt.Errorf("cannot build github organization URL: %w", err) + } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { return "", fmt.Errorf("cannot create github organization request: %w", err) } @@ -639,7 +652,10 @@ func (r *gitlabNameResolver) ResolveInstanceName(ctx context.Context) (string, e return "", nil } - endpoint := fmt.Sprintf("https://gitlab.com/api/v4/groups/%s", url.PathEscape(r.groupID)) + endpoint, err := url.JoinPath("https://gitlab.com", "api", "v4", "groups", r.groupID) + if err != nil { + return "", fmt.Errorf("cannot build gitlab group URL: %w", err) + } req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { @@ -689,7 +705,10 @@ func (r *bitbucketNameResolver) ResolveInstanceName(ctx context.Context) (string return "", nil } - endpoint := fmt.Sprintf("https://api.bitbucket.org/2.0/workspaces/%s", url.PathEscape(r.workspace)) + endpoint, err := url.JoinPath("https://api.bitbucket.org", "2.0", "workspaces", r.workspace) + if err != nil { + return "", fmt.Errorf("cannot build bitbucket workspace URL: %w", err) + } req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { @@ -739,7 +758,10 @@ func (r *herokuNameResolver) ResolveInstanceName(ctx context.Context) (string, e return "", nil } - endpoint := fmt.Sprintf("https://api.heroku.com/teams/%s", url.PathEscape(r.teamID)) + endpoint, err := url.JoinPath("https://api.heroku.com", "teams", r.teamID) + if err != nil { + return "", fmt.Errorf("cannot build heroku team URL: %w", err) + } req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { @@ -799,7 +821,10 @@ func (r *asanaNameResolver) ResolveInstanceName(ctx context.Context) (string, er return "", nil } - endpoint := fmt.Sprintf("https://app.asana.com/api/1.0/workspaces/%s", url.PathEscape(r.workspaceGID)) + endpoint, err := url.JoinPath("https://app.asana.com", "api", "1.0", "workspaces", r.workspaceGID) + if err != nil { + return "", fmt.Errorf("cannot build asana workspace URL: %w", err) + } req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { @@ -846,7 +871,10 @@ func (r *netlifyNameResolver) ResolveInstanceName(ctx context.Context) (string, return "", nil } - endpoint := fmt.Sprintf("https://api.netlify.com/api/v1/accounts/%s", url.PathEscape(r.accountSlug)) + endpoint, err := url.JoinPath("https://api.netlify.com", "api", "v1", "accounts", r.accountSlug) + if err != nil { + return "", fmt.Errorf("cannot build netlify account URL: %w", err) + } req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { @@ -891,7 +919,10 @@ func (r *clickupNameResolver) ResolveInstanceName(ctx context.Context) (string, return "", nil } - endpoint := fmt.Sprintf("https://api.clickup.com/api/v2/team/%s", url.PathEscape(r.teamID)) + endpoint, err := url.JoinPath("https://api.clickup.com", "api", "v2", "team", r.teamID) + if err != nil { + return "", fmt.Errorf("cannot build clickup team URL: %w", err) + } req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) if err != nil { @@ -941,7 +972,10 @@ func (r *vercelNameResolver) ResolveInstanceName(ctx context.Context) (string, e return "", nil } - teamURL := fmt.Sprintf("https://api.vercel.com/v2/teams/%s", url.PathEscape(r.teamID)) + teamURL, err := url.JoinPath("https://api.vercel.com", "v2", "teams", r.teamID) + if err != nil { + return "", fmt.Errorf("cannot build vercel team URL: %w", err) + } teamReq, err := http.NewRequestWithContext(ctx, http.MethodGet, teamURL, nil) if err != nil { @@ -1107,12 +1141,16 @@ func NewMicrosoft365NameResolver(httpClient *http.Client) NameResolver { } func (r *microsoft365NameResolver) ResolveInstanceName(ctx context.Context) (string, error) { - req, err := http.NewRequestWithContext( - ctx, - http.MethodGet, - "https://graph.microsoft.com/v1.0/organization?$select=displayName,verifiedDomains", - nil, - ) + msURL, err := url.Parse("https://graph.microsoft.com/v1.0/organization") + if err != nil { + return "", fmt.Errorf("cannot parse microsoft 365 organization URL: %w", err) + } + + q := msURL.Query() + q.Set("$select", "displayName,verifiedDomains") + msURL.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, msURL.String(), nil) if err != nil { return "", fmt.Errorf("cannot create microsoft 365 organization request: %w", err) } diff --git a/pkg/accessreview/drivers/netlify.go b/pkg/accessreview/drivers/netlify.go index 66b4f1b5b..12c3b9573 100644 --- a/pkg/accessreview/drivers/netlify.go +++ b/pkg/accessreview/drivers/netlify.go @@ -56,10 +56,20 @@ type netlifyMember struct { func (d *NetlifyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) { var records []AccountRecord - next := fmt.Sprintf( - "https://api.netlify.com/api/v1/%s/members?per_page=100", - url.PathEscape(d.accountSlug), - ) + u, err := url.JoinPath("https://api.netlify.com", "api", "v1", d.accountSlug, "members") + if err != nil { + return nil, fmt.Errorf("cannot build netlify members URL: %w", err) + } + + parsed, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("cannot parse netlify members URL: %w", err) + } + + q := parsed.Query() + q.Set("per_page", "100") + parsed.RawQuery = q.Encode() + next := parsed.String() for range maxPaginationPages { members, linkHeader, err := d.queryMembers(ctx, next) diff --git a/pkg/accessreview/drivers/probo_memberships.go b/pkg/accessreview/drivers/probo_memberships.go index 96f77a0e3..208368b58 100644 --- a/pkg/accessreview/drivers/probo_memberships.go +++ b/pkg/accessreview/drivers/probo_memberships.go @@ -65,18 +65,21 @@ func (d *ProboMembershipsDriver) ListAccounts(ctx context.Context) ([]AccountRec isAdmin := role == string(coredata.MembershipRoleOwner) || role == string(coredata.MembershipRoleAdmin) createdAt := account.CreatedAt - records = append(records, AccountRecord{ - Email: account.Email, - FullName: account.FullName, - Role: role, - Active: new(account.State == string(coredata.ProfileStateActive)), - IsAdmin: isAdmin, - ExternalID: account.ID.String(), - CreatedAt: &createdAt, - MFAStatus: coredata.MFAStatusUnknown, - AuthMethod: coredata.AccessEntryAuthMethodUnknown, - AccountType: coredata.AccessEntryAccountTypeUser, - }) + records = append( + records, + AccountRecord{ + Email: account.Email, + FullName: account.FullName, + Role: role, + Active: new(account.State == string(coredata.ProfileStateActive)), + IsAdmin: isAdmin, + ExternalID: account.ID.String(), + CreatedAt: &createdAt, + MFAStatus: coredata.MFAStatusUnknown, + AuthMethod: coredata.AccessEntryAuthMethodUnknown, + AccountType: coredata.AccessEntryAccountTypeUser, + }, + ) } return nil diff --git a/pkg/accessreview/drivers/sentry.go b/pkg/accessreview/drivers/sentry.go index eb9e0b4b0..38e182b99 100644 --- a/pkg/accessreview/drivers/sentry.go +++ b/pkg/accessreview/drivers/sentry.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "time" "go.probo.inc/probo/pkg/coredata" @@ -108,10 +109,10 @@ func (d *SentryDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error var records []AccountRecord - nextURL := fmt.Sprintf( - "https://sentry.io/api/0/organizations/%s/members/", - orgSlug, - ) + nextURL, err := url.JoinPath("https://sentry.io", "api", "0", "organizations", orgSlug, "members") + if err != nil { + return nil, fmt.Errorf("cannot build sentry members URL: %w", err) + } for range maxPaginationPages { members, linkHeader, err := d.queryMembers(ctx, nextURL) diff --git a/pkg/accessreview/source_name_worker.go b/pkg/accessreview/source_name_worker.go index ed7b33d0a..29ef72911 100644 --- a/pkg/accessreview/source_name_worker.go +++ b/pkg/accessreview/source_name_worker.go @@ -87,7 +87,9 @@ func (h *sourceNameHandler) Claim(ctx context.Context) (coredata.AccessSource, e } func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessSource) error { - h.logger.InfoCtx(ctx, "syncing source name", + h.logger.InfoCtx( + ctx, + "syncing source name", log.String("source_id", source.ID.String()), log.String("current_name", source.Name), ) @@ -134,7 +136,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS }, ) if err != nil { - h.logger.ErrorCtx(ctx, "cannot load connector for source name sync", + h.logger.ErrorCtx( + ctx, + "cannot load connector for source name sync", log.String("source_id", source.ID.String()), log.Error(err), ) @@ -143,7 +147,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS } if resolver == nil { - h.logger.InfoCtx(ctx, "no name resolver for provider, keeping generic name", + h.logger.InfoCtx( + ctx, + "no name resolver for provider, keeping generic name", log.String("source_id", source.ID.String()), log.String("provider", dbConnector.Provider.String()), ) @@ -156,7 +162,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS instanceName, err := resolver.ResolveInstanceName(resolveCtx) if err != nil { - h.logger.ErrorCtx(ctx, "cannot resolve instance name", + h.logger.ErrorCtx( + ctx, + "cannot resolve instance name", log.String("source_id", source.ID.String()), log.String("provider", dbConnector.Provider.String()), log.Error(err), @@ -166,7 +174,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS } if instanceName == "" { - h.logger.InfoCtx(ctx, "instance name is empty, keeping generic name", + h.logger.InfoCtx( + ctx, + "instance name is empty, keeping generic name", log.String("source_id", source.ID.String()), log.String("provider", dbConnector.Provider.String()), ) @@ -177,7 +187,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS displayName := drivers.ProviderDisplayName(dbConnector.Provider) newName := displayName + " " + instanceName - h.logger.InfoCtx(ctx, "resolved source name", + h.logger.InfoCtx( + ctx, + "resolved source name", log.String("source_id", source.ID.String()), log.String("old_name", source.Name), log.String("new_name", newName), diff --git a/pkg/agent/run.go b/pkg/agent/run.go index a570e23ae..c5c09b0a7 100644 --- a/pkg/agent/run.go +++ b/pkg/agent/run.go @@ -95,8 +95,7 @@ func blockingCallLLM(ctx context.Context, agent *Agent, req *llm.ChatCompletionR // Some providers (e.g. Anthropic) require streaming for large // max_tokens or when thinking is enabled. Fall back to streaming // transparently when the blocking call returns ErrStreamingRequired. - var streamRequired *llm.ErrStreamingRequired - if !errors.As(err, &streamRequired) { + if _, ok := errors.AsType[*llm.ErrStreamingRequired](err); !ok { return nil, err } diff --git a/pkg/agent/tools/browser/download_pdf.go b/pkg/agent/tools/browser/download_pdf.go index 0fb999851..810cc2162 100644 --- a/pkg/agent/tools/browser/download_pdf.go +++ b/pkg/agent/tools/browser/download_pdf.go @@ -54,56 +54,70 @@ func DownloadPDFTool() agent.Tool { "Download a PDF document from a URL and extract its text content. Use this for DPAs, SOC 2 reports, privacy policies, and other documents hosted as PDFs.", func(ctx context.Context, p downloadPDFParams) (agent.ToolResult, error) { if err := validatePublicURL(p.URL); err != nil { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("URL not allowed: %s", err), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("URL not allowed: %s", err), + }, + ), nil } req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil) if err != nil { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("cannot create request: %s", err), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("cannot create request: %s", err), + }, + ), nil } resp, err := client.Do(req) if err != nil { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("cannot download PDF: %s", err), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("cannot download PDF: %s", err), + }, + ), nil } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("PDF download returned status %d", resp.StatusCode), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("PDF download returned status %d", resp.StatusCode), + }, + ), nil } // Read PDF into memory (max 20MB). body, err := io.ReadAll(io.LimitReader(resp.Body, 20*1024*1024)) if err != nil { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("cannot read PDF body: %s", err), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("cannot read PDF body: %s", err), + }, + ), nil } // Write to temp file for pdfcpu. tmpDir, err := os.MkdirTemp("", "pdf-extract-*") if err != nil { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("cannot create temp dir: %s", err), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("cannot create temp dir: %s", err), + }, + ), nil } defer func() { _ = os.RemoveAll(tmpDir) }() tmpFile := filepath.Join(tmpDir, "input.pdf") if err := os.WriteFile(tmpFile, body, 0o600); err != nil { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("cannot write temp file: %s", err), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("cannot write temp file: %s", err), + }, + ), nil } // Get page count. @@ -111,24 +125,30 @@ func DownloadPDFTool() agent.Tool { pageCount, err := api.PageCountFile(tmpFile) if err != nil { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("cannot read PDF: %s", err), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("cannot read PDF: %s", err), + }, + ), nil } // Extract content to output dir. outDir := filepath.Join(tmpDir, "out") if err := os.MkdirAll(outDir, 0o700); err != nil { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("cannot create output dir: %s", err), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("cannot create output dir: %s", err), + }, + ), nil } reader := bytes.NewReader(body) if err := api.ExtractContent(reader, outDir, "content", nil, conf); err != nil { - return agent.ResultJSON(downloadPDFResult{ - ErrorDetail: fmt.Sprintf("cannot extract PDF content: %s", err), - }), nil + return agent.ResultJSON( + downloadPDFResult{ + ErrorDetail: fmt.Sprintf("cannot extract PDF content: %s", err), + }, + ), nil } // Read all extracted content files. @@ -154,10 +174,12 @@ func DownloadPDFTool() agent.Tool { text = text[:maxTextLength] + "\n[... truncated]" } - return agent.ResultJSON(downloadPDFResult{ - Text: text, - PageCount: pageCount, - }), nil + return agent.ResultJSON( + downloadPDFResult{ + Text: text, + PageCount: pageCount, + }, + ), nil }, ) } diff --git a/pkg/agent/tools/browser/fetch_robots.go b/pkg/agent/tools/browser/fetch_robots.go index dd0599a0b..a82732bb6 100644 --- a/pkg/agent/tools/browser/fetch_robots.go +++ b/pkg/agent/tools/browser/fetch_robots.go @@ -19,6 +19,7 @@ import ( "context" "fmt" "net/http" + "net/url" "strings" "time" @@ -46,37 +47,49 @@ func FetchRobotsTxtTool() agent.Tool { "Fetch and parse the robots.txt file for a domain. Returns sitemap URLs and disallowed paths, which can reveal hidden pages the crawler might miss.", func(ctx context.Context, p robotsParams) (agent.ToolResult, error) { if err := validatePublicDomain(p.Domain); err != nil { - return agent.ResultJSON(robotsResult{ - Found: false, - ErrorDetail: fmt.Sprintf("domain not allowed: %s", err), - }), nil + return agent.ResultJSON( + robotsResult{ + Found: false, + ErrorDetail: fmt.Sprintf("domain not allowed: %s", err), + }, + ), nil } - u := "https://" + p.Domain + "/robots.txt" + u := &url.URL{ + Scheme: "https", + Host: p.Domain, + Path: "/robots.txt", + } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { - return agent.ResultJSON(robotsResult{ - Found: false, - ErrorDetail: fmt.Sprintf("cannot create request: %s", err), - }), nil + return agent.ResultJSON( + robotsResult{ + Found: false, + ErrorDetail: fmt.Sprintf("cannot create request: %s", err), + }, + ), nil } resp, err := client.Do(req) if err != nil { - return agent.ResultJSON(robotsResult{ - Found: false, - ErrorDetail: fmt.Sprintf("cannot fetch robots.txt: %s", err), - }), nil + return agent.ResultJSON( + robotsResult{ + Found: false, + ErrorDetail: fmt.Sprintf("cannot fetch robots.txt: %s", err), + }, + ), nil } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { - return agent.ResultJSON(robotsResult{ - Found: false, - ErrorDetail: fmt.Sprintf("robots.txt returned status %d", resp.StatusCode), - }), nil + return agent.ResultJSON( + robotsResult{ + Found: false, + ErrorDetail: fmt.Sprintf("robots.txt returned status %d", resp.StatusCode), + }, + ), nil } var result robotsResult diff --git a/pkg/agent/tools/browser/fetch_sitemap.go b/pkg/agent/tools/browser/fetch_sitemap.go index 5745479e1..73c5a6353 100644 --- a/pkg/agent/tools/browser/fetch_sitemap.go +++ b/pkg/agent/tools/browser/fetch_sitemap.go @@ -52,35 +52,43 @@ func FetchSitemapTool() agent.Tool { "Fetch and parse a sitemap XML file. Returns discovered URLs which can reveal pages not linked from the main navigation (trust centers, legal docs, status pages).", func(ctx context.Context, p sitemapParams) (agent.ToolResult, error) { if err := validatePublicURL(p.URL); err != nil { - return agent.ResultJSON(sitemapResult{ - Found: false, - ErrorDetail: fmt.Sprintf("URL not allowed: %s", err), - }), nil + return agent.ResultJSON( + sitemapResult{ + Found: false, + ErrorDetail: fmt.Sprintf("URL not allowed: %s", err), + }, + ), nil } req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil) if err != nil { - return agent.ResultJSON(sitemapResult{ - Found: false, - ErrorDetail: fmt.Sprintf("cannot create request: %s", err), - }), nil + return agent.ResultJSON( + sitemapResult{ + Found: false, + ErrorDetail: fmt.Sprintf("cannot create request: %s", err), + }, + ), nil } resp, err := client.Do(req) if err != nil { - return agent.ResultJSON(sitemapResult{ - Found: false, - ErrorDetail: fmt.Sprintf("cannot fetch sitemap: %s", err), - }), nil + return agent.ResultJSON( + sitemapResult{ + Found: false, + ErrorDetail: fmt.Sprintf("cannot fetch sitemap: %s", err), + }, + ), nil } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { - return agent.ResultJSON(sitemapResult{ - Found: false, - ErrorDetail: fmt.Sprintf("sitemap returned status %d", resp.StatusCode), - }), nil + return agent.ResultJSON( + sitemapResult{ + Found: false, + ErrorDetail: fmt.Sprintf("sitemap returned status %d", resp.StatusCode), + }, + ), nil } var reader io.Reader = resp.Body @@ -88,10 +96,12 @@ func FetchSitemapTool() agent.Tool { resp.Header.Get("Content-Encoding") == "gzip" { gz, err := gzip.NewReader(resp.Body) if err != nil { - return agent.ResultJSON(sitemapResult{ - Found: false, - ErrorDetail: fmt.Sprintf("cannot decompress gzipped sitemap: %s", err), - }), nil + return agent.ResultJSON( + sitemapResult{ + Found: false, + ErrorDetail: fmt.Sprintf("cannot decompress gzipped sitemap: %s", err), + }, + ), nil } defer func() { _ = gz.Close() }() @@ -104,10 +114,12 @@ func FetchSitemapTool() agent.Tool { urls, err := parseSitemapXML(reader) if err != nil { - return agent.ResultJSON(sitemapResult{ - Found: false, - ErrorDetail: fmt.Sprintf("cannot parse sitemap XML: %s", err), - }), nil + return agent.ResultJSON( + sitemapResult{ + Found: false, + ErrorDetail: fmt.Sprintf("cannot parse sitemap XML: %s", err), + }, + ), nil } result := sitemapResult{ diff --git a/pkg/agent/tools/browser/navigate.go b/pkg/agent/tools/browser/navigate.go index 535fa4ea2..db434d0a9 100644 --- a/pkg/agent/tools/browser/navigate.go +++ b/pkg/agent/tools/browser/navigate.go @@ -80,11 +80,13 @@ func NavigateToURLTool(b *Browser) agent.Tool { return agent.ResultError(b.classifyError(ctx, p.URL, err)), nil } - return agent.ResultJSON(navigateResult{ - Title: title, - Description: description, - FinalURL: finalURL, - }), nil + return agent.ResultJSON( + navigateResult{ + Title: title, + Description: description, + FinalURL: finalURL, + }, + ), nil }, ) } diff --git a/pkg/agent/tools/search/diff_documents.go b/pkg/agent/tools/search/diff_documents.go index c2edb768c..1e5177b03 100644 --- a/pkg/agent/tools/search/diff_documents.go +++ b/pkg/agent/tools/search/diff_documents.go @@ -64,10 +64,12 @@ func DiffDocumentsTool() agent.Tool { diff := computeDiff(linesA, linesB, labelA, labelB) if diff.tooLarge { - return agent.ResultJSON(diffResult{ - HasDifferences: true, - ErrorDetail: diff.output, - }), nil + return agent.ResultJSON( + diffResult{ + HasDifferences: true, + ErrorDetail: diff.output, + }, + ), nil } result := diffResult{ diff --git a/pkg/agent/tools/search/wayback.go b/pkg/agent/tools/search/wayback.go index 1f313b405..1c4eda9c3 100644 --- a/pkg/agent/tools/search/wayback.go +++ b/pkg/agent/tools/search/wayback.go @@ -65,9 +65,17 @@ func CheckWaybackTool() agent.Tool { var result waybackResult // Check availability. - availURL := "https://archive.org/wayback/available?url=" + url.QueryEscape(p.URL) + availURL, err := url.Parse("https://archive.org/wayback/available") + if err != nil { + result.ErrorDetail = fmt.Sprintf("cannot parse Wayback Machine URL: %s", err) + return agent.ResultJSON(result), nil + } - body, err := httpGet(ctx, client, availURL) + q := availURL.Query() + q.Set("url", p.URL) + availURL.RawQuery = q.Encode() + + body, err := httpGet(ctx, client, availURL.String()) if err != nil { result.ErrorDetail = fmt.Sprintf("cannot check Wayback Machine availability: %s", err) return agent.ResultJSON(result), nil diff --git a/pkg/agent/tools/security/cors.go b/pkg/agent/tools/security/cors.go index 43c49d461..7463a2053 100644 --- a/pkg/agent/tools/security/cors.go +++ b/pkg/agent/tools/security/cors.go @@ -68,9 +68,11 @@ func CheckCORSTool() agent.Tool { "Send a CORS preflight (OPTIONS) request to a URL with a given Origin and analyze the Access-Control-* response headers, flagging wildcard origins and origin reflection.", func(ctx context.Context, p corsParams) (agent.ToolResult, error) { if err := netcheck.ValidatePublicURL(p.URL); err != nil { - return agent.ResultJSON(corsResult{ - ErrorDetail: fmt.Sprintf("URL not allowed: %s", err), - }), nil + return agent.ResultJSON( + corsResult{ + ErrorDetail: fmt.Sprintf("URL not allowed: %s", err), + }, + ), nil } client := &http.Client{ @@ -87,9 +89,11 @@ func CheckCORSTool() agent.Tool { nil, ) if err != nil { - return agent.ResultJSON(corsResult{ - ErrorDetail: fmt.Sprintf("cannot build request: %s", err), - }), nil + return agent.ResultJSON( + corsResult{ + ErrorDetail: fmt.Sprintf("cannot build request: %s", err), + }, + ), nil } req.Header.Set("Origin", p.Origin) @@ -97,9 +101,11 @@ func CheckCORSTool() agent.Tool { resp, err := client.Do(req) if err != nil { - return agent.ResultJSON(corsResult{ - ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err), - }), nil + return agent.ResultJSON( + corsResult{ + ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err), + }, + ), nil } defer func() { _ = resp.Body.Close() }() diff --git a/pkg/agent/tools/security/csp.go b/pkg/agent/tools/security/csp.go index bd807d0bc..ff9852dbc 100644 --- a/pkg/agent/tools/security/csp.go +++ b/pkg/agent/tools/security/csp.go @@ -81,16 +81,20 @@ func AnalyzeCSPTool() agent.Tool { req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil) if err != nil { - return agent.ResultJSON(cspResult{ - ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", p.URL, err), - }), nil + return agent.ResultJSON( + cspResult{ + ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", p.URL, err), + }, + ), nil } resp, err := client.Do(req) if err != nil { - return agent.ResultJSON(cspResult{ - ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err), - }), nil + return agent.ResultJSON( + cspResult{ + ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err), + }, + ), nil } defer func() { _ = resp.Body.Close() }() diff --git a/pkg/agent/tools/security/dmarc.go b/pkg/agent/tools/security/dmarc.go index 3f621ebfc..b52501ca6 100644 --- a/pkg/agent/tools/security/dmarc.go +++ b/pkg/agent/tools/security/dmarc.go @@ -73,10 +73,12 @@ func CheckDMARCTool() agent.Tool { }, ) if err != nil { - return agent.ResultJSON(dmarcResult{ - Found: false, - ErrorDetail: fmt.Sprintf("cannot lookup DMARC record: %s", err), - }), nil + return agent.ResultJSON( + dmarcResult{ + Found: false, + ErrorDetail: fmt.Sprintf("cannot lookup DMARC record: %s", err), + }, + ), nil } for _, answer := range answers { diff --git a/pkg/agent/tools/security/dnssec.go b/pkg/agent/tools/security/dnssec.go index cb5f32b84..1ac33923b 100644 --- a/pkg/agent/tools/security/dnssec.go +++ b/pkg/agent/tools/security/dnssec.go @@ -61,10 +61,12 @@ func CheckDNSSECTool() agent.Tool { withDNSSEC(), ) if err != nil { - return agent.ResultJSON(dnssecResult{ - Enabled: false, - ErrorDetail: fmt.Sprintf("cannot query DNSKEY records: %s", err), - }), nil + return agent.ResultJSON( + dnssecResult{ + Enabled: false, + ErrorDetail: fmt.Sprintf("cannot query DNSKEY records: %s", err), + }, + ), nil } var ( diff --git a/pkg/agent/tools/security/headers.go b/pkg/agent/tools/security/headers.go index cd53ede40..1cb800fc6 100644 --- a/pkg/agent/tools/security/headers.go +++ b/pkg/agent/tools/security/headers.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "net/http" + "net/url" "strings" "time" @@ -79,9 +80,11 @@ func CheckSecurityHeadersTool() agent.Tool { "Check security-related HTTP headers for a URL (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Cross-Origin-*-Policy). Also checks if HTTP redirects to HTTPS.", func(ctx context.Context, p headersParams) (agent.ToolResult, error) { if err := netcheck.ValidatePublicURL(p.URL); err != nil { - return agent.ResultJSON(headersResult{ - ErrorDetail: fmt.Sprintf("URL not allowed: %s", err), - }), nil + return agent.ResultJSON( + headersResult{ + ErrorDetail: fmt.Sprintf("URL not allowed: %s", err), + }, + ), nil } client := &http.Client{ @@ -94,11 +97,19 @@ func CheckSecurityHeadersTool() agent.Tool { // First check the HTTP version to detect HTTP→HTTPS redirect. redirectsToHTTPS := false - httpURL := p.URL - if after, ok := strings.CutPrefix(httpURL, "https://"); ok { - httpURL = "http://" + after + parsedURL, err := url.Parse(p.URL) + if err != nil { + return agent.ResultJSON( + headersResult{ + ErrorDetail: fmt.Sprintf("cannot parse URL: %s", err), + }, + ), nil } + httpParsed := *parsedURL + httpParsed.Scheme = "http" + httpURL := httpParsed.String() + httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, httpURL, nil) if err == nil { httpResp, err := client.Do(httpReq) @@ -114,25 +125,28 @@ func CheckSecurityHeadersTool() agent.Tool { } // Now check the HTTPS version for the actual security headers. - httpsURL := p.URL - if after, ok := strings.CutPrefix(httpsURL, "http://"); ok { - httpsURL = "https://" + after - } + httpsParsed := *parsedURL + httpsParsed.Scheme = "https" + httpsURL := httpsParsed.String() followClient := &http.Client{Timeout: 10 * time.Second} httpsReq, err := http.NewRequestWithContext(ctx, http.MethodGet, httpsURL, nil) if err != nil { - return agent.ResultJSON(headersResult{ - ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", httpsURL, err), - }), nil + return agent.ResultJSON( + headersResult{ + ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", httpsURL, err), + }, + ), nil } resp, err := followClient.Do(httpsReq) if err != nil { - return agent.ResultJSON(headersResult{ - ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", httpsURL, err), - }), nil + return agent.ResultJSON( + headersResult{ + ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", httpsURL, err), + }, + ), nil } defer func() { _ = resp.Body.Close() }() diff --git a/pkg/agent/tools/security/hibp.go b/pkg/agent/tools/security/hibp.go index f2bb0c4bf..909c28582 100644 --- a/pkg/agent/tools/security/hibp.go +++ b/pkg/agent/tools/security/hibp.go @@ -61,34 +61,48 @@ func CheckBreachesTool() agent.Tool { func(ctx context.Context, p hibpParams) (agent.ToolResult, error) { client := &http.Client{Timeout: 10 * time.Second} - req, err := http.NewRequestWithContext( - ctx, - http.MethodGet, - "https://haveibeenpwned.com/api/v3/breaches?domain="+url.QueryEscape(p.Domain), - nil, - ) + hibpURL, err := url.Parse("https://haveibeenpwned.com/api/v3/breaches") if err != nil { - return agent.ResultJSON(hibpResult{ - ErrorDetail: fmt.Sprintf("cannot create request: %s", err), - }), nil + return agent.ResultJSON( + hibpResult{ + ErrorDetail: fmt.Sprintf("cannot parse HIBP URL: %s", err), + }, + ), nil + } + + q := hibpURL.Query() + q.Set("domain", p.Domain) + hibpURL.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, hibpURL.String(), nil) + if err != nil { + return agent.ResultJSON( + hibpResult{ + ErrorDetail: fmt.Sprintf("cannot create request: %s", err), + }, + ), nil } req.Header.Set("User-Agent", "Probo-Vendor-Assessment") resp, err := client.Do(req) if err != nil { - return agent.ResultJSON(hibpResult{ - ErrorDetail: fmt.Sprintf("cannot fetch breaches: %s", err), - }), nil + return agent.ResultJSON( + hibpResult{ + ErrorDetail: fmt.Sprintf("cannot fetch breaches: %s", err), + }, + ), nil } defer func() { _ = resp.Body.Close() }() body, err := io.ReadAll(resp.Body) if err != nil { - return agent.ResultJSON(hibpResult{ - ErrorDetail: fmt.Sprintf("cannot read response: %s", err), - }), nil + return agent.ResultJSON( + hibpResult{ + ErrorDetail: fmt.Sprintf("cannot read response: %s", err), + }, + ), nil } if resp.StatusCode == http.StatusNotFound { @@ -96,23 +110,29 @@ func CheckBreachesTool() agent.Tool { } if resp.StatusCode != http.StatusOK { - return agent.ResultJSON(hibpResult{ - ErrorDetail: fmt.Sprintf("HIBP API returned status %d", resp.StatusCode), - }), nil + return agent.ResultJSON( + hibpResult{ + ErrorDetail: fmt.Sprintf("HIBP API returned status %d", resp.StatusCode), + }, + ), nil } var breaches []breach if err := json.Unmarshal(body, &breaches); err != nil { - return agent.ResultJSON(hibpResult{ - ErrorDetail: fmt.Sprintf("cannot parse response: %s", err), - }), nil + return agent.ResultJSON( + hibpResult{ + ErrorDetail: fmt.Sprintf("cannot parse response: %s", err), + }, + ), nil } - return agent.ResultJSON(hibpResult{ - Found: len(breaches) > 0, - Count: len(breaches), - Breaches: breaches, - }), nil + return agent.ResultJSON( + hibpResult{ + Found: len(breaches) > 0, + Count: len(breaches), + Breaches: breaches, + }, + ), nil }, ) } diff --git a/pkg/agent/tools/security/spf.go b/pkg/agent/tools/security/spf.go index f37259ad5..ccb45789f 100644 --- a/pkg/agent/tools/security/spf.go +++ b/pkg/agent/tools/security/spf.go @@ -77,10 +77,12 @@ func CheckSPFTool() agent.Tool { }, ) if err != nil { - return agent.ResultJSON(spfResult{ - Found: false, - ErrorDetail: fmt.Sprintf("cannot lookup SPF record: %s", err), - }), nil + return agent.ResultJSON( + spfResult{ + Found: false, + ErrorDetail: fmt.Sprintf("cannot lookup SPF record: %s", err), + }, + ), nil } var spfRecords []string @@ -100,21 +102,25 @@ func CheckSPFTool() agent.Tool { } if len(spfRecords) > 1 { - return agent.ResultJSON(spfResult{ - Found: true, - ErrorDetail: fmt.Sprintf("multiple SPF records found (%d); this is an invalid configuration per RFC 7208", len(spfRecords)), - }), nil + return agent.ResultJSON( + spfResult{ + Found: true, + ErrorDetail: fmt.Sprintf("multiple SPF records found (%d); this is an invalid configuration per RFC 7208", len(spfRecords)), + }, + ), nil } if len(spfRecords) == 1 { record := spfRecords[0] - return agent.ResultJSON(spfResult{ - Found: true, - RawRecord: record, - Policy: parseSPFPolicy(record), - Mechanisms: record, - }), nil + return agent.ResultJSON( + spfResult{ + Found: true, + RawRecord: record, + Policy: parseSPFPolicy(record), + Mechanisms: record, + }, + ), nil } return agent.ResultJSON(spfResult{Found: false}), nil diff --git a/pkg/agent/tools/security/ssl.go b/pkg/agent/tools/security/ssl.go index 130a9dfe2..7c20e0aaa 100644 --- a/pkg/agent/tools/security/ssl.go +++ b/pkg/agent/tools/security/ssl.go @@ -66,10 +66,12 @@ func CheckSSLCertificateTool() agent.Tool { "Check the SSL/TLS certificate for a domain, returning issuer, expiry, protocol version, and validity.", func(ctx context.Context, p sslParams) (agent.ToolResult, error) { if err := netcheck.ValidatePublicDomain(p.Domain); err != nil { - return agent.ResultJSON(sslResult{ - Valid: false, - ErrorDetail: fmt.Sprintf("domain not allowed: %s", err), - }), nil + return agent.ResultJSON( + sslResult{ + Valid: false, + ErrorDetail: fmt.Sprintf("domain not allowed: %s", err), + }, + ), nil } // This is a certificate inspection tool: we intentionally @@ -96,20 +98,24 @@ func CheckSSLCertificateTool() agent.Tool { } if err != nil { - return agent.ResultJSON(sslResult{ - Valid: false, - ErrorDetail: err.Error(), - }), nil + return agent.ResultJSON( + sslResult{ + Valid: false, + ErrorDetail: err.Error(), + }, + ), nil } defer func() { _ = conn.Close() }() state := conn.ConnectionState() if len(state.PeerCertificates) == 0 { - return agent.ResultJSON(sslResult{ - Valid: false, - ErrorDetail: "no peer certificates", - }), nil + return agent.ResultJSON( + sslResult{ + Valid: false, + ErrorDetail: "no peer certificates", + }, + ), nil } cert := state.PeerCertificates[0] diff --git a/pkg/agent/tools/security/whois.go b/pkg/agent/tools/security/whois.go index 57b7327cd..6f4ca81eb 100644 --- a/pkg/agent/tools/security/whois.go +++ b/pkg/agent/tools/security/whois.go @@ -50,17 +50,21 @@ func CheckWhoisTool() agent.Tool { "Perform a WHOIS lookup on a domain to retrieve registration details including registrar, creation date, expiry date, registrant organization, and name servers.", func(ctx context.Context, p whoisParams) (agent.ToolResult, error) { if err := netcheck.ValidatePublicDomain(p.Domain); err != nil { - return agent.ResultJSON(whoisResult{ - ErrorDetail: fmt.Sprintf("domain not allowed: %s", err), - }), nil + return agent.ResultJSON( + whoisResult{ + ErrorDetail: fmt.Sprintf("domain not allowed: %s", err), + }, + ), nil } // Step 1: query IANA to find the referral WHOIS server. referral, err := queryWhois(ctx, "whois.iana.org:43", p.Domain) if err != nil { - return agent.ResultJSON(whoisResult{ - ErrorDetail: fmt.Sprintf("cannot query IANA WHOIS: %s", err), - }), nil + return agent.ResultJSON( + whoisResult{ + ErrorDetail: fmt.Sprintf("cannot query IANA WHOIS: %s", err), + }, + ), nil } whoisServer := parseWhoisField(referral, "refer") @@ -87,17 +91,21 @@ func CheckWhoisTool() agent.Tool { } if err := netcheck.ValidatePublicDomain(whoisHost); err != nil { - return agent.ResultJSON(whoisResult{ - ErrorDetail: fmt.Sprintf("WHOIS referral server not allowed: %s", err), - }), nil + return agent.ResultJSON( + whoisResult{ + ErrorDetail: fmt.Sprintf("WHOIS referral server not allowed: %s", err), + }, + ), nil } // Step 2: query the registrar's WHOIS server. raw, err := queryWhois(ctx, whoisServer, p.Domain) if err != nil { - return agent.ResultJSON(whoisResult{ - ErrorDetail: fmt.Sprintf("cannot query WHOIS server %s: %s", whoisServer, err), - }), nil + return agent.ResultJSON( + whoisResult{ + ErrorDetail: fmt.Sprintf("cannot query WHOIS server %s: %s", whoisServer, err), + }, + ), nil } result := parseWhoisResponse(raw) diff --git a/pkg/awsconfig/config.go b/pkg/awsconfig/config.go index 5dbedf0e9..0ae33ebfb 100644 --- a/pkg/awsconfig/config.go +++ b/pkg/awsconfig/config.go @@ -17,6 +17,7 @@ package awsconfig import ( "context" "net/http" + "net/url" "os" "time" @@ -86,7 +87,9 @@ func NewConfig(logger *log.Logger, httpClient *http.Client, opts Options) aws.Co }) ecsCredentialsURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") - ecsProvider := endpointcreds.New("http://169.254.170.2"+ecsCredentialsURI, + ecsEndpoint, _ := url.JoinPath("http://169.254.170.2", ecsCredentialsURI) + ecsProvider := endpointcreds.New( + ecsEndpoint, func(options *endpointcreds.Options) { options.HTTPClient = httpClient }, diff --git a/pkg/bootstrap/builder.go b/pkg/bootstrap/builder.go index 868afa987..91fbda44b 100644 --- a/pkg/bootstrap/builder.go +++ b/pkg/bootstrap/builder.go @@ -247,116 +247,146 @@ func (b *Builder) Build() (*probodconfig.FullConfig, error) { } if slackClientID := b.getEnv("CONNECTOR_SLACK_CLIENT_ID"); slackClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "SLACK", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: slackClientID, - ClientSecret: b.getEnv("CONNECTOR_SLACK_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "SLACK", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: slackClientID, + ClientSecret: b.getEnv("CONNECTOR_SLACK_CLIENT_SECRET"), + }, + RawSettings: map[string]any{ + "signing-secret": b.getEnv("CONNECTOR_SLACK_SIGNING_SECRET"), + }, }, - RawSettings: map[string]any{ - "signing-secret": b.getEnv("CONNECTOR_SLACK_SIGNING_SECRET"), - }, - }) + ) } if hubspotClientID := b.getEnv("CONNECTOR_HUBSPOT_CLIENT_ID"); hubspotClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "HUBSPOT", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: hubspotClientID, - ClientSecret: b.getEnv("CONNECTOR_HUBSPOT_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "HUBSPOT", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: hubspotClientID, + ClientSecret: b.getEnv("CONNECTOR_HUBSPOT_CLIENT_SECRET"), + }, }, - }) + ) } if docusignClientID := b.getEnv("CONNECTOR_DOCUSIGN_CLIENT_ID"); docusignClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "DOCUSIGN", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: docusignClientID, - ClientSecret: b.getEnv("CONNECTOR_DOCUSIGN_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "DOCUSIGN", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: docusignClientID, + ClientSecret: b.getEnv("CONNECTOR_DOCUSIGN_CLIENT_SECRET"), + }, }, - }) + ) } if notionClientID := b.getEnv("CONNECTOR_NOTION_CLIENT_ID"); notionClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "NOTION", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: notionClientID, - ClientSecret: b.getEnv("CONNECTOR_NOTION_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "NOTION", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: notionClientID, + ClientSecret: b.getEnv("CONNECTOR_NOTION_CLIENT_SECRET"), + }, }, - }) + ) } if githubClientID := b.getEnv("CONNECTOR_GITHUB_CLIENT_ID"); githubClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "GITHUB", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: githubClientID, - ClientSecret: b.getEnv("CONNECTOR_GITHUB_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "GITHUB", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: githubClientID, + ClientSecret: b.getEnv("CONNECTOR_GITHUB_CLIENT_SECRET"), + }, }, - }) + ) } if sentryClientID := b.getEnv("CONNECTOR_SENTRY_CLIENT_ID"); sentryClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "SENTRY", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: sentryClientID, - ClientSecret: b.getEnv("CONNECTOR_SENTRY_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "SENTRY", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: sentryClientID, + ClientSecret: b.getEnv("CONNECTOR_SENTRY_CLIENT_SECRET"), + }, }, - }) + ) } if intercomClientID := b.getEnv("CONNECTOR_INTERCOM_CLIENT_ID"); intercomClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "INTERCOM", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: intercomClientID, - ClientSecret: b.getEnv("CONNECTOR_INTERCOM_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "INTERCOM", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: intercomClientID, + ClientSecret: b.getEnv("CONNECTOR_INTERCOM_CLIENT_SECRET"), + }, }, - }) + ) } if brexClientID := b.getEnv("CONNECTOR_BREX_CLIENT_ID"); brexClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "BREX", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: brexClientID, - ClientSecret: b.getEnv("CONNECTOR_BREX_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "BREX", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: brexClientID, + ClientSecret: b.getEnv("CONNECTOR_BREX_CLIENT_SECRET"), + }, }, - }) + ) } if googleWorkspaceClientID := b.getEnv("CONNECTOR_GOOGLE_WORKSPACE_CLIENT_ID"); googleWorkspaceClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "GOOGLE_WORKSPACE", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: googleWorkspaceClientID, - ClientSecret: b.getEnv("CONNECTOR_GOOGLE_WORKSPACE_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "GOOGLE_WORKSPACE", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: googleWorkspaceClientID, + ClientSecret: b.getEnv("CONNECTOR_GOOGLE_WORKSPACE_CLIENT_SECRET"), + }, }, - }) + ) } if microsoft365ClientID := b.getEnv("CONNECTOR_MICROSOFT_365_CLIENT_ID"); microsoft365ClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "MICROSOFT_365", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: microsoft365ClientID, - ClientSecret: b.getEnv("CONNECTOR_MICROSOFT_365_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "MICROSOFT_365", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: microsoft365ClientID, + ClientSecret: b.getEnv("CONNECTOR_MICROSOFT_365_CLIENT_SECRET"), + }, }, - }) + ) } for _, provider := range []string{ @@ -374,28 +404,34 @@ func (b *Builder) Build() (*probodconfig.FullConfig, error) { continue } - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: provider, - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: clientID, - ClientSecret: b.getEnv("CONNECTOR_" + provider + "_CLIENT_SECRET"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: provider, + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: clientID, + ClientSecret: b.getEnv("CONNECTOR_" + provider + "_CLIENT_SECRET"), + }, }, - }) + ) } // Vercel needs the operator-supplied integration slug to resolve the // templated AuthURL ("https://vercel.com/integrations/{integration_slug}/new"). if vercelClientID := b.getEnv("CONNECTOR_VERCEL_CLIENT_ID"); vercelClientID != "" { - cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{ - Provider: "VERCEL", - Protocol: "oauth2", - RawConfig: probodconfig.ConnectorConfigOAuth2{ - ClientID: vercelClientID, - ClientSecret: b.getEnv("CONNECTOR_VERCEL_CLIENT_SECRET"), - IntegrationSlug: b.getEnv("CONNECTOR_VERCEL_INTEGRATION_SLUG"), + cfg.Probod.Connectors = append( + cfg.Probod.Connectors, + probodconfig.ConnectorConfig{ + Provider: "VERCEL", + Protocol: "oauth2", + RawConfig: probodconfig.ConnectorConfigOAuth2{ + ClientID: vercelClientID, + ClientSecret: b.getEnv("CONNECTOR_VERCEL_CLIENT_SECRET"), + IntegrationSlug: b.getEnv("CONNECTOR_VERCEL_INTEGRATION_SLUG"), + }, }, - }) + ) } return cfg, nil diff --git a/pkg/bootstrap/oauth2.go b/pkg/bootstrap/oauth2.go index 799957b92..af87b404e 100644 --- a/pkg/bootstrap/oauth2.go +++ b/pkg/bootstrap/oauth2.go @@ -32,10 +32,12 @@ func GenerateOAuth2SigningKey() (string, error) { return "", fmt.Errorf("generate RSA key: %w", err) } - keyPEM := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) + keyPEM := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }, + ) return string(keyPEM), nil } diff --git a/pkg/bootstrap/saml.go b/pkg/bootstrap/saml.go index 0b3be9cb5..8682af84d 100644 --- a/pkg/bootstrap/saml.go +++ b/pkg/bootstrap/saml.go @@ -62,15 +62,19 @@ func GenerateSAMLCertificate() (cert string, key string, err error) { return "", "", fmt.Errorf("create certificate: %w", err) } - certPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: certDER, - }) + certPEM := pem.EncodeToMemory( + &pem.Block{ + Type: "CERTIFICATE", + Bytes: certDER, + }, + ) - keyPEM := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privateKey), - }) + keyPEM := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }, + ) return string(certPEM), string(keyPEM), nil } diff --git a/pkg/certmanager/provisioner.go b/pkg/certmanager/provisioner.go index 55bdc5cd6..71e89f155 100644 --- a/pkg/certmanager/provisioner.go +++ b/pkg/certmanager/provisioner.go @@ -21,13 +21,12 @@ import ( "strings" "time" + "codeberg.org/miekg/dns" "go.gearno.de/kit/log" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/crypto/cipher" "go.probo.inc/probo/pkg/gid" - - "codeberg.org/miekg/dns" ) type ( diff --git a/pkg/cli/api/client.go b/pkg/cli/api/client.go index aa358a991..da5d10b90 100644 --- a/pkg/cli/api/client.go +++ b/pkg/cli/api/client.go @@ -245,10 +245,12 @@ func (c *Client) doUploadRequest( writer := multipart.NewWriter(&buf) // Part 1: operations - operationsJSON, err := json.Marshal(graphQLRequest{ - Query: query, - Variables: variables, - }) + operationsJSON, err := json.Marshal( + graphQLRequest{ + Query: query, + Variables: variables, + }, + ) if err != nil { return nil, fmt.Errorf("cannot marshal operations: %w", err) } @@ -258,9 +260,11 @@ func (c *Client) doUploadRequest( } // Part 2: map - mapJSON, err := json.Marshal(map[string][]string{ - "0": {varPath}, - }) + mapJSON, err := json.Marshal( + map[string][]string{ + "0": {varPath}, + }, + ) if err != nil { return nil, fmt.Errorf("cannot marshal map: %w", err) } diff --git a/pkg/coredata/connector.go b/pkg/coredata/connector.go index a240a6180..6aeb1a395 100644 --- a/pkg/coredata/connector.go +++ b/pkg/coredata/connector.go @@ -338,10 +338,12 @@ INSERT INTO connectors ( if c.Provider == ConnectorProviderSlack { if slackConn, ok := c.Connection.(*connector.SlackConnection); ok { - _ = c.SetSettings(&SlackConnectorSettings{ - Channel: slackConn.Settings.Channel, - ChannelID: slackConn.Settings.ChannelID, - }) + _ = c.SetSettings( + &SlackConnectorSettings{ + Channel: slackConn.Settings.Channel, + ChannelID: slackConn.Settings.ChannelID, + }, + ) } } @@ -554,10 +556,12 @@ WHERE if c.Provider == ConnectorProviderSlack { if slackConn, ok := c.Connection.(*connector.SlackConnection); ok { - _ = c.SetSettings(&SlackConnectorSettings{ - Channel: slackConn.Settings.Channel, - ChannelID: slackConn.Settings.ChannelID, - }) + _ = c.SetSettings( + &SlackConnectorSettings{ + Channel: slackConn.Settings.Channel, + ChannelID: slackConn.Settings.ChannelID, + }, + ) } } diff --git a/pkg/coredata/connector_test.go b/pkg/coredata/connector_test.go index 15505109d..4ba3f77fd 100644 --- a/pkg/coredata/connector_test.go +++ b/pkg/coredata/connector_test.go @@ -18,7 +18,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "go.probo.inc/probo/pkg/connector" ) diff --git a/pkg/coredata/control.go b/pkg/coredata/control.go index ef2eae1a1..54bcae562 100644 --- a/pkg/coredata/control.go +++ b/pkg/coredata/control.go @@ -822,8 +822,7 @@ VALUES ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_framework_ref_unique" { return ErrResourceAlreadyExists } @@ -892,8 +891,7 @@ WHERE %s _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_framework_ref_unique" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/control_document.go b/pkg/coredata/control_document.go index 958cd4648..15234b8d7 100644 --- a/pkg/coredata/control_document.go +++ b/pkg/coredata/control_document.go @@ -72,8 +72,7 @@ VALUES ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_policies_pkey" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/custom_domain.go b/pkg/coredata/custom_domain.go index 843642d3e..a494bc159 100644 --- a/pkg/coredata/custom_domain.go +++ b/pkg/coredata/custom_domain.go @@ -458,8 +458,7 @@ INSERT INTO custom_domains ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "custom_domains_domain_key" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/data_protection_impact_assessment.go b/pkg/coredata/data_protection_impact_assessment.go index 2104094ff..9b6e61bf6 100644 --- a/pkg/coredata/data_protection_impact_assessment.go +++ b/pkg/coredata/data_protection_impact_assessment.go @@ -453,8 +453,7 @@ INSERT INTO processing_activity_data_protection_impact_assessments ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "processing_activity_dpias_processing_activity_id_snapshot_id_uniq" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/document_default_approver.go b/pkg/coredata/document_default_approver.go index 3b2c5b640..2162e7515 100644 --- a/pkg/coredata/document_default_approver.go +++ b/pkg/coredata/document_default_approver.go @@ -130,13 +130,16 @@ WHEN NOT MATCHED BY SOURCE result := make(DocumentDefaultApprovers, 0, len(approverProfileIDs)) for _, profileID := range approverProfileIDs { - result = append(result, &DocumentDefaultApprover{ - DocumentID: documentID, - ApproverProfileID: profileID, - OrganizationID: organizationID, - CreatedAt: now, - UpdatedAt: now, - }) + result = append( + result, + &DocumentDefaultApprover{ + DocumentID: documentID, + ApproverProfileID: profileID, + OrganizationID: organizationID, + CreatedAt: now, + UpdatedAt: now, + }, + ) } *das = result diff --git a/pkg/coredata/document_version.go b/pkg/coredata/document_version.go index 94d9f7a5a..1f895a952 100644 --- a/pkg/coredata/document_version.go +++ b/pkg/coredata/document_version.go @@ -273,8 +273,7 @@ VALUES ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" { if pgErr.ConstraintName == "document_versions_document_id_major_minor_key" || pgErr.ConstraintName == "document_one_active_version_idx" { return ErrResourceAlreadyExists @@ -589,9 +588,13 @@ LIMIT 1 FOR UPDATE OF dv SKIP LOCKED; ` - rows, err := conn.Query(ctx, q, pgx.StrictNamedArgs{ - "max_pdf_attempts": maxAttempts, - }) + rows, err := conn.Query( + ctx, + q, + pgx.StrictNamedArgs{ + "max_pdf_attempts": maxAttempts, + }, + ) if err != nil { return fmt.Errorf("cannot query document versions: %w", err) } diff --git a/pkg/coredata/document_version_approval_decision.go b/pkg/coredata/document_version_approval_decision.go index 058f49059..21b1ab3c5 100644 --- a/pkg/coredata/document_version_approval_decision.go +++ b/pkg/coredata/document_version_approval_decision.go @@ -328,19 +328,22 @@ func (ds DocumentVersionApprovalDecisions) BulkInsert( rows := make([][]any, 0, len(ds)) for _, d := range ds { - rows = append(rows, []any{ - d.ID, - scope.GetTenantID(), - d.OrganizationID, - d.QuorumID, - d.ApproverID, - d.State, - d.Comment, - d.ElectronicSignatureID, - d.DecidedAt, - d.CreatedAt, - d.UpdatedAt, - }) + rows = append( + rows, + []any{ + d.ID, + scope.GetTenantID(), + d.OrganizationID, + d.QuorumID, + d.ApproverID, + d.State, + d.Comment, + d.ElectronicSignatureID, + d.DecidedAt, + d.CreatedAt, + d.UpdatedAt, + }, + ) } _, err := conn.CopyFrom( diff --git a/pkg/coredata/document_version_signature.go b/pkg/coredata/document_version_signature.go index 2ae903f7d..dd3735a29 100644 --- a/pkg/coredata/document_version_signature.go +++ b/pkg/coredata/document_version_signature.go @@ -216,8 +216,7 @@ INSERT INTO document_version_signatures ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "policy_version_signatures_policy_version_id_signed_by_key" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/email.go b/pkg/coredata/email.go index e33aacce3..f7fbe9af9 100644 --- a/pkg/coredata/email.go +++ b/pkg/coredata/email.go @@ -176,20 +176,23 @@ func (emails Emails) BulkInsert( rows := make([][]any, 0, len(emails)) for _, e := range emails { - rows = append(rows, []any{ - e.ID, - e.RecipientEmail, - e.RecipientName, - e.SenderName, - e.ReplyTo, - e.UnsubscribeURL, - e.MailingListUpdateID, - e.Subject, - e.TextBody, - e.HtmlBody, - e.CreatedAt, - e.UpdatedAt, - }) + rows = append( + rows, + []any{ + e.ID, + e.RecipientEmail, + e.RecipientName, + e.SenderName, + e.ReplyTo, + e.UnsubscribeURL, + e.MailingListUpdateID, + e.Subject, + e.TextBody, + e.HtmlBody, + e.CreatedAt, + e.UpdatedAt, + }, + ) } _, err := conn.CopyFrom( diff --git a/pkg/coredata/evidence.go b/pkg/coredata/evidence.go index 7cb7eb92f..52b99fffd 100644 --- a/pkg/coredata/evidence.go +++ b/pkg/coredata/evidence.go @@ -204,8 +204,7 @@ VALUES ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "evidences_reference_id_key" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/evidence_state.go b/pkg/coredata/evidence_state.go index 1a0dc6f12..00949dffc 100644 --- a/pkg/coredata/evidence_state.go +++ b/pkg/coredata/evidence_state.go @@ -20,26 +20,24 @@ import ( ) type ( - EvidenceState uint8 + EvidenceState string ) const ( - EvidenceStateRequested EvidenceState = iota - EvidenceStateFulfilled + EvidenceStateRequested EvidenceState = "REQUESTED" + EvidenceStateFulfilled EvidenceState = "FULFILLED" ) func (es EvidenceState) MarshalText() ([]byte, error) { - return []byte(es.String()), nil + return []byte(es), nil } func (es *EvidenceState) UnmarshalText(data []byte) error { - val := string(data) + val := EvidenceState(data) switch val { - case EvidenceStateRequested.String(): - *es = EvidenceStateRequested - case EvidenceStateFulfilled.String(): - *es = EvidenceStateFulfilled + case EvidenceStateRequested, EvidenceStateFulfilled: + *es = val default: return fmt.Errorf("invalid EvidenceState value: %q", val) } @@ -48,16 +46,7 @@ func (es *EvidenceState) UnmarshalText(data []byte) error { } func (es EvidenceState) String() string { - var val string - - switch es { - case EvidenceStateRequested: - val = "REQUESTED" - case EvidenceStateFulfilled: - val = "FULFILLED" - } - - return val + return string(es) } func (es *EvidenceState) Scan(value any) error { @@ -70,5 +59,5 @@ func (es *EvidenceState) Scan(value any) error { } func (es EvidenceState) Value() (driver.Value, error) { - return es.String(), nil + return string(es), nil } diff --git a/pkg/coredata/evidence_type.go b/pkg/coredata/evidence_type.go index 5ac14d37f..1e7ab716b 100644 --- a/pkg/coredata/evidence_type.go +++ b/pkg/coredata/evidence_type.go @@ -20,26 +20,24 @@ import ( ) type ( - EvidenceType uint8 + EvidenceType string ) const ( - EvidenceTypeFile EvidenceType = iota - EvidenceTypeLink + EvidenceTypeFile EvidenceType = "FILE" + EvidenceTypeLink EvidenceType = "LINK" ) func (et EvidenceType) MarshalText() ([]byte, error) { - return []byte(et.String()), nil + return []byte(et), nil } func (et *EvidenceType) UnmarshalText(data []byte) error { - val := string(data) + val := EvidenceType(data) switch val { - case EvidenceTypeFile.String(): - *et = EvidenceTypeFile - case EvidenceTypeLink.String(): - *et = EvidenceTypeLink + case EvidenceTypeFile, EvidenceTypeLink: + *et = val default: return fmt.Errorf("invalid EvidenceType value: %q", val) } @@ -48,16 +46,7 @@ func (et *EvidenceType) UnmarshalText(data []byte) error { } func (et EvidenceType) String() string { - var val string - - switch et { - case EvidenceTypeFile: - val = "FILE" - case EvidenceTypeLink: - val = "LINK" - } - - return val + return string(et) } func (et *EvidenceType) Scan(value any) error { @@ -70,5 +59,5 @@ func (et *EvidenceType) Scan(value any) error { } func (et EvidenceType) Value() (driver.Value, error) { - return et.String(), nil + return string(et), nil } diff --git a/pkg/coredata/file.go b/pkg/coredata/file.go index 97882c223..fdea489b9 100644 --- a/pkg/coredata/file.go +++ b/pkg/coredata/file.go @@ -232,8 +232,7 @@ VALUES ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "files_file_key_key" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/framework.go b/pkg/coredata/framework.go index ada707c04..195c45d80 100644 --- a/pkg/coredata/framework.go +++ b/pkg/coredata/framework.go @@ -336,8 +336,7 @@ VALUES ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "frameworks_org_ref_unique" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/identity.go b/pkg/coredata/identity.go index c8247d846..8e91b415d 100644 --- a/pkg/coredata/identity.go +++ b/pkg/coredata/identity.go @@ -204,8 +204,7 @@ VALUES ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && strings.Contains(pgErr.ConstraintName, "email_address") { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/measure.go b/pkg/coredata/measure.go index 32d944715..d7be86f08 100644 --- a/pkg/coredata/measure.go +++ b/pkg/coredata/measure.go @@ -23,10 +23,9 @@ import ( "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - - "go.gearno.de/kit/pg" ) type ( @@ -630,8 +629,7 @@ VALUES ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "mitigations_org_ref_unique" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/measure_document.go b/pkg/coredata/measure_document.go index 172bcc404..e57af91cc 100644 --- a/pkg/coredata/measure_document.go +++ b/pkg/coredata/measure_document.go @@ -72,8 +72,7 @@ VALUES ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "measures_documents_pkey" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/mesure_state.go b/pkg/coredata/mesure_state.go index bae236e5e..cb4db3453 100644 --- a/pkg/coredata/mesure_state.go +++ b/pkg/coredata/mesure_state.go @@ -20,16 +20,16 @@ import ( ) type ( - MeasureState uint8 + MeasureState string ) const ( - MeasureStateNotStarted MeasureState = iota - MeasureStateInProgress - MeasureStateNotApplicable - MeasureStateImplemented - MeasureStateUnknown - MeasureStateNotImplemented + MeasureStateNotStarted MeasureState = "NOT_STARTED" + MeasureStateInProgress MeasureState = "IN_PROGRESS" + MeasureStateNotApplicable MeasureState = "NOT_APPLICABLE" + MeasureStateImplemented MeasureState = "IMPLEMENTED" + MeasureStateUnknown MeasureState = "UNKNOWN" + MeasureStateNotImplemented MeasureState = "NOT_IMPLEMENTED" ) func MeasureStates() []MeasureState { @@ -44,25 +44,17 @@ func MeasureStates() []MeasureState { } func (ms MeasureState) MarshalText() ([]byte, error) { - return []byte(ms.String()), nil + return []byte(ms), nil } func (ms *MeasureState) UnmarshalText(data []byte) error { - val := string(data) + val := MeasureState(data) switch val { - case MeasureStateNotStarted.String(): - *ms = MeasureStateNotStarted - case MeasureStateInProgress.String(): - *ms = MeasureStateInProgress - case MeasureStateNotApplicable.String(): - *ms = MeasureStateNotApplicable - case MeasureStateImplemented.String(): - *ms = MeasureStateImplemented - case MeasureStateUnknown.String(): - *ms = MeasureStateUnknown - case MeasureStateNotImplemented.String(): - *ms = MeasureStateNotImplemented + case MeasureStateNotStarted, MeasureStateInProgress, + MeasureStateNotApplicable, MeasureStateImplemented, + MeasureStateUnknown, MeasureStateNotImplemented: + *ms = val default: return fmt.Errorf("invalid MeasureState value: %q", val) } @@ -71,24 +63,7 @@ func (ms *MeasureState) UnmarshalText(data []byte) error { } func (ms MeasureState) String() string { - var val string - - switch ms { - case MeasureStateNotStarted: - val = "NOT_STARTED" - case MeasureStateInProgress: - val = "IN_PROGRESS" - case MeasureStateNotApplicable: - val = "NOT_APPLICABLE" - case MeasureStateImplemented: - val = "IMPLEMENTED" - case MeasureStateUnknown: - val = "UNKNOWN" - case MeasureStateNotImplemented: - val = "NOT_IMPLEMENTED" - } - - return val + return string(ms) } func (ms *MeasureState) Scan(value any) error { @@ -101,5 +76,5 @@ func (ms *MeasureState) Scan(value any) error { } func (ms MeasureState) Value() (driver.Value, error) { - return ms.String(), nil + return string(ms), nil } diff --git a/pkg/coredata/saml_assertion.go b/pkg/coredata/saml_assertion.go index 7a0af0263..f6dda3332 100644 --- a/pkg/coredata/saml_assertion.go +++ b/pkg/coredata/saml_assertion.go @@ -51,8 +51,7 @@ VALUES (@id, @organization_id, @used_at, @expires_at) _, err := conn.Exec(ctx, query, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == "23505" && pgErr.ConstraintName == "iam_saml_assertions_pkey" { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok && pgErr.Code == "23505" && pgErr.ConstraintName == "iam_saml_assertions_pkey" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/saml_configuration.go b/pkg/coredata/saml_configuration.go index e8eb43510..8d72f4f0f 100644 --- a/pkg/coredata/saml_configuration.go +++ b/pkg/coredata/saml_configuration.go @@ -333,8 +333,7 @@ INSERT INTO iam_saml_configurations ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "idx_saml_config_domain_org_unique" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/scim_configuration.go b/pkg/coredata/scim_configuration.go index 039b0d4eb..298e09f72 100644 --- a/pkg/coredata/scim_configuration.go +++ b/pkg/coredata/scim_configuration.go @@ -268,8 +268,7 @@ INSERT INTO iam_scim_configurations ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "iam_scim_configurations_organization_unique" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/task.go b/pkg/coredata/task.go index 5bb06a179..2e12c6ba9 100644 --- a/pkg/coredata/task.go +++ b/pkg/coredata/task.go @@ -23,10 +23,9 @@ import ( "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - - "go.gearno.de/kit/pg" ) type ( @@ -252,8 +251,7 @@ RETURNING rank, priority_rank; err := conn.QueryRow(ctx, q, args).Scan(&t.Rank, &t.PriorityRank) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "tasks_reference_id_unique" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/token.go b/pkg/coredata/token.go index be2d09f73..a27ba8f7b 100644 --- a/pkg/coredata/token.go +++ b/pkg/coredata/token.go @@ -94,8 +94,7 @@ INSERT INTO iam_tokens( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "iam_tokens_hashed_value_unique" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/transfer_impact_assessment.go b/pkg/coredata/transfer_impact_assessment.go index f94fcdf58..d1f302585 100644 --- a/pkg/coredata/transfer_impact_assessment.go +++ b/pkg/coredata/transfer_impact_assessment.go @@ -452,8 +452,7 @@ INSERT INTO processing_activity_transfer_impact_assessments ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "processing_activity_tias_processing_activity_id_snapshot_id_uniq" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/trust_center.go b/pkg/coredata/trust_center.go index cf7888e3d..8fe219458 100644 --- a/pkg/coredata/trust_center.go +++ b/pkg/coredata/trust_center.go @@ -327,8 +327,7 @@ INSERT INTO trust_centers ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "trust_centers_slug_key" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/trust_center_access.go b/pkg/coredata/trust_center_access.go index 4a002ea5b..3a8094f0f 100644 --- a/pkg/coredata/trust_center_access.go +++ b/pkg/coredata/trust_center_access.go @@ -208,8 +208,7 @@ INSERT INTO trust_center_accesses ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "trust_center_accesses_trust_center_id_email_key" { return ErrResourceAlreadyExists } diff --git a/pkg/coredata/trust_center_document_access.go b/pkg/coredata/trust_center_document_access.go index 53d55ccd5..b186cc2e0 100644 --- a/pkg/coredata/trust_center_document_access.go +++ b/pkg/coredata/trust_center_document_access.go @@ -271,8 +271,7 @@ INSERT INTO trust_center_document_accesses ( _, err := conn.Exec(ctx, q, args) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" { switch pgErr.ConstraintName { case "trust_center_document_accesse_trust_center_access_id_docume_key", diff --git a/pkg/coredata/trust_center_reference.go b/pkg/coredata/trust_center_reference.go index 731a0e0b6..72a9bc510 100644 --- a/pkg/coredata/trust_center_reference.go +++ b/pkg/coredata/trust_center_reference.go @@ -171,8 +171,7 @@ RETURNING rank; err := conn.QueryRow(ctx, q, args).Scan(&t.Rank) if err != nil { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { if pgErr.Code == "23505" && pgErr.ConstraintName == "trust_center_references_trust_center_id_rank_key" { return ErrResourceAlreadyExists } diff --git a/pkg/esign/completion_certificate_worker.go b/pkg/esign/completion_certificate_worker.go index 09b29ac29..9afc5a0f8 100644 --- a/pkg/esign/completion_certificate_worker.go +++ b/pkg/esign/completion_certificate_worker.go @@ -25,12 +25,11 @@ import ( "go.gearno.de/kit/pg" "go.gearno.de/kit/worker" "go.gearno.de/x/ref" + emails "go.probo.inc/probo/packages/emails" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/filemanager" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/mail" - - emails "go.probo.inc/probo/packages/emails" ) // EmailPresenterConfigFunc resolves the emails.PresenterConfig for the diff --git a/pkg/iam/auth_service.go b/pkg/iam/auth_service.go index 4bb8f077a..cda239131 100644 --- a/pkg/iam/auth_service.go +++ b/pkg/iam/auth_service.go @@ -646,8 +646,7 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques func (s AuthService) GetMagicLinkEmail(ctx context.Context, tokenString string) (mail.Addr, error) { payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString) if err != nil { - var errExpired *statelesstoken.ErrExpiredToken - if errors.As(err, &errExpired) { + if _, ok := errors.AsType[*statelesstoken.ErrExpiredToken](err); ok { return mail.Nil, NewExpiredTokenError() } @@ -666,8 +665,7 @@ func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, tokenString s payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString) if err != nil { - var errExpired *statelesstoken.ErrExpiredToken - if errors.As(err, &errExpired) { + if _, ok := errors.AsType[*statelesstoken.ErrExpiredToken](err); ok { return nil, nil, nil, NewExpiredTokenError() } diff --git a/pkg/iam/authorizer.go b/pkg/iam/authorizer.go index ef4766a1d..911211de6 100644 --- a/pkg/iam/authorizer.go +++ b/pkg/iam/authorizer.go @@ -101,12 +101,11 @@ func (a *Authorizer) authorize(ctx context.Context, tx pg.Tx, params AuthorizePa *params.Session, membership.ID, ); err != nil { - var ( - errSessionNotFound *ErrSessionNotFound - errSessionExpired *ErrSessionExpired - ) + if _, ok := errors.AsType[*ErrSessionNotFound](err); ok { + return NewAssumptionRequiredError(params.Principal, membership.ID) + } - if errors.As(err, &errSessionNotFound) || errors.As(err, &errSessionExpired) { + if _, ok := errors.AsType[*ErrSessionExpired](err); ok { return NewAssumptionRequiredError(params.Principal, membership.ID) } diff --git a/pkg/iam/policy_set_test.go b/pkg/iam/policy_set_test.go index b9573580a..ff959a41a 100644 --- a/pkg/iam/policy_set_test.go +++ b/pkg/iam/policy_set_test.go @@ -19,7 +19,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.probo.inc/probo/pkg/iam/policy" ) diff --git a/pkg/iam/scim/bridge/provider/googleworkspace/provider.go b/pkg/iam/scim/bridge/provider/googleworkspace/provider.go index e5ec58cb2..c44b2379e 100644 --- a/pkg/iam/scim/bridge/provider/googleworkspace/provider.go +++ b/pkg/iam/scim/bridge/provider/googleworkspace/provider.go @@ -23,11 +23,10 @@ import ( "net/http" "strings" - admin "google.golang.org/api/admin/directory/v1" - "google.golang.org/api/option" - scimclient "go.probo.inc/probo/pkg/iam/scim/bridge/client" "go.probo.inc/probo/pkg/iam/scim/bridge/provider" + admin "google.golang.org/api/admin/directory/v1" + "google.golang.org/api/option" ) var _ provider.Provider = (*Provider)(nil) diff --git a/pkg/llm/anthropic/provider.go b/pkg/llm/anthropic/provider.go index f0f242309..0613e1398 100644 --- a/pkg/llm/anthropic/provider.go +++ b/pkg/llm/anthropic/provider.go @@ -396,8 +396,8 @@ func mapError(err error) error { return &llm.ErrStreamingRequired{Err: err} } - var apiErr *anthropic.Error - if !errors.As(err, &apiErr) { + apiErr, ok := errors.AsType[*anthropic.Error](err) + if !ok { return err } diff --git a/pkg/llm/bedrock/provider.go b/pkg/llm/bedrock/provider.go index 8f3dd4767..e4830d80d 100644 --- a/pkg/llm/bedrock/provider.go +++ b/pkg/llm/bedrock/provider.go @@ -329,9 +329,8 @@ func mapStopReason(reason types.StopReason) llm.FinishReason { } func mapError(err error) error { - var respErr *smithyhttp.ResponseError - if !errors.As(err, &respErr) { - // Check for common error types by message content. + respErr, ok := errors.AsType[*smithyhttp.ResponseError](err) + if !ok { msg := err.Error() if strings.Contains(msg, "throttling") || strings.Contains(msg, "ThrottlingException") { return &llm.ErrRateLimit{Err: err} diff --git a/pkg/llm/chat.go b/pkg/llm/chat.go index 56e2af163..b050b425b 100644 --- a/pkg/llm/chat.go +++ b/pkg/llm/chat.go @@ -206,10 +206,13 @@ func (a *StreamAccumulator) Response() *ChatCompletionResponse { var parts []Part if thinking := a.thinking.String(); thinking != "" { - parts = append(parts, ThinkingPart{ - Text: thinking, - Signature: a.thinkingSignature, - }) + parts = append( + parts, + ThinkingPart{ + Text: thinking, + Signature: a.thinkingSignature, + }, + ) } parts = append(parts, TextPart{Text: a.content.String()}) diff --git a/pkg/llm/openai/provider.go b/pkg/llm/openai/provider.go index 37a0e3cb4..8dab02acd 100644 --- a/pkg/llm/openai/provider.go +++ b/pkg/llm/openai/provider.go @@ -213,9 +213,10 @@ func buildMessages(messages []llm.Message) []openai.ChatCompletionMessageParamUn case llm.ImagePart: parts = append( parts, - openai.ImageContentPart(openai.ChatCompletionContentPartImageImageURLParam{ - URL: p.URL, - }, + openai.ImageContentPart( + openai.ChatCompletionContentPartImageImageURLParam{ + URL: p.URL, + }, ), ) case llm.FilePart: @@ -381,8 +382,8 @@ func mapFinishReason(reason string) llm.FinishReason { } func mapError(err error) error { - var apiErr *openai.Error - if !errors.As(err, &apiErr) { + apiErr, ok := errors.AsType[*openai.Error](err) + if !ok { return err } @@ -513,9 +514,11 @@ func isReasoningModel(model string) bool { func buildFilePart(p llm.FilePart) openai.ChatCompletionContentPartUnionParam { switch { case strings.HasPrefix(p.MimeType, "image/"): - return openai.ImageContentPart(openai.ChatCompletionContentPartImageImageURLParam{ - URL: fmt.Sprintf("data:%s;base64,%s", p.MimeType, p.Data), - }) + return openai.ImageContentPart( + openai.ChatCompletionContentPartImageImageURLParam{ + URL: fmt.Sprintf("data:%s;base64,%s", p.MimeType, p.Data), + }, + ) case strings.HasPrefix(p.MimeType, "text/"): decoded, err := base64.StdEncoding.DecodeString(p.Data) if err != nil { @@ -524,9 +527,11 @@ func buildFilePart(p llm.FilePart) openai.ChatCompletionContentPartUnionParam { return openai.TextContentPart(fmt.Sprintf("File: %s\n\n%s", p.Filename, string(decoded))) default: - return openai.FileContentPart(openai.ChatCompletionContentPartFileFileParam{ - FileData: param.NewOpt(fmt.Sprintf("data:%s;base64,%s", p.MimeType, p.Data)), - Filename: param.NewOpt(p.Filename), - }) + return openai.FileContentPart( + openai.ChatCompletionContentPartFileFileParam{ + FileData: param.NewOpt(fmt.Sprintf("data:%s;base64,%s", p.MimeType, p.Data)), + Filename: param.NewOpt(p.Filename), + }, + ) } } diff --git a/pkg/probo/file_service.go b/pkg/probo/file_service.go index 35e4d958f..aa394358a 100644 --- a/pkg/probo/file_service.go +++ b/pkg/probo/file_service.go @@ -104,7 +104,8 @@ func (s FileService) UploadAndSaveFile( ctx context.Context, fileValidator *filevalidation.FileValidator, s3Metadata map[string]string, - req *FileUpload) (*coredata.File, error) { + req *FileUpload, +) (*coredata.File, error) { objectKey, err := uuid.NewV7() if err != nil { return nil, fmt.Errorf("cannot generate object key: %w", err) diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go index 9349708c4..b4343cd58 100644 --- a/pkg/probod/probod.go +++ b/pkg/probod/probod.go @@ -29,9 +29,6 @@ import ( "sync" "time" - "go.probo.inc/probo/packages/emails" - pemutil "go.probo.inc/probo/pkg/crypto/pem" - "github.com/aws/aws-sdk-go-v2/service/s3" proxyproto "github.com/pires/go-proxyproto" "github.com/prometheus/client_golang/prometheus" @@ -43,6 +40,7 @@ import ( "go.gearno.de/kit/unit" "go.gearno.de/kit/worker" "go.opentelemetry.io/otel/trace" + "go.probo.inc/probo/packages/emails" "go.probo.inc/probo/pkg/accessreview" "go.probo.inc/probo/pkg/awsconfig" "go.probo.inc/probo/pkg/baseurl" @@ -53,6 +51,7 @@ import ( "go.probo.inc/probo/pkg/crypto/cipher" "go.probo.inc/probo/pkg/crypto/keys" "go.probo.inc/probo/pkg/crypto/passwdhash" + pemutil "go.probo.inc/probo/pkg/crypto/pem" "go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/evidencedescriber" "go.probo.inc/probo/pkg/file" @@ -379,11 +378,14 @@ func (impl *Implm) Run( hasActive = true } - oauth2SigningKeys = append(oauth2SigningKeys, oauth2server.SigningKey{ - PrivateKey: rsaKey, - KID: kid, - Active: keyCfg.Active, - }) + oauth2SigningKeys = append( + oauth2SigningKeys, + oauth2server.SigningKey{ + PrivateKey: rsaKey, + KID: kid, + Active: keyCfg.Active, + }, + ) } if !hasActive { @@ -1112,10 +1114,9 @@ func (impl *Implm) runTrustCenterServer( cert, err := certSelector.GetCertificate(hello) // Silently reject connections without SNI (load balancers, health checks, scanners) if err != nil { - var noSNIErr *certmanager.NoSNIError - if errors.As(err, &noSNIErr) { - return nil, nil - } + if _, ok := errors.AsType[*certmanager.NoSNIError](err); ok { + return nil, nil + } if errors.Is(err, coredata.ErrResourceNotFound) { return nil, nil diff --git a/pkg/prosemirror/markdown_converter.go b/pkg/prosemirror/markdown_converter.go index caed089cb..da11325e3 100644 --- a/pkg/prosemirror/markdown_converter.go +++ b/pkg/prosemirror/markdown_converter.go @@ -635,11 +635,14 @@ func (c *converter) convertTableCells(row ast.Node, cellType NodeType) ([]Node, return nil, fmt.Errorf("cannot marshal table cell attrs: %w", err) } - cells = append(cells, Node{ - Type: cellType, - Attrs: attrs, - Content: content, - }) + cells = append( + cells, + Node{ + Type: cellType, + Attrs: attrs, + Content: content, + }, + ) } return cells, nil diff --git a/pkg/server/api/authn/api_key_middleware.go b/pkg/server/api/authn/api_key_middleware.go index ddda36d85..efaae897d 100644 --- a/pkg/server/api/authn/api_key_middleware.go +++ b/pkg/server/api/authn/api_key_middleware.go @@ -62,32 +62,31 @@ func NewAPIKeyMiddleware(svc *iam.Service, tokenSecret string) func(next http.Ha return } - apiKey, err := svc.APIKeyService.GetAPIKey(ctx, keyID) - if err != nil { - var ( - errPersonalAPIKeyNotFound *iam.ErrPersonalAPIKeyNotFound - errPersonalAPIKeyExpired *iam.ErrPersonalAPIKeyExpired - ) - - if errors.As(err, &errPersonalAPIKeyNotFound) || errors.As(err, &errPersonalAPIKeyExpired) { - next.ServeHTTP(w, r) - return - } - - panic(fmt.Errorf("cannot get personal API key: %w", err)) + apiKey, err := svc.APIKeyService.GetAPIKey(ctx, keyID) + if err != nil { + if _, ok := errors.AsType[*iam.ErrPersonalAPIKeyNotFound](err); ok { + next.ServeHTTP(w, r) + return } - identity, err := svc.AccountService.GetIdentity(ctx, apiKey.IdentityID) - if err != nil { - var errIdentityNotFound *iam.ErrIdentityNotFound - if errors.As(err, &errIdentityNotFound) { - next.ServeHTTP(w, r) - return - } - - panic(fmt.Errorf("cannot get identity: %w", err)) + if _, ok := errors.AsType[*iam.ErrPersonalAPIKeyExpired](err); ok { + next.ServeHTTP(w, r) + return } + panic(fmt.Errorf("cannot get personal API key: %w", err)) + } + + identity, err := svc.AccountService.GetIdentity(ctx, apiKey.IdentityID) + if err != nil { + if _, ok := errors.AsType[*iam.ErrIdentityNotFound](err); ok { + next.ServeHTTP(w, r) + return + } + + panic(fmt.Errorf("cannot get identity: %w", err)) + } + ctx = ContextWithAPIKey(ctx, apiKey) ctx = ContextWithIdentity(ctx, identity) diff --git a/pkg/server/api/authn/session_middleware.go b/pkg/server/api/authn/session_middleware.go index 8e2d5c15b..4295d8481 100644 --- a/pkg/server/api/authn/session_middleware.go +++ b/pkg/server/api/authn/session_middleware.go @@ -65,36 +65,37 @@ func NewSessionMiddleware(svc *iam.Service, cookieConfig securecookie.Config) fu return } - session, err := svc.SessionService.GetSession(ctx, sessionID) - if err != nil { - var ( - errSessionNotFound *iam.ErrSessionNotFound - errSessionExpired *iam.ErrSessionExpired - ) + session, err := svc.SessionService.GetSession(ctx, sessionID) + if err != nil { + if _, ok := errors.AsType[*iam.ErrSessionNotFound](err); ok { + securecookie.Clear(w, cookieConfig) + next.ServeHTTP(w, r) - if errors.As(err, &errSessionNotFound) || errors.As(err, &errSessionExpired) { - securecookie.Clear(w, cookieConfig) - next.ServeHTTP(w, r) - - return - } - - panic(fmt.Errorf("cannot get session: %w", err)) + return } - identity, err := svc.AccountService.GetIdentity(ctx, session.IdentityID) - if err != nil { - var errIdentityNotFound *iam.ErrIdentityNotFound - if errors.As(err, &errIdentityNotFound) { - securecookie.Clear(w, cookieConfig) - next.ServeHTTP(w, r) + if _, ok := errors.AsType[*iam.ErrSessionExpired](err); ok { + securecookie.Clear(w, cookieConfig) + next.ServeHTTP(w, r) - return - } - - panic(fmt.Errorf("cannot get identity: %w", err)) + return } + panic(fmt.Errorf("cannot get session: %w", err)) + } + + identity, err := svc.AccountService.GetIdentity(ctx, session.IdentityID) + if err != nil { + if _, ok := errors.AsType[*iam.ErrIdentityNotFound](err); ok { + securecookie.Clear(w, cookieConfig) + next.ServeHTTP(w, r) + + return + } + + panic(fmt.Errorf("cannot get identity: %w", err)) + } + userAgent := r.UserAgent() // TODO: will work well when no layer 7 proxy is in front of the server var ipAddress net.IP diff --git a/pkg/server/api/authz/authorization.go b/pkg/server/api/authz/authorization.go index 13b5c9979..2b696e682 100644 --- a/pkg/server/api/authz/authorization.go +++ b/pkg/server/api/authz/authorization.go @@ -79,13 +79,11 @@ func NewAuthorizeFunc( } if err := svc.Authorizer.Authorize(ctx, params); err != nil { - var errAssumptionRequired *iam.ErrAssumptionRequired - if errors.As(err, &errAssumptionRequired) { + if _, ok := errors.AsType[*iam.ErrAssumptionRequired](err); ok { return gqlutils.AssumptionRequired(ctx, err) } - var errInsufficientPermissions *iam.ErrInsufficientPermissions - if errors.As(err, &errInsufficientPermissions) { + if _, ok := errors.AsType[*iam.ErrInsufficientPermissions](err); ok { return gqlutils.Forbidden(ctx, err) } diff --git a/pkg/server/api/connect/v1/base_resolvers.go b/pkg/server/api/connect/v1/base_resolvers.go index c9044e063..041a3e4cf 100644 --- a/pkg/server/api/connect/v1/base_resolvers.go +++ b/pkg/server/api/connect/v1/base_resolvers.go @@ -152,23 +152,27 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error node, err := loadNode(ctx, id) if err != nil { - var ( - errOrganizationNotFound *iam.ErrOrganizationNotFound - errIdentityNotFound *iam.ErrIdentityNotFound - errSessionNotFound *iam.ErrSessionNotFound - errProfileNotFound *iam.ErrProfileNotFound - errMembershipNotFound *iam.ErrMembershipNotFound - errInvitationNotFound *iam.ErrInvitationNotFound + if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok { + return nil, gqlutils.NotFound(ctx, err) + } - isNotFoundErr = errors.As(err, &errOrganizationNotFound) || - errors.As(err, &errIdentityNotFound) || - errors.As(err, &errSessionNotFound) || - errors.As(err, &errProfileNotFound) || - errors.As(err, &errMembershipNotFound) || - errors.As(err, &errInvitationNotFound) - ) + if _, ok := errors.AsType[*iam.ErrIdentityNotFound](err); ok { + return nil, gqlutils.NotFound(ctx, err) + } - if isNotFoundErr { + if _, ok := errors.AsType[*iam.ErrSessionNotFound](err); ok { + return nil, gqlutils.NotFound(ctx, err) + } + + if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); ok { + return nil, gqlutils.NotFound(ctx, err) + } + + if _, ok := errors.AsType[*iam.ErrMembershipNotFound](err); ok { + return nil, gqlutils.NotFound(ctx, err) + } + + if _, ok := errors.AsType[*iam.ErrInvitationNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) } diff --git a/pkg/server/api/connect/v1/invitation_resolvers.go b/pkg/server/api/connect/v1/invitation_resolvers.go index 2f94d8fbf..8dd04d76a 100644 --- a/pkg/server/api/connect/v1/invitation_resolvers.go +++ b/pkg/server/api/connect/v1/invitation_resolvers.go @@ -36,16 +36,11 @@ func (r *mutationResolver) InviteUser(ctx context.Context, input types.InviteUse }, ) if err != nil { - var ( - errOrganizationNotFound *iam.ErrOrganizationNotFound - errUserAlreadyExists *iam.ErrUserAlreadyExists - ) - - if errors.As(err, &errOrganizationNotFound) { + if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) } - if errors.As(err, &errUserAlreadyExists) { + if _, ok := errors.AsType[*iam.ErrUserAlreadyExists](err); ok { return nil, gqlutils.Conflict(ctx, err) } diff --git a/pkg/server/api/connect/v1/membership_resolvers.go b/pkg/server/api/connect/v1/membership_resolvers.go index 0415d8319..32c0e553f 100644 --- a/pkg/server/api/connect/v1/membership_resolvers.go +++ b/pkg/server/api/connect/v1/membership_resolvers.go @@ -32,8 +32,7 @@ func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Members childSession, err := r.iam.SessionService.GetActiveSessionForMembership(ctx, session.ID, obj.ID) if err != nil { - var errSessionNotFound *iam.ErrSessionNotFound - if errors.As(err, &errSessionNotFound) { + if _, ok := errors.AsType[*iam.ErrSessionNotFound](err); ok { return nil, nil } diff --git a/pkg/server/api/connect/v1/organization_resolvers.go b/pkg/server/api/connect/v1/organization_resolvers.go index 2e4fe86d9..10712e87b 100644 --- a/pkg/server/api/connect/v1/organization_resolvers.go +++ b/pkg/server/api/connect/v1/organization_resolvers.go @@ -263,8 +263,7 @@ func (r *organizationResolver) ScimConfiguration(ctx context.Context, obj *types config, err := r.iam.OrganizationService.GetSCIMConfiguration(ctx, obj.ID) if err != nil { - var notFound *iam.ErrNoSCIMConfigurationFound - if errors.As(err, ¬Found) { + if _, ok := errors.AsType[*iam.ErrNoSCIMConfigurationFound](err); ok { return nil, nil } @@ -348,8 +347,7 @@ func (r *organizationResolver) Viewer(ctx context.Context, obj *types.Organizati profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, obj.ID) if err != nil { - var errNotFound *iam.ErrProfileNotFound - if errors.As(err, &errNotFound) { + if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) } diff --git a/pkg/server/api/connect/v1/profile_resolvers.go b/pkg/server/api/connect/v1/profile_resolvers.go index 0ee68b60d..627e09d0a 100644 --- a/pkg/server/api/connect/v1/profile_resolvers.go +++ b/pkg/server/api/connect/v1/profile_resolvers.go @@ -41,8 +41,7 @@ func (r *mutationResolver) CreateUser(ctx context.Context, input types.CreateUse }, ) if err != nil { - var errAlreadyExists *iam.ErrUserAlreadyExists - if errors.As(err, &errAlreadyExists) { + if _, ok := errors.AsType[*iam.ErrUserAlreadyExists](err); ok { return nil, gqlutils.Conflict(ctx, err) } @@ -113,16 +112,11 @@ func (r *mutationResolver) RemoveUser(ctx context.Context, input types.RemoveUse err := r.iam.OrganizationService.RemoveUser(ctx, input.OrganizationID, input.ProfileID) if err != nil { - var ( - errManagedBySCIM *iam.ErrUserManagedBySCIM - errLastActiveOwner *iam.ErrLastActiveOwner - ) - - if errors.As(err, &errManagedBySCIM) { + if _, ok := errors.AsType[*iam.ErrUserManagedBySCIM](err); ok { return nil, gqlutils.Conflict(ctx, err) } - if errors.As(err, &errLastActiveOwner) { + if _, ok := errors.AsType[*iam.ErrLastActiveOwner](err); ok { return nil, gqlutils.Conflict(ctx, err) } @@ -147,8 +141,7 @@ func (r *profileResolver) Identity(ctx context.Context, obj *types.Profile) (*ty identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID) if err != nil { - var errNotFound *iam.ErrIdentityNotFound - if errors.As(err, &errNotFound) { + if _, ok := errors.AsType[*iam.ErrIdentityNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) } @@ -168,8 +161,7 @@ func (r *profileResolver) Organization(ctx context.Context, obj *types.Profile) organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID) if err != nil { - var errNotFound *iam.ErrOrganizationNotFound - if errors.As(err, &errNotFound) { + if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) } @@ -189,8 +181,7 @@ func (r *profileResolver) Membership(ctx context.Context, obj *types.Profile) (* membership, err := r.iam.AccountService.GetMembershipForOrganization(ctx, obj.Identity.ID, obj.Organization.ID) if err != nil { - var errNotFound *iam.ErrMembershipNotFound - if errors.As(err, &errNotFound) { + if _, ok := errors.AsType[*iam.ErrMembershipNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) } diff --git a/pkg/server/api/connect/v1/saml_resolvers.go b/pkg/server/api/connect/v1/saml_resolvers.go index 6511ece42..37afdbd20 100644 --- a/pkg/server/api/connect/v1/saml_resolvers.go +++ b/pkg/server/api/connect/v1/saml_resolvers.go @@ -44,8 +44,7 @@ func (r *mutationResolver) CreateSAMLConfiguration(ctx context.Context, input ty req, ) if err != nil { - var errSAMLConfigurationEmailDomainAlreadyExists *iam.ErrSAMLConfigurationEmailDomainAlreadyExists - if errors.As(err, &errSAMLConfigurationEmailDomainAlreadyExists) { + if _, ok := errors.AsType[*iam.ErrSAMLConfigurationEmailDomainAlreadyExists](err); ok { return nil, gqlutils.Conflict(ctx, err) } diff --git a/pkg/server/api/connect/v1/scim_handler.go b/pkg/server/api/connect/v1/scim_handler.go index aca424da7..bec2932da 100644 --- a/pkg/server/api/connect/v1/scim_handler.go +++ b/pkg/server/api/connect/v1/scim_handler.go @@ -131,8 +131,7 @@ func (h *SCIMHandler) BearerTokenMiddleware(next http.Handler) http.Handler { config, err := h.iam.SCIMService.ValidateToken(r.Context(), token) if err != nil { - var invalidToken *scimservice.ErrSCIMInvalidToken - if errors.As(err, &invalidToken) { + if _, ok := errors.AsType[*scimservice.ErrSCIMInvalidToken](err); ok { httpserver.RenderError(w, http.StatusUnauthorized, errors.New("invalid token")) return } @@ -149,8 +148,7 @@ func (h *SCIMHandler) BearerTokenMiddleware(next http.Handler) http.Handler { } func (rc *scimRequestContext) logAndWrapError(err error, logMsg string) error { - var scimErr scimerrors.ScimError - if errors.As(err, &scimErr) { + if scimErr, ok := errors.AsType[scimerrors.ScimError](err); ok { errMsg := scimErr.Detail // Don't reference profileID for 404 errors - the resource doesn't exist diff --git a/pkg/server/api/connect/v1/scim_resolvers.go b/pkg/server/api/connect/v1/scim_resolvers.go index a236adf46..6be36667b 100644 --- a/pkg/server/api/connect/v1/scim_resolvers.go +++ b/pkg/server/api/connect/v1/scim_resolvers.go @@ -121,8 +121,7 @@ func (r *sCIMBridgeResolver) ScimConfiguration(ctx context.Context, obj *types.S scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, obj.ScimConfiguration.ID) if err != nil { - var errNoSCIMConfigurationFound *iam.ErrNoSCIMConfigurationFound - if errors.As(err, &errNoSCIMConfigurationFound) { + if _, ok := errors.AsType[*iam.ErrNoSCIMConfigurationFound](err); ok { return nil, nil } @@ -183,8 +182,7 @@ func (r *sCIMConfigurationResolver) Organization(ctx context.Context, obj *types organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID) if err != nil { - var errOrganizationNotFound *iam.ErrOrganizationNotFound - if errors.As(err, &errOrganizationNotFound) { + if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok { return nil, nil } @@ -208,8 +206,7 @@ func (r *sCIMConfigurationResolver) Bridge(ctx context.Context, obj *types.SCIMC bridge, err := r.iam.OrganizationService.GetSCIMBridgeByID(ctx, obj.Bridge.ID) if err != nil { - var errSCIMBridgeNotFound *iam.ErrSCIMBridgeNotFound - if errors.As(err, &errSCIMBridgeNotFound) { + if _, ok := errors.AsType[*iam.ErrSCIMBridgeNotFound](err); ok { return nil, nil } diff --git a/pkg/server/api/connect/v1/session_resolvers.go b/pkg/server/api/connect/v1/session_resolvers.go index 182bc958c..a103770a4 100644 --- a/pkg/server/api/connect/v1/session_resolvers.go +++ b/pkg/server/api/connect/v1/session_resolvers.go @@ -22,13 +22,11 @@ import ( func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) { identity, err := r.iam.AuthService.CheckCredentials(ctx, input.Email, input.Password) if err != nil { - var errInvalidPassword *iam.ErrInvalidPassword - if errors.As(err, &errInvalidPassword) { + if _, ok := errors.AsType[*iam.ErrInvalidPassword](err); ok { return nil, gqlutils.Invalid(ctx, err) } - var errInvalidCredentials *iam.ErrInvalidCredentials - if errors.As(err, &errInvalidCredentials) { + if _, ok := errors.AsType[*iam.ErrInvalidCredentials](err); ok { return nil, &gqlerror.Error{ Message: err.Error(), Extensions: map[string]any{ @@ -81,12 +79,11 @@ func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) _, _, err = r.iam.SessionService.OpenPasswordChildSessionForOrganization(ctx, session.ID, *input.OrganizationID) if err != nil { // Here session middleware already took care of expired/nil root session so we only handle membership related errors - var ( - errMembershipNotFound *iam.ErrMembershipNotFound - errUserInactive *iam.ErrUserInactive - ) + if _, ok := errors.AsType[*iam.ErrMembershipNotFound](err); ok { + return nil, gqlutils.Forbiddenf(ctx, "forbidden") + } - if errors.As(err, &errMembershipNotFound) || errors.As(err, &errUserInactive) { + if _, ok := errors.AsType[*iam.ErrUserInactive](err); ok { return nil, gqlutils.Forbiddenf(ctx, "forbidden") } @@ -113,13 +110,11 @@ func (r *mutationResolver) SignUp(ctx context.Context, input types.SignUpInput) }, ) if err != nil { - var errIdentityAlreadyExists *iam.ErrIdentityAlreadyExists - if errors.As(err, &errIdentityAlreadyExists) { + if _, ok := errors.AsType[*iam.ErrIdentityAlreadyExists](err); ok { return nil, gqlutils.Invalid(ctx, err) } - var errSignupDisabled *iam.ErrSignupDisabled - if errors.As(err, &errSignupDisabled) { + if _, ok := errors.AsType[*iam.ErrSignupDisabled](err); ok { return nil, gqlutils.Forbidden(ctx, err) } @@ -142,8 +137,7 @@ func (r *mutationResolver) SignOut(ctx context.Context) (*types.SignOutPayload, err := r.iam.SessionService.CloseSession(ctx, session.ID) if err != nil { - var ErrSessionNotFound *iam.ErrSessionNotFound - if errors.As(err, &ErrSessionNotFound) { + if _, ok := errors.AsType[*iam.ErrSessionNotFound](err); ok { return &types.SignOutPayload{}, nil } @@ -166,8 +160,7 @@ func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.Acti // Sign out any other account before activating a new one err := r.iam.SessionService.CloseSession(ctx, session.ID) if err != nil { - var ErrSessionNotFound *iam.ErrSessionNotFound - if !errors.As(err, &ErrSessionNotFound) { + if _, ok := errors.AsType[*iam.ErrSessionNotFound](err); !ok { r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) return nil, gqlutils.Internal(ctx) } @@ -184,17 +177,15 @@ func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.Acti }, ) if err != nil { - var ( - errInvalidToken *iam.ErrInvalidToken - errInvitationNotFound *iam.ErrInvitationNotFound - errInvitationExpired *iam.ErrInvitationExpired + if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok { + return nil, gqlutils.Invalid(ctx, err) + } - isInvalidErr = errors.As(err, &errInvalidToken) || - errors.As(err, &errInvitationNotFound) || - errors.As(err, &errInvitationExpired) - ) + if _, ok := errors.AsType[*iam.ErrInvitationNotFound](err); ok { + return nil, gqlutils.Invalid(ctx, err) + } - if isInvalidErr { + if _, ok := errors.AsType[*iam.ErrInvitationExpired](err); ok { return nil, gqlutils.Invalid(ctx, err) } @@ -276,8 +267,7 @@ func (r *mutationResolver) ResetPassword(ctx context.Context, input types.ResetP }, ) if err != nil { - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { + if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok { return nil, gqlutils.Invalid(ctx, err) } @@ -295,25 +285,19 @@ func (r *mutationResolver) ResetPassword(ctx context.Context, input types.ResetP func (r *mutationResolver) VerifyEmail(ctx context.Context, input types.VerifyEmailInput) (*types.VerifyEmailPayload, error) { err := r.iam.AccountService.VerifyEmail(ctx, input.Token) if err != nil { - var ( - errInvalidToken *iam.ErrInvalidToken - errIdentityNotFound *iam.ErrIdentityNotFound - errEmailAlreadyVerified *iam.ErrEmailAlreadyVerified - errEmailVerificationMismatch *iam.ErrEmailVerificationMismatch - - isInvalidErr = errors.As(err, &errInvalidToken) || - errors.As(err, &errEmailVerificationMismatch) - ) - - if isInvalidErr { + if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok { return nil, gqlutils.Invalid(ctx, err) } - if errors.As(err, &errEmailAlreadyVerified) { + if _, ok := errors.AsType[*iam.ErrEmailVerificationMismatch](err); ok { + return nil, gqlutils.Invalid(ctx, err) + } + + if _, ok := errors.AsType[*iam.ErrEmailAlreadyVerified](err); ok { return nil, gqlutils.Conflict(ctx, err) } - if errors.As(err, &errIdentityNotFound) { + if _, ok := errors.AsType[*iam.ErrIdentityNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) } @@ -342,16 +326,11 @@ func (r *mutationResolver) ChangePassword(ctx context.Context, input types.Chang }, ) if err != nil { - var ( - errInvalidPassword *iam.ErrInvalidPassword - errIdentityNotFound *iam.ErrIdentityNotFound - ) - - if errors.As(err, &errInvalidPassword) { + if _, ok := errors.AsType[*iam.ErrInvalidPassword](err); ok { return nil, gqlutils.Invalid(ctx, err) } - if errors.As(err, &errIdentityNotFound) { + if _, ok := errors.AsType[*iam.ErrIdentityNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) } @@ -378,16 +357,11 @@ func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEm }, ) if err != nil { - var ( - errInvalidPassword *iam.ErrInvalidPassword - errIdentityNotFound *iam.ErrIdentityNotFound - ) - - if errors.As(err, &errInvalidPassword) { + if _, ok := errors.AsType[*iam.ErrInvalidPassword](err); ok { return nil, gqlutils.Invalid(ctx, err) } - if errors.As(err, &errIdentityNotFound) { + if _, ok := errors.AsType[*iam.ErrIdentityNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) } @@ -407,34 +381,28 @@ func (r *mutationResolver) AssumeOrganizationSession(ctx context.Context, input childSession, membership, err := r.iam.SessionService.AssumeOrganizationSession(ctx, rootSession.ID, input.OrganizationID, input.Continue) if err != nil { - var ( - errMembershipNotFound *iam.ErrMembershipNotFound - errPasswordAuthenticationRequired *iam.ErrPasswordAuthenticationRequired - errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired - ) - - switch { - case errors.As(err, &errMembershipNotFound): + if _, ok := errors.AsType[*iam.ErrMembershipNotFound](err); ok { return nil, gqlutils.NotFound(ctx, err) + } - case errors.As(err, &errPasswordAuthenticationRequired): + if errPasswordAuthenticationRequired, ok := errors.AsType[*iam.ErrPasswordAuthenticationRequired](err); ok { return &types.AssumeOrganizationSessionPayload{ Result: types.PasswordRequired{ Reason: types.ReauthenticationReason(errPasswordAuthenticationRequired.Reason), }, }, nil + } - case errors.As(err, &errSAMLAuthenticationRequired): + if errSAMLAuthenticationRequired, ok := errors.AsType[*iam.ErrSAMLAuthenticationRequired](err); ok { return &types.AssumeOrganizationSessionPayload{ Result: types.SAMLAuthenticationRequired{ Reason: types.ReauthenticationReason(errSAMLAuthenticationRequired.Reason), }, }, nil - - default: - r.logger.ErrorCtx(ctx, "cannot assume organization session", log.Error(err)) - return nil, gqlutils.Internal(ctx) } + + r.logger.ErrorCtx(ctx, "cannot assume organization session", log.Error(err)) + return nil, gqlutils.Internal(ctx) } return &types.AssumeOrganizationSessionPayload{ @@ -455,8 +423,7 @@ func (r *mutationResolver) RevokeSession(ctx context.Context, input types.Revoke err := r.iam.SessionService.RevokeSession(ctx, identity.ID, input.SessionID) if err != nil { - var ErrSessionExpired *iam.ErrSessionExpired - if errors.As(err, &ErrSessionExpired) { + if _, ok := errors.AsType[*iam.ErrSessionExpired](err); ok { return &types.RevokeSessionPayload{Success: true}, nil } diff --git a/pkg/server/api/mcp/mcputils/recovery.go b/pkg/server/api/mcp/mcputils/recovery.go index 85c367ae1..6349b8a80 100644 --- a/pkg/server/api/mcp/mcputils/recovery.go +++ b/pkg/server/api/mcp/mcputils/recovery.go @@ -55,13 +55,11 @@ func NewRecoverFunc(logger *log.Logger) mcpgenmcp.RecoverFunc { // those. Unknown errors are logged and replaced with a generic internal error // to avoid leaking implementation details to the client. func sanitizeError(ctx context.Context, logger *log.Logger, err error) error { - var permissionDeniedErr *iam.ErrInsufficientPermissions - if errors.As(err, &permissionDeniedErr) { + if _, ok := errors.AsType[*iam.ErrInsufficientPermissions](err); ok { return fmt.Errorf("permission denied") } - var assumptionRequiredErr *iam.ErrAssumptionRequired - if errors.As(err, &assumptionRequiredErr) { + if _, ok := errors.AsType[*iam.ErrAssumptionRequired](err); ok { return fmt.Errorf("assumption required") } @@ -77,13 +75,11 @@ func sanitizeError(ctx context.Context, logger *log.Logger, err error) error { return fmt.Errorf("resource is in use") } - var validationErrors validator.ValidationErrors - if errors.As(err, &validationErrors) { + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { return validationErrors } - var validationError *validator.ValidationError - if errors.As(err, &validationError) { + if validationError, ok := errors.AsType[*validator.ValidationError](err); ok { return validationError } diff --git a/pkg/server/api/mcp/v1/middleware.go b/pkg/server/api/mcp/v1/middleware.go index b2b25135a..d50990f68 100644 --- a/pkg/server/api/mcp/v1/middleware.go +++ b/pkg/server/api/mcp/v1/middleware.go @@ -15,9 +15,8 @@ package mcp_v1 import ( - "net/http" - "errors" + "net/http" "go.gearno.de/kit/httpserver" "go.gearno.de/kit/log" diff --git a/pkg/server/api/mcp/v1/schema.resolvers.go b/pkg/server/api/mcp/v1/schema.resolvers.go index 5ca8d353f..9794236dd 100644 --- a/pkg/server/api/mcp/v1/schema.resolvers.go +++ b/pkg/server/api/mcp/v1/schema.resolvers.go @@ -2435,8 +2435,7 @@ func (r *Resolver) ListUsersTool(ctx context.Context, req *mcp.CallToolRequest, func (r *Resolver) GetUserTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetUserInput) (*mcp.CallToolResult, types.GetUserOutput, error) { profile, err := r.iamSvc.OrganizationService.GetProfile(ctx, input.ID) if err != nil { - var errNotFound *iam.ErrProfileNotFound - if errors.As(err, &errNotFound) { + if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); ok { return nil, types.GetUserOutput{}, fmt.Errorf("user not found: %w", err) } @@ -2472,8 +2471,7 @@ func (r *Resolver) CreateUserTool(ctx context.Context, req *mcp.CallToolRequest, ContractEndDate: contractEnd, }) if err != nil { - var errAlreadyExists *iam.ErrUserAlreadyExists - if errors.As(err, &errAlreadyExists) { + if _, ok := errors.AsType[*iam.ErrUserAlreadyExists](err); ok { return nil, types.CreateUserOutput{}, fmt.Errorf("user with email already exists: %w", err) } @@ -2491,16 +2489,11 @@ func (r *Resolver) InviteUserTool(ctx context.Context, req *mcp.CallToolRequest, ProfileID: input.ProfileID, }) if err != nil { - var ( - errOrgNotFound *iam.ErrOrganizationNotFound - errUserExists *iam.ErrUserAlreadyExists - ) - - if errors.As(err, &errOrgNotFound) { + if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok { return nil, types.InviteUserOutput{}, fmt.Errorf("organization not found: %w", err) } - if errors.As(err, &errUserExists) { + if _, ok := errors.AsType[*iam.ErrUserAlreadyExists](err); ok { return nil, types.InviteUserOutput{}, fmt.Errorf("user already in organization: %w", err) } @@ -2574,16 +2567,11 @@ func (r *Resolver) RemoveUserTool(ctx context.Context, req *mcp.CallToolRequest, err := r.iamSvc.OrganizationService.RemoveUser(ctx, input.OrganizationID, input.ProfileID) if err != nil { - var ( - errManagedBySCIM *iam.ErrUserManagedBySCIM - errLastOwner *iam.ErrLastActiveOwner - ) - - if errors.As(err, &errManagedBySCIM) { + if _, ok := errors.AsType[*iam.ErrUserManagedBySCIM](err); ok { return nil, types.RemoveUserOutput{}, fmt.Errorf("user is managed by SCIM and cannot be removed: %w", err) } - if errors.As(err, &errLastOwner) { + if _, ok := errors.AsType[*iam.ErrLastActiveOwner](err); ok { return nil, types.RemoveUserOutput{}, fmt.Errorf("cannot remove last active owner: %w", err) } @@ -5189,8 +5177,7 @@ func (r *Resolver) GetSCIMConfigurationTool(ctx context.Context, req *mcp.CallTo config, err := r.iamSvc.OrganizationService.GetSCIMConfiguration(ctx, input.OrganizationID) if err != nil { - var errNotFound *iam.ErrNoSCIMConfigurationFound - if errors.As(err, &errNotFound) { + if _, ok := errors.AsType[*iam.ErrNoSCIMConfigurationFound](err); ok { return nil, types.GetSCIMConfigurationOutput{}, fmt.Errorf("SCIM configuration not found") } @@ -5255,8 +5242,7 @@ func (r *Resolver) GetSCIMBridgeTool(ctx context.Context, req *mcp.CallToolReque bridge, err := r.iamSvc.OrganizationService.GetSCIMBridgeByID(ctx, input.ID) if err != nil { - var errNotFound *iam.ErrSCIMBridgeNotFound - if errors.As(err, &errNotFound) { + if _, ok := errors.AsType[*iam.ErrSCIMBridgeNotFound](err); ok { return nil, types.GetSCIMBridgeOutput{}, fmt.Errorf("SCIM bridge %s not found", input.ID) } diff --git a/pkg/server/api/trust/v1/auth_resolvers.go b/pkg/server/api/trust/v1/auth_resolvers.go index 1fd89299e..149a55670 100644 --- a/pkg/server/api/trust/v1/auth_resolvers.go +++ b/pkg/server/api/trust/v1/auth_resolvers.go @@ -7,8 +7,8 @@ package trust_v1 import ( "context" - "errors" + "errors" "go.gearno.de/kit/log" "go.probo.inc/probo/pkg/baseurl" "go.probo.inc/probo/pkg/coredata" @@ -58,13 +58,11 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri email, err := r.iam.AuthService.GetMagicLinkEmail(ctx, input.Token) if err != nil { - var errExpiredToken *iam.ErrExpiredToken - if errors.As(err, &errExpiredToken) { + if _, ok := errors.AsType[*iam.ErrExpiredToken](err); ok { return nil, gqlutils.Invalid(ctx, err) } - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { + if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok { return nil, gqlutils.Invalid(ctx, err) } @@ -81,15 +79,13 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) if err != nil { - var errExpiredToken *iam.ErrExpiredToken - if errors.As(err, &errExpiredToken) { - return nil, gqlutils.Invalid(ctx, err) - } + if _, ok := errors.AsType[*iam.ErrExpiredToken](err); ok { + return nil, gqlutils.Invalid(ctx, err) + } - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { - return nil, gqlutils.Invalid(ctx, err) - } + if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok { + return nil, gqlutils.Invalid(ctx, err) + } r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) @@ -105,15 +101,13 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) if err != nil { - var errExpiredToken *iam.ErrExpiredToken - if errors.As(err, &errExpiredToken) { - return nil, gqlutils.Invalid(ctx, err) - } + if _, ok := errors.AsType[*iam.ErrExpiredToken](err); ok { + return nil, gqlutils.Invalid(ctx, err) + } - var errInvalidToken *iam.ErrInvalidToken - if errors.As(err, &errInvalidToken) { - return nil, gqlutils.Invalid(ctx, err) - } + if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok { + return nil, gqlutils.Invalid(ctx, err) + } r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) diff --git a/pkg/server/gqlutils/errors.go b/pkg/server/gqlutils/errors.go index 64443bb78..1a6347343 100644 --- a/pkg/server/gqlutils/errors.go +++ b/pkg/server/gqlutils/errors.go @@ -143,11 +143,9 @@ func Conflictf(ctx context.Context, format string, a ...any) *gqlerror.Error { } func Invalid(ctx context.Context, err error) *gqlerror.Error { - var errValidation *validator.ValidationError - var details map[string]any - if errors.As(err, &errValidation) { + if errValidation, ok := errors.AsType[*validator.ValidationError](err); ok { details = map[string]any{ "cause": errValidation.Code, "field": errValidation.Field, diff --git a/pkg/server/gqlutils/handler.go b/pkg/server/gqlutils/handler.go index 5f7c3986d..3f75f8bb9 100644 --- a/pkg/server/gqlutils/handler.go +++ b/pkg/server/gqlutils/handler.go @@ -22,7 +22,6 @@ import ( "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/extension" "github.com/99designs/gqlgen/graphql/handler/transport" - "go.gearno.de/kit/log" ) diff --git a/pkg/server/gqlutils/recovery.go b/pkg/server/gqlutils/recovery.go index eb5b00656..0513911de 100644 --- a/pkg/server/gqlutils/recovery.go +++ b/pkg/server/gqlutils/recovery.go @@ -31,8 +31,7 @@ func RecoverFunc(ctx context.Context, err any) error { return gqlErr } - var errValidations validator.ValidationErrors - if errors.As(asError(err), &errValidations) { + if errValidations, ok := errors.AsType[validator.ValidationErrors](asError(err)); ok { gqlErrors := gqlerror.List{} for _, err := range errValidations { @@ -48,9 +47,10 @@ func RecoverFunc(ctx context.Context, err any) error { return gqlErrors } - var permissionDeniedErr *iam.ErrInsufficientPermissions - if errTyped, ok := err.(error); ok && errors.As(errTyped, &permissionDeniedErr) { - return Forbidden(ctx, permissionDeniedErr) + if errTyped, ok := err.(error); ok { + if permissionDeniedErr, ok := errors.AsType[*iam.ErrInsufficientPermissions](errTyped); ok { + return Forbidden(ctx, permissionDeniedErr) + } } logger := httpserver.LoggerFromContext(ctx) diff --git a/pkg/slack/slack_message_service.go b/pkg/slack/slack_message_service.go index 71089a04c..35ab1d90b 100644 --- a/pkg/slack/slack_message_service.go +++ b/pkg/slack/slack_message_service.go @@ -333,11 +333,14 @@ func (s *SlackMessageService) loadDocumentsReportsAndFilesFromAccesses( return nil, nil, nil, fmt.Errorf("cannot load document: %w", err) } - documents = append(documents, SlackMessageDocument{ - ID: access.DocumentID.String(), - Title: doc.Title, - Status: access.Status.String(), - }) + documents = append( + documents, + SlackMessageDocument{ + ID: access.DocumentID.String(), + Title: doc.Title, + Status: access.Status.String(), + }, + ) } if access.ReportID != nil { @@ -361,12 +364,15 @@ func (s *SlackMessageService) loadDocumentsReportsAndFilesFromAccesses( label = label + " - " + *audit.Name } - reports = append(reports, SlackMessageReport{ - ID: access.ReportID.String(), - Title: label, - AuditID: audit.ID.String(), - Status: access.Status.String(), - }) + reports = append( + reports, + SlackMessageReport{ + ID: access.ReportID.String(), + Title: label, + AuditID: audit.ID.String(), + Status: access.Status.String(), + }, + ) } if access.TrustCenterFileID != nil { @@ -375,12 +381,15 @@ func (s *SlackMessageService) loadDocumentsReportsAndFilesFromAccesses( return nil, nil, nil, fmt.Errorf("cannot load trust center file: %w", err) } - files = append(files, SlackMessageFile{ - ID: access.TrustCenterFileID.String(), - Name: file.Name, - Category: file.Category, - Status: access.Status.String(), - }) + files = append( + files, + SlackMessageFile{ + ID: access.TrustCenterFileID.String(), + Name: file.Name, + Category: file.Category, + Status: access.Status.String(), + }, + ) } } diff --git a/pkg/trust/framework_service.go b/pkg/trust/framework_service.go index fd2ce503d..df8a96508 100644 --- a/pkg/trust/framework_service.go +++ b/pkg/trust/framework_service.go @@ -16,9 +16,8 @@ package trust import ( "context" - "time" - "fmt" + "time" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" diff --git a/pkg/webhook/sender.go b/pkg/webhook/sender.go index fc49db04d..6b315d8e8 100644 --- a/pkg/webhook/sender.go +++ b/pkg/webhook/sender.go @@ -170,10 +170,13 @@ func (s *Sender) claimNextWebhookData(ctx context.Context) (*coredata.WebhookDat return fmt.Errorf("cannot insert webhook event: %w", err) } - deliveries = append(deliveries, pendingDelivery{ - Event: event, - Config: config, - }) + deliveries = append( + deliveries, + pendingDelivery{ + Event: event, + Config: config, + }, + ) } webhookData.ProcessedAt = &now