Enforce Go style rules across codebase
Apply five style rules: convert iota string enums to typed string constants, replace errors.As with errors.AsType, merge three-group imports into two groups, fix multiline parameter/argument formatting, and replace fmt.Sprintf URL construction with net/url. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -385,51 +385,67 @@ func (s AccessSourceService) ConfigureAccessSource(
|
|||||||
|
|
||||||
switch dbConnector.Provider {
|
switch dbConnector.Provider {
|
||||||
case coredata.ConnectorProviderGitHub:
|
case coredata.ConnectorProviderGitHub:
|
||||||
if err := dbConnector.SetSettings(&coredata.GitHubConnectorSettings{
|
if err := dbConnector.SetSettings(
|
||||||
Organization: req.OrganizationSlug,
|
&coredata.GitHubConnectorSettings{
|
||||||
}); err != nil {
|
Organization: req.OrganizationSlug,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("cannot set github settings: %w", err)
|
return fmt.Errorf("cannot set github settings: %w", err)
|
||||||
}
|
}
|
||||||
case coredata.ConnectorProviderSentry:
|
case coredata.ConnectorProviderSentry:
|
||||||
if err := dbConnector.SetSettings(&coredata.SentryConnectorSettings{
|
if err := dbConnector.SetSettings(
|
||||||
OrganizationSlug: req.OrganizationSlug,
|
&coredata.SentryConnectorSettings{
|
||||||
}); err != nil {
|
OrganizationSlug: req.OrganizationSlug,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("cannot set sentry settings: %w", err)
|
return fmt.Errorf("cannot set sentry settings: %w", err)
|
||||||
}
|
}
|
||||||
case coredata.ConnectorProviderGitLab:
|
case coredata.ConnectorProviderGitLab:
|
||||||
if err := dbConnector.SetSettings(&coredata.GitLabConnectorSettings{
|
if err := dbConnector.SetSettings(
|
||||||
GroupID: req.OrganizationSlug,
|
&coredata.GitLabConnectorSettings{
|
||||||
}); err != nil {
|
GroupID: req.OrganizationSlug,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("cannot set gitlab settings: %w", err)
|
return fmt.Errorf("cannot set gitlab settings: %w", err)
|
||||||
}
|
}
|
||||||
case coredata.ConnectorProviderBitbucket:
|
case coredata.ConnectorProviderBitbucket:
|
||||||
if err := dbConnector.SetSettings(&coredata.BitbucketConnectorSettings{
|
if err := dbConnector.SetSettings(
|
||||||
Workspace: req.OrganizationSlug,
|
&coredata.BitbucketConnectorSettings{
|
||||||
}); err != nil {
|
Workspace: req.OrganizationSlug,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("cannot set bitbucket settings: %w", err)
|
return fmt.Errorf("cannot set bitbucket settings: %w", err)
|
||||||
}
|
}
|
||||||
case coredata.ConnectorProviderHeroku:
|
case coredata.ConnectorProviderHeroku:
|
||||||
if err := dbConnector.SetSettings(&coredata.HerokuConnectorSettings{
|
if err := dbConnector.SetSettings(
|
||||||
TeamID: req.OrganizationSlug,
|
&coredata.HerokuConnectorSettings{
|
||||||
}); err != nil {
|
TeamID: req.OrganizationSlug,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("cannot set heroku settings: %w", err)
|
return fmt.Errorf("cannot set heroku settings: %w", err)
|
||||||
}
|
}
|
||||||
case coredata.ConnectorProviderAsana:
|
case coredata.ConnectorProviderAsana:
|
||||||
if err := dbConnector.SetSettings(&coredata.AsanaConnectorSettings{
|
if err := dbConnector.SetSettings(
|
||||||
WorkspaceGID: req.OrganizationSlug,
|
&coredata.AsanaConnectorSettings{
|
||||||
}); err != nil {
|
WorkspaceGID: req.OrganizationSlug,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("cannot set asana settings: %w", err)
|
return fmt.Errorf("cannot set asana settings: %w", err)
|
||||||
}
|
}
|
||||||
case coredata.ConnectorProviderNetlify:
|
case coredata.ConnectorProviderNetlify:
|
||||||
if err := dbConnector.SetSettings(&coredata.NetlifyConnectorSettings{
|
if err := dbConnector.SetSettings(
|
||||||
AccountSlug: req.OrganizationSlug,
|
&coredata.NetlifyConnectorSettings{
|
||||||
}); err != nil {
|
AccountSlug: req.OrganizationSlug,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("cannot set netlify settings: %w", err)
|
return fmt.Errorf("cannot set netlify settings: %w", err)
|
||||||
}
|
}
|
||||||
case coredata.ConnectorProviderClickUp:
|
case coredata.ConnectorProviderClickUp:
|
||||||
if err := dbConnector.SetSettings(&coredata.ClickUpConnectorSettings{
|
if err := dbConnector.SetSettings(
|
||||||
TeamID: req.OrganizationSlug,
|
&coredata.ClickUpConnectorSettings{
|
||||||
}); err != nil {
|
TeamID: req.OrganizationSlug,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf("cannot set clickup settings: %w", err)
|
return fmt.Errorf("cannot set clickup settings: %w", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -67,10 +67,21 @@ type asanaUsersPage struct {
|
|||||||
func (d *AsanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
func (d *AsanaDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
||||||
var records []AccountRecord
|
var records []AccountRecord
|
||||||
|
|
||||||
next := fmt.Sprintf(
|
u, err := url.JoinPath("https://app.asana.com", "api", "1.0", "workspaces", d.workspaceGID, "users")
|
||||||
"https://app.asana.com/api/1.0/workspaces/%s/users?opt_fields=email,name&limit=100",
|
if err != nil {
|
||||||
url.PathEscape(d.workspaceGID),
|
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 {
|
for range maxPaginationPages {
|
||||||
page, err := d.queryUsers(ctx, next)
|
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
|
// Active=false from any of those would fabricate state, so
|
||||||
// leave Active nil (unknown) and let downstream review surface
|
// leave Active nil (unknown) and let downstream review surface
|
||||||
// the gap honestly.
|
// the gap honestly.
|
||||||
records = append(records, AccountRecord{
|
records = append(
|
||||||
Email: u.Email,
|
records,
|
||||||
FullName: u.Name,
|
AccountRecord{
|
||||||
ExternalID: u.GID,
|
Email: u.Email,
|
||||||
MFAStatus: coredata.MFAStatusUnknown,
|
FullName: u.Name,
|
||||||
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
|
ExternalID: u.GID,
|
||||||
AccountType: coredata.AccessEntryAccountTypeUser,
|
MFAStatus: coredata.MFAStatusUnknown,
|
||||||
})
|
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
|
||||||
|
AccountType: coredata.AccessEntryAccountTypeUser,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if page.NextPage == nil || page.NextPage.URI == "" {
|
if page.NextPage == nil || page.NextPage.URI == "" {
|
||||||
|
|||||||
@@ -67,10 +67,21 @@ type bitbucketMembersPage struct {
|
|||||||
func (d *BitbucketDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
func (d *BitbucketDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
||||||
var records []AccountRecord
|
var records []AccountRecord
|
||||||
|
|
||||||
next := fmt.Sprintf(
|
u, err := url.JoinPath("https://api.bitbucket.org", "2.0", "workspaces", d.workspace, "members")
|
||||||
"https://api.bitbucket.org/2.0/workspaces/%s/members?fields=%%2Bvalues.user.email&pagelen=100",
|
if err != nil {
|
||||||
url.PathEscape(d.workspace),
|
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 {
|
for range maxPaginationPages {
|
||||||
page, err := d.queryMembers(ctx, next)
|
page, err := d.queryMembers(ctx, next)
|
||||||
|
|||||||
@@ -71,7 +71,10 @@ type clickupTeamResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *ClickUpDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
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)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"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) {
|
func (d *CloudflareDriver) queryAccounts(ctx context.Context, page int) (*cloudflareListAccountsResponse, error) {
|
||||||
url := fmt.Sprintf(
|
parsed, err := url.Parse("https://api.cloudflare.com/client/v4/accounts")
|
||||||
"https://api.cloudflare.com/client/v4/accounts?page=%d&per_page=50",
|
if err != nil {
|
||||||
page,
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot create cloudflare accounts request: %w", err)
|
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) {
|
func (d *CloudflareDriver) queryMembers(ctx context.Context, accountID string, page int) (*cloudflareListMembersResponse, error) {
|
||||||
url := fmt.Sprintf(
|
u, err := url.JoinPath("https://api.cloudflare.com", "client", "v4", "accounts", accountID, "members")
|
||||||
"https://api.cloudflare.com/client/v4/accounts/%s/members?page=%d&per_page=50",
|
if err != nil {
|
||||||
accountID,
|
return nil, fmt.Errorf("cannot build cloudflare members URL: %w", err)
|
||||||
page,
|
}
|
||||||
)
|
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot create cloudflare members request: %w", err)
|
return nil, fmt.Errorf("cannot create cloudflare members request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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) {
|
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",
|
u, err := url.JoinPath(baseURI, "restapi", "v2.1", "accounts", accountID, "users")
|
||||||
baseURI, accountID, docusignUsersPageSize, startPosition)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot create docusign users request: %w", err)
|
return nil, fmt.Errorf("cannot create docusign users request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -82,7 +83,9 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
|||||||
for _, m := range members {
|
for _, m := range members {
|
||||||
membership, err := d.fetchMembership(ctx, m.Login)
|
membership, err := d.fetchMembership(ctx, m.Login)
|
||||||
if err != nil {
|
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),
|
log.Error(err),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,7 +94,9 @@ func (d *GitHubDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
|||||||
|
|
||||||
profile, err := d.fetchUserProfile(ctx, m.Login)
|
profile, err := d.fetchUserProfile(ctx, m.Login)
|
||||||
if err != nil {
|
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),
|
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) {
|
func (d *GitHubDriver) fetchAllMembers(ctx context.Context) ([]githubMember, error) {
|
||||||
var members []githubMember
|
var members []githubMember
|
||||||
|
|
||||||
url := fmt.Sprintf(
|
u, err := url.JoinPath("https://api.github.com", "orgs", d.org, "members")
|
||||||
"https://api.github.com/orgs/%s/members?per_page=100",
|
if err != nil {
|
||||||
d.org,
|
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 {
|
for range maxPaginationPages {
|
||||||
page, nextURL, err := d.fetchMembersPage(ctx, url)
|
page, nextURL, err := d.fetchMembersPage(ctx, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -162,7 +177,7 @@ func (d *GitHubDriver) fetchAllMembers(ctx context.Context) ([]githubMember, err
|
|||||||
return members, nil
|
return members, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
url = nextURL
|
endpoint = nextURL
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("cannot list all github members: %w", ErrPaginationLimitReached)
|
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) {
|
func (d *GitHubDriver) fetchAll2FADisabledLogins(ctx context.Context) (map[string]bool, error) {
|
||||||
set := make(map[string]bool)
|
set := make(map[string]bool)
|
||||||
|
|
||||||
url := fmt.Sprintf(
|
u, err := url.JoinPath("https://api.github.com", "orgs", d.org, "members")
|
||||||
"https://api.github.com/orgs/%s/members?filter=2fa_disabled&per_page=100",
|
if err != nil {
|
||||||
d.org,
|
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 {
|
for range maxPaginationPages {
|
||||||
page, nextURL, err := d.fetchMembersPage(ctx, url)
|
page, nextURL, err := d.fetchMembersPage(ctx, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -221,20 +247,19 @@ func (d *GitHubDriver) fetchAll2FADisabledLogins(ctx context.Context) (map[strin
|
|||||||
return set, nil
|
return set, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
url = nextURL
|
endpoint = nextURL
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("cannot list all github 2fa-disabled members: %w", ErrPaginationLimitReached)
|
return nil, fmt.Errorf("cannot list all github 2fa-disabled members: %w", ErrPaginationLimitReached)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *GitHubDriver) fetchMembership(ctx context.Context, login string) (*githubMembership, error) {
|
func (d *GitHubDriver) fetchMembership(ctx context.Context, login string) (*githubMembership, error) {
|
||||||
url := fmt.Sprintf(
|
endpoint, err := url.JoinPath("https://api.github.com", "orgs", d.org, "memberships", login)
|
||||||
"https://api.github.com/orgs/%s/memberships/%s",
|
if err != nil {
|
||||||
d.org,
|
return nil, fmt.Errorf("cannot build github membership URL: %w", err)
|
||||||
login,
|
}
|
||||||
)
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot create github membership request: %w", err)
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot create github user profile request: %w", err)
|
return nil, fmt.Errorf("cannot create github user profile request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,10 +67,20 @@ type gitlabMember struct {
|
|||||||
func (d *GitLabDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
func (d *GitLabDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
||||||
var records []AccountRecord
|
var records []AccountRecord
|
||||||
|
|
||||||
next := fmt.Sprintf(
|
u, err := url.JoinPath("https://gitlab.com", "api", "v4", "groups", d.groupID, "members", "all")
|
||||||
"https://gitlab.com/api/v4/groups/%s/members/all?per_page=100",
|
if err != nil {
|
||||||
url.PathEscape(d.groupID),
|
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 {
|
for range maxPaginationPages {
|
||||||
members, linkHeader, err := d.queryMembers(ctx, next)
|
members, linkHeader, err := d.queryMembers(ctx, next)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
admin "google.golang.org/api/admin/directory/v1"
|
admin "google.golang.org/api/admin/directory/v1"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GoogleWorkspaceDriver fetches user accounts from Google Workspace
|
// GoogleWorkspaceDriver fetches user accounts from Google Workspace
|
||||||
|
|||||||
@@ -72,7 +72,10 @@ type herokuTeamMember struct {
|
|||||||
func (d *HerokuDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
func (d *HerokuDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
||||||
var records []AccountRecord
|
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 := ""
|
rangeHeader := ""
|
||||||
|
|
||||||
for range maxPaginationPages {
|
for range maxPaginationPages {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -248,7 +248,12 @@ func (d *Microsoft365Driver) listUsers(ctx context.Context) ([]microsoft365User,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildMicrosoft365UsersURL() (string, error) {
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot parse graph users URL: %w", err)
|
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) {
|
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
|
var all []microsoft365DirectoryRole
|
||||||
|
|
||||||
for range microsoft365MaxPaginationOK {
|
for range microsoft365MaxPaginationOK {
|
||||||
var page microsoft365RolesPage
|
var page microsoft365RolesPage
|
||||||
if err := d.fetchJSON(ctx, url, &page); err != nil {
|
if err := d.fetchJSON(ctx, endpoint, &page); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,20 +286,23 @@ func (d *Microsoft365Driver) listDirectoryRoles(ctx context.Context) ([]microsof
|
|||||||
return all, nil
|
return all, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
url = page.NextLink
|
endpoint = page.NextLink
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("cannot list all microsoft 365 directory roles: %w", ErrPaginationLimitReached)
|
return nil, fmt.Errorf("cannot list all microsoft 365 directory roles: %w", ErrPaginationLimitReached)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Microsoft365Driver) listRoleMembers(ctx context.Context, roleID string) ([]microsoft365RoleMember, error) {
|
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
|
var all []microsoft365RoleMember
|
||||||
|
|
||||||
for range microsoft365MaxPaginationOK {
|
for range microsoft365MaxPaginationOK {
|
||||||
var page microsoft365MembersPage
|
var page microsoft365MembersPage
|
||||||
if err := d.fetchJSON(ctx, url, &page); err != nil {
|
if err := d.fetchJSON(ctx, endpoint, &page); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +311,7 @@ func (d *Microsoft365Driver) listRoleMembers(ctx context.Context, roleID string)
|
|||||||
return all, nil
|
return all, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
url = page.NextLink
|
endpoint = page.NextLink
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("cannot list all members of role %q: %w", roleID, ErrPaginationLimitReached)
|
return nil, fmt.Errorf("cannot list all members of role %q: %w", roleID, ErrPaginationLimitReached)
|
||||||
|
|||||||
@@ -22,11 +22,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"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/connector"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"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
|
// 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) {
|
func (r *cloudflareNameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
|
||||||
req, err := http.NewRequestWithContext(
|
cfURL, err := url.Parse("https://api.cloudflare.com/client/v4/accounts")
|
||||||
ctx,
|
if err != nil {
|
||||||
http.MethodGet,
|
return "", fmt.Errorf("cannot parse cloudflare accounts URL: %w", err)
|
||||||
"https://api.cloudflare.com/client/v4/accounts?page=1&per_page=1",
|
}
|
||||||
nil,
|
|
||||||
)
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot create cloudflare accounts request: %w", err)
|
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) {
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot create tally organization request: %w", err)
|
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
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot create sentry organization request: %w", err)
|
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) {
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot create github organization request: %w", err)
|
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
|
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)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -689,7 +705,10 @@ func (r *bitbucketNameResolver) ResolveInstanceName(ctx context.Context) (string
|
|||||||
return "", nil
|
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)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -739,7 +758,10 @@ func (r *herokuNameResolver) ResolveInstanceName(ctx context.Context) (string, e
|
|||||||
return "", nil
|
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)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -799,7 +821,10 @@ func (r *asanaNameResolver) ResolveInstanceName(ctx context.Context) (string, er
|
|||||||
return "", nil
|
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)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -846,7 +871,10 @@ func (r *netlifyNameResolver) ResolveInstanceName(ctx context.Context) (string,
|
|||||||
return "", nil
|
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)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -891,7 +919,10 @@ func (r *clickupNameResolver) ResolveInstanceName(ctx context.Context) (string,
|
|||||||
return "", nil
|
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)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -941,7 +972,10 @@ func (r *vercelNameResolver) ResolveInstanceName(ctx context.Context) (string, e
|
|||||||
return "", nil
|
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)
|
teamReq, err := http.NewRequestWithContext(ctx, http.MethodGet, teamURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1107,12 +1141,16 @@ func NewMicrosoft365NameResolver(httpClient *http.Client) NameResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *microsoft365NameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
|
func (r *microsoft365NameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
|
||||||
req, err := http.NewRequestWithContext(
|
msURL, err := url.Parse("https://graph.microsoft.com/v1.0/organization")
|
||||||
ctx,
|
if err != nil {
|
||||||
http.MethodGet,
|
return "", fmt.Errorf("cannot parse microsoft 365 organization URL: %w", err)
|
||||||
"https://graph.microsoft.com/v1.0/organization?$select=displayName,verifiedDomains",
|
}
|
||||||
nil,
|
|
||||||
)
|
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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot create microsoft 365 organization request: %w", err)
|
return "", fmt.Errorf("cannot create microsoft 365 organization request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,10 +56,20 @@ type netlifyMember struct {
|
|||||||
func (d *NetlifyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
func (d *NetlifyDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
||||||
var records []AccountRecord
|
var records []AccountRecord
|
||||||
|
|
||||||
next := fmt.Sprintf(
|
u, err := url.JoinPath("https://api.netlify.com", "api", "v1", d.accountSlug, "members")
|
||||||
"https://api.netlify.com/api/v1/%s/members?per_page=100",
|
if err != nil {
|
||||||
url.PathEscape(d.accountSlug),
|
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 {
|
for range maxPaginationPages {
|
||||||
members, linkHeader, err := d.queryMembers(ctx, next)
|
members, linkHeader, err := d.queryMembers(ctx, next)
|
||||||
|
|||||||
@@ -65,18 +65,21 @@ func (d *ProboMembershipsDriver) ListAccounts(ctx context.Context) ([]AccountRec
|
|||||||
isAdmin := role == string(coredata.MembershipRoleOwner) || role == string(coredata.MembershipRoleAdmin)
|
isAdmin := role == string(coredata.MembershipRoleOwner) || role == string(coredata.MembershipRoleAdmin)
|
||||||
createdAt := account.CreatedAt
|
createdAt := account.CreatedAt
|
||||||
|
|
||||||
records = append(records, AccountRecord{
|
records = append(
|
||||||
Email: account.Email,
|
records,
|
||||||
FullName: account.FullName,
|
AccountRecord{
|
||||||
Role: role,
|
Email: account.Email,
|
||||||
Active: new(account.State == string(coredata.ProfileStateActive)),
|
FullName: account.FullName,
|
||||||
IsAdmin: isAdmin,
|
Role: role,
|
||||||
ExternalID: account.ID.String(),
|
Active: new(account.State == string(coredata.ProfileStateActive)),
|
||||||
CreatedAt: &createdAt,
|
IsAdmin: isAdmin,
|
||||||
MFAStatus: coredata.MFAStatusUnknown,
|
ExternalID: account.ID.String(),
|
||||||
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
|
CreatedAt: &createdAt,
|
||||||
AccountType: coredata.AccessEntryAccountTypeUser,
|
MFAStatus: coredata.MFAStatusUnknown,
|
||||||
})
|
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
|
||||||
|
AccountType: coredata.AccessEntryAccountTypeUser,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
@@ -108,10 +109,10 @@ func (d *SentryDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
|||||||
|
|
||||||
var records []AccountRecord
|
var records []AccountRecord
|
||||||
|
|
||||||
nextURL := fmt.Sprintf(
|
nextURL, err := url.JoinPath("https://sentry.io", "api", "0", "organizations", orgSlug, "members")
|
||||||
"https://sentry.io/api/0/organizations/%s/members/",
|
if err != nil {
|
||||||
orgSlug,
|
return nil, fmt.Errorf("cannot build sentry members URL: %w", err)
|
||||||
)
|
}
|
||||||
|
|
||||||
for range maxPaginationPages {
|
for range maxPaginationPages {
|
||||||
members, linkHeader, err := d.queryMembers(ctx, nextURL)
|
members, linkHeader, err := d.queryMembers(ctx, nextURL)
|
||||||
|
|||||||
@@ -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 {
|
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("source_id", source.ID.String()),
|
||||||
log.String("current_name", source.Name),
|
log.String("current_name", source.Name),
|
||||||
)
|
)
|
||||||
@@ -134,7 +136,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
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.String("source_id", source.ID.String()),
|
||||||
log.Error(err),
|
log.Error(err),
|
||||||
)
|
)
|
||||||
@@ -143,7 +147,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resolver == nil {
|
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("source_id", source.ID.String()),
|
||||||
log.String("provider", dbConnector.Provider.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)
|
instanceName, err := resolver.ResolveInstanceName(resolveCtx)
|
||||||
if err != nil {
|
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("source_id", source.ID.String()),
|
||||||
log.String("provider", dbConnector.Provider.String()),
|
log.String("provider", dbConnector.Provider.String()),
|
||||||
log.Error(err),
|
log.Error(err),
|
||||||
@@ -166,7 +174,9 @@ func (h *sourceNameHandler) Process(ctx context.Context, source coredata.AccessS
|
|||||||
}
|
}
|
||||||
|
|
||||||
if instanceName == "" {
|
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("source_id", source.ID.String()),
|
||||||
log.String("provider", dbConnector.Provider.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)
|
displayName := drivers.ProviderDisplayName(dbConnector.Provider)
|
||||||
newName := displayName + " " + instanceName
|
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("source_id", source.ID.String()),
|
||||||
log.String("old_name", source.Name),
|
log.String("old_name", source.Name),
|
||||||
log.String("new_name", newName),
|
log.String("new_name", newName),
|
||||||
|
|||||||
@@ -95,8 +95,7 @@ func blockingCallLLM(ctx context.Context, agent *Agent, req *llm.ChatCompletionR
|
|||||||
// Some providers (e.g. Anthropic) require streaming for large
|
// Some providers (e.g. Anthropic) require streaming for large
|
||||||
// max_tokens or when thinking is enabled. Fall back to streaming
|
// max_tokens or when thinking is enabled. Fall back to streaming
|
||||||
// transparently when the blocking call returns ErrStreamingRequired.
|
// transparently when the blocking call returns ErrStreamingRequired.
|
||||||
var streamRequired *llm.ErrStreamingRequired
|
if _, ok := errors.AsType[*llm.ErrStreamingRequired](err); !ok {
|
||||||
if !errors.As(err, &streamRequired) {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.",
|
"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) {
|
func(ctx context.Context, p downloadPDFParams) (agent.ToolResult, error) {
|
||||||
if err := validatePublicURL(p.URL); err != nil {
|
if err := validatePublicURL(p.URL); err != nil {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot create request: %s", err),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot create request: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot download PDF: %s", err),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot download PDF: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("PDF download returned status %d", resp.StatusCode),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("PDF download returned status %d", resp.StatusCode),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read PDF into memory (max 20MB).
|
// Read PDF into memory (max 20MB).
|
||||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 20*1024*1024))
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 20*1024*1024))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot read PDF body: %s", err),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot read PDF body: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to temp file for pdfcpu.
|
// Write to temp file for pdfcpu.
|
||||||
tmpDir, err := os.MkdirTemp("", "pdf-extract-*")
|
tmpDir, err := os.MkdirTemp("", "pdf-extract-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot create temp dir: %s", err),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot create temp dir: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = os.RemoveAll(tmpDir) }()
|
defer func() { _ = os.RemoveAll(tmpDir) }()
|
||||||
|
|
||||||
tmpFile := filepath.Join(tmpDir, "input.pdf")
|
tmpFile := filepath.Join(tmpDir, "input.pdf")
|
||||||
if err := os.WriteFile(tmpFile, body, 0o600); err != nil {
|
if err := os.WriteFile(tmpFile, body, 0o600); err != nil {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot write temp file: %s", err),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot write temp file: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get page count.
|
// Get page count.
|
||||||
@@ -111,24 +125,30 @@ func DownloadPDFTool() agent.Tool {
|
|||||||
|
|
||||||
pageCount, err := api.PageCountFile(tmpFile)
|
pageCount, err := api.PageCountFile(tmpFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot read PDF: %s", err),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot read PDF: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract content to output dir.
|
// Extract content to output dir.
|
||||||
outDir := filepath.Join(tmpDir, "out")
|
outDir := filepath.Join(tmpDir, "out")
|
||||||
if err := os.MkdirAll(outDir, 0o700); err != nil {
|
if err := os.MkdirAll(outDir, 0o700); err != nil {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot create output dir: %s", err),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot create output dir: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := bytes.NewReader(body)
|
reader := bytes.NewReader(body)
|
||||||
if err := api.ExtractContent(reader, outDir, "content", nil, conf); err != nil {
|
if err := api.ExtractContent(reader, outDir, "content", nil, conf); err != nil {
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot extract PDF content: %s", err),
|
downloadPDFResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot extract PDF content: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read all extracted content files.
|
// Read all extracted content files.
|
||||||
@@ -154,10 +174,12 @@ func DownloadPDFTool() agent.Tool {
|
|||||||
text = text[:maxTextLength] + "\n[... truncated]"
|
text = text[:maxTextLength] + "\n[... truncated]"
|
||||||
}
|
}
|
||||||
|
|
||||||
return agent.ResultJSON(downloadPDFResult{
|
return agent.ResultJSON(
|
||||||
Text: text,
|
downloadPDFResult{
|
||||||
PageCount: pageCount,
|
Text: text,
|
||||||
}), nil
|
PageCount: pageCount,
|
||||||
|
},
|
||||||
|
), nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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.",
|
"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) {
|
func(ctx context.Context, p robotsParams) (agent.ToolResult, error) {
|
||||||
if err := validatePublicDomain(p.Domain); err != nil {
|
if err := validatePublicDomain(p.Domain); err != nil {
|
||||||
return agent.ResultJSON(robotsResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
robotsResult{
|
||||||
ErrorDetail: fmt.Sprintf("domain not allowed: %s", err),
|
Found: false,
|
||||||
}), nil
|
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 {
|
if err != nil {
|
||||||
return agent.ResultJSON(robotsResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
robotsResult{
|
||||||
ErrorDetail: fmt.Sprintf("cannot create request: %s", err),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot create request: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(robotsResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
robotsResult{
|
||||||
ErrorDetail: fmt.Sprintf("cannot fetch robots.txt: %s", err),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot fetch robots.txt: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return agent.ResultJSON(robotsResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
robotsResult{
|
||||||
ErrorDetail: fmt.Sprintf("robots.txt returned status %d", resp.StatusCode),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("robots.txt returned status %d", resp.StatusCode),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var result robotsResult
|
var result robotsResult
|
||||||
|
|||||||
@@ -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).",
|
"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) {
|
func(ctx context.Context, p sitemapParams) (agent.ToolResult, error) {
|
||||||
if err := validatePublicURL(p.URL); err != nil {
|
if err := validatePublicURL(p.URL); err != nil {
|
||||||
return agent.ResultJSON(sitemapResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
sitemapResult{
|
||||||
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(sitemapResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
sitemapResult{
|
||||||
ErrorDetail: fmt.Sprintf("cannot create request: %s", err),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot create request: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(sitemapResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
sitemapResult{
|
||||||
ErrorDetail: fmt.Sprintf("cannot fetch sitemap: %s", err),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot fetch sitemap: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return agent.ResultJSON(sitemapResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
sitemapResult{
|
||||||
ErrorDetail: fmt.Sprintf("sitemap returned status %d", resp.StatusCode),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("sitemap returned status %d", resp.StatusCode),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var reader io.Reader = resp.Body
|
var reader io.Reader = resp.Body
|
||||||
@@ -88,10 +96,12 @@ func FetchSitemapTool() agent.Tool {
|
|||||||
resp.Header.Get("Content-Encoding") == "gzip" {
|
resp.Header.Get("Content-Encoding") == "gzip" {
|
||||||
gz, err := gzip.NewReader(resp.Body)
|
gz, err := gzip.NewReader(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(sitemapResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
sitemapResult{
|
||||||
ErrorDetail: fmt.Sprintf("cannot decompress gzipped sitemap: %s", err),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot decompress gzipped sitemap: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = gz.Close() }()
|
defer func() { _ = gz.Close() }()
|
||||||
@@ -104,10 +114,12 @@ func FetchSitemapTool() agent.Tool {
|
|||||||
|
|
||||||
urls, err := parseSitemapXML(reader)
|
urls, err := parseSitemapXML(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(sitemapResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
sitemapResult{
|
||||||
ErrorDetail: fmt.Sprintf("cannot parse sitemap XML: %s", err),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot parse sitemap XML: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := sitemapResult{
|
result := sitemapResult{
|
||||||
|
|||||||
@@ -80,11 +80,13 @@ func NavigateToURLTool(b *Browser) agent.Tool {
|
|||||||
return agent.ResultError(b.classifyError(ctx, p.URL, err)), nil
|
return agent.ResultError(b.classifyError(ctx, p.URL, err)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return agent.ResultJSON(navigateResult{
|
return agent.ResultJSON(
|
||||||
Title: title,
|
navigateResult{
|
||||||
Description: description,
|
Title: title,
|
||||||
FinalURL: finalURL,
|
Description: description,
|
||||||
}), nil
|
FinalURL: finalURL,
|
||||||
|
},
|
||||||
|
), nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,12 @@ func DiffDocumentsTool() agent.Tool {
|
|||||||
diff := computeDiff(linesA, linesB, labelA, labelB)
|
diff := computeDiff(linesA, linesB, labelA, labelB)
|
||||||
|
|
||||||
if diff.tooLarge {
|
if diff.tooLarge {
|
||||||
return agent.ResultJSON(diffResult{
|
return agent.ResultJSON(
|
||||||
HasDifferences: true,
|
diffResult{
|
||||||
ErrorDetail: diff.output,
|
HasDifferences: true,
|
||||||
}), nil
|
ErrorDetail: diff.output,
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := diffResult{
|
result := diffResult{
|
||||||
|
|||||||
@@ -65,9 +65,17 @@ func CheckWaybackTool() agent.Tool {
|
|||||||
var result waybackResult
|
var result waybackResult
|
||||||
|
|
||||||
// Check availability.
|
// 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 {
|
if err != nil {
|
||||||
result.ErrorDetail = fmt.Sprintf("cannot check Wayback Machine availability: %s", err)
|
result.ErrorDetail = fmt.Sprintf("cannot check Wayback Machine availability: %s", err)
|
||||||
return agent.ResultJSON(result), nil
|
return agent.ResultJSON(result), nil
|
||||||
|
|||||||
@@ -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.",
|
"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) {
|
func(ctx context.Context, p corsParams) (agent.ToolResult, error) {
|
||||||
if err := netcheck.ValidatePublicURL(p.URL); err != nil {
|
if err := netcheck.ValidatePublicURL(p.URL); err != nil {
|
||||||
return agent.ResultJSON(corsResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
corsResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
@@ -87,9 +89,11 @@ func CheckCORSTool() agent.Tool {
|
|||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(corsResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot build request: %s", err),
|
corsResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot build request: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Origin", p.Origin)
|
req.Header.Set("Origin", p.Origin)
|
||||||
@@ -97,9 +101,11 @@ func CheckCORSTool() agent.Tool {
|
|||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(corsResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err),
|
corsResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|||||||
@@ -81,16 +81,20 @@ func AnalyzeCSPTool() agent.Tool {
|
|||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(cspResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", p.URL, err),
|
cspResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", p.URL, err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(cspResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err),
|
cspResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|||||||
@@ -73,10 +73,12 @@ func CheckDMARCTool() agent.Tool {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(dmarcResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
dmarcResult{
|
||||||
ErrorDetail: fmt.Sprintf("cannot lookup DMARC record: %s", err),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot lookup DMARC record: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, answer := range answers {
|
for _, answer := range answers {
|
||||||
|
|||||||
@@ -61,10 +61,12 @@ func CheckDNSSECTool() agent.Tool {
|
|||||||
withDNSSEC(),
|
withDNSSEC(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(dnssecResult{
|
return agent.ResultJSON(
|
||||||
Enabled: false,
|
dnssecResult{
|
||||||
ErrorDetail: fmt.Sprintf("cannot query DNSKEY records: %s", err),
|
Enabled: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot query DNSKEY records: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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.",
|
"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) {
|
func(ctx context.Context, p headersParams) (agent.ToolResult, error) {
|
||||||
if err := netcheck.ValidatePublicURL(p.URL); err != nil {
|
if err := netcheck.ValidatePublicURL(p.URL); err != nil {
|
||||||
return agent.ResultJSON(headersResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
headersResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
@@ -94,11 +97,19 @@ func CheckSecurityHeadersTool() agent.Tool {
|
|||||||
// First check the HTTP version to detect HTTP→HTTPS redirect.
|
// First check the HTTP version to detect HTTP→HTTPS redirect.
|
||||||
redirectsToHTTPS := false
|
redirectsToHTTPS := false
|
||||||
|
|
||||||
httpURL := p.URL
|
parsedURL, err := url.Parse(p.URL)
|
||||||
if after, ok := strings.CutPrefix(httpURL, "https://"); ok {
|
if err != nil {
|
||||||
httpURL = "http://" + after
|
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)
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, httpURL, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
httpResp, err := client.Do(httpReq)
|
httpResp, err := client.Do(httpReq)
|
||||||
@@ -114,25 +125,28 @@ func CheckSecurityHeadersTool() agent.Tool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now check the HTTPS version for the actual security headers.
|
// Now check the HTTPS version for the actual security headers.
|
||||||
httpsURL := p.URL
|
httpsParsed := *parsedURL
|
||||||
if after, ok := strings.CutPrefix(httpsURL, "http://"); ok {
|
httpsParsed.Scheme = "https"
|
||||||
httpsURL = "https://" + after
|
httpsURL := httpsParsed.String()
|
||||||
}
|
|
||||||
|
|
||||||
followClient := &http.Client{Timeout: 10 * time.Second}
|
followClient := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
|
||||||
httpsReq, err := http.NewRequestWithContext(ctx, http.MethodGet, httpsURL, nil)
|
httpsReq, err := http.NewRequestWithContext(ctx, http.MethodGet, httpsURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(headersResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", httpsURL, err),
|
headersResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", httpsURL, err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := followClient.Do(httpsReq)
|
resp, err := followClient.Do(httpsReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(headersResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", httpsURL, err),
|
headersResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", httpsURL, err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|||||||
@@ -61,34 +61,48 @@ func CheckBreachesTool() agent.Tool {
|
|||||||
func(ctx context.Context, p hibpParams) (agent.ToolResult, error) {
|
func(ctx context.Context, p hibpParams) (agent.ToolResult, error) {
|
||||||
client := &http.Client{Timeout: 10 * time.Second}
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
hibpURL, err := url.Parse("https://haveibeenpwned.com/api/v3/breaches")
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
"https://haveibeenpwned.com/api/v3/breaches?domain="+url.QueryEscape(p.Domain),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(hibpResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot create request: %s", err),
|
hibpResult{
|
||||||
}), nil
|
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")
|
req.Header.Set("User-Agent", "Probo-Vendor-Assessment")
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(hibpResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot fetch breaches: %s", err),
|
hibpResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot fetch breaches: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(hibpResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot read response: %s", err),
|
hibpResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot read response: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
@@ -96,23 +110,29 @@ func CheckBreachesTool() agent.Tool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return agent.ResultJSON(hibpResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("HIBP API returned status %d", resp.StatusCode),
|
hibpResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("HIBP API returned status %d", resp.StatusCode),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var breaches []breach
|
var breaches []breach
|
||||||
if err := json.Unmarshal(body, &breaches); err != nil {
|
if err := json.Unmarshal(body, &breaches); err != nil {
|
||||||
return agent.ResultJSON(hibpResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot parse response: %s", err),
|
hibpResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot parse response: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return agent.ResultJSON(hibpResult{
|
return agent.ResultJSON(
|
||||||
Found: len(breaches) > 0,
|
hibpResult{
|
||||||
Count: len(breaches),
|
Found: len(breaches) > 0,
|
||||||
Breaches: breaches,
|
Count: len(breaches),
|
||||||
}), nil
|
Breaches: breaches,
|
||||||
|
},
|
||||||
|
), nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,10 +77,12 @@ func CheckSPFTool() agent.Tool {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(spfResult{
|
return agent.ResultJSON(
|
||||||
Found: false,
|
spfResult{
|
||||||
ErrorDetail: fmt.Sprintf("cannot lookup SPF record: %s", err),
|
Found: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot lookup SPF record: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var spfRecords []string
|
var spfRecords []string
|
||||||
@@ -100,21 +102,25 @@ func CheckSPFTool() agent.Tool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(spfRecords) > 1 {
|
if len(spfRecords) > 1 {
|
||||||
return agent.ResultJSON(spfResult{
|
return agent.ResultJSON(
|
||||||
Found: true,
|
spfResult{
|
||||||
ErrorDetail: fmt.Sprintf("multiple SPF records found (%d); this is an invalid configuration per RFC 7208", len(spfRecords)),
|
Found: true,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("multiple SPF records found (%d); this is an invalid configuration per RFC 7208", len(spfRecords)),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(spfRecords) == 1 {
|
if len(spfRecords) == 1 {
|
||||||
record := spfRecords[0]
|
record := spfRecords[0]
|
||||||
|
|
||||||
return agent.ResultJSON(spfResult{
|
return agent.ResultJSON(
|
||||||
Found: true,
|
spfResult{
|
||||||
RawRecord: record,
|
Found: true,
|
||||||
Policy: parseSPFPolicy(record),
|
RawRecord: record,
|
||||||
Mechanisms: record,
|
Policy: parseSPFPolicy(record),
|
||||||
}), nil
|
Mechanisms: record,
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return agent.ResultJSON(spfResult{Found: false}), nil
|
return agent.ResultJSON(spfResult{Found: false}), nil
|
||||||
|
|||||||
@@ -66,10 +66,12 @@ func CheckSSLCertificateTool() agent.Tool {
|
|||||||
"Check the SSL/TLS certificate for a domain, returning issuer, expiry, protocol version, and validity.",
|
"Check the SSL/TLS certificate for a domain, returning issuer, expiry, protocol version, and validity.",
|
||||||
func(ctx context.Context, p sslParams) (agent.ToolResult, error) {
|
func(ctx context.Context, p sslParams) (agent.ToolResult, error) {
|
||||||
if err := netcheck.ValidatePublicDomain(p.Domain); err != nil {
|
if err := netcheck.ValidatePublicDomain(p.Domain); err != nil {
|
||||||
return agent.ResultJSON(sslResult{
|
return agent.ResultJSON(
|
||||||
Valid: false,
|
sslResult{
|
||||||
ErrorDetail: fmt.Sprintf("domain not allowed: %s", err),
|
Valid: false,
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("domain not allowed: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a certificate inspection tool: we intentionally
|
// This is a certificate inspection tool: we intentionally
|
||||||
@@ -96,20 +98,24 @@ func CheckSSLCertificateTool() agent.Tool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(sslResult{
|
return agent.ResultJSON(
|
||||||
Valid: false,
|
sslResult{
|
||||||
ErrorDetail: err.Error(),
|
Valid: false,
|
||||||
}), nil
|
ErrorDetail: err.Error(),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = conn.Close() }()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
state := conn.ConnectionState()
|
state := conn.ConnectionState()
|
||||||
if len(state.PeerCertificates) == 0 {
|
if len(state.PeerCertificates) == 0 {
|
||||||
return agent.ResultJSON(sslResult{
|
return agent.ResultJSON(
|
||||||
Valid: false,
|
sslResult{
|
||||||
ErrorDetail: "no peer certificates",
|
Valid: false,
|
||||||
}), nil
|
ErrorDetail: "no peer certificates",
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := state.PeerCertificates[0]
|
cert := state.PeerCertificates[0]
|
||||||
|
|||||||
@@ -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.",
|
"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) {
|
func(ctx context.Context, p whoisParams) (agent.ToolResult, error) {
|
||||||
if err := netcheck.ValidatePublicDomain(p.Domain); err != nil {
|
if err := netcheck.ValidatePublicDomain(p.Domain); err != nil {
|
||||||
return agent.ResultJSON(whoisResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("domain not allowed: %s", err),
|
whoisResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("domain not allowed: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: query IANA to find the referral WHOIS server.
|
// Step 1: query IANA to find the referral WHOIS server.
|
||||||
referral, err := queryWhois(ctx, "whois.iana.org:43", p.Domain)
|
referral, err := queryWhois(ctx, "whois.iana.org:43", p.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(whoisResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot query IANA WHOIS: %s", err),
|
whoisResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot query IANA WHOIS: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
whoisServer := parseWhoisField(referral, "refer")
|
whoisServer := parseWhoisField(referral, "refer")
|
||||||
@@ -87,17 +91,21 @@ func CheckWhoisTool() agent.Tool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := netcheck.ValidatePublicDomain(whoisHost); err != nil {
|
if err := netcheck.ValidatePublicDomain(whoisHost); err != nil {
|
||||||
return agent.ResultJSON(whoisResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("WHOIS referral server not allowed: %s", err),
|
whoisResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("WHOIS referral server not allowed: %s", err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: query the registrar's WHOIS server.
|
// Step 2: query the registrar's WHOIS server.
|
||||||
raw, err := queryWhois(ctx, whoisServer, p.Domain)
|
raw, err := queryWhois(ctx, whoisServer, p.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return agent.ResultJSON(whoisResult{
|
return agent.ResultJSON(
|
||||||
ErrorDetail: fmt.Sprintf("cannot query WHOIS server %s: %s", whoisServer, err),
|
whoisResult{
|
||||||
}), nil
|
ErrorDetail: fmt.Sprintf("cannot query WHOIS server %s: %s", whoisServer, err),
|
||||||
|
},
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := parseWhoisResponse(raw)
|
result := parseWhoisResponse(raw)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package awsconfig
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"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")
|
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) {
|
func(options *endpointcreds.Options) {
|
||||||
options.HTTPClient = httpClient
|
options.HTTPClient = httpClient
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -247,116 +247,146 @@ func (b *Builder) Build() (*probodconfig.FullConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if slackClientID := b.getEnv("CONNECTOR_SLACK_CLIENT_ID"); slackClientID != "" {
|
if slackClientID := b.getEnv("CONNECTOR_SLACK_CLIENT_ID"); slackClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "SLACK",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "SLACK",
|
||||||
ClientID: slackClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_SLACK_CLIENT_SECRET"),
|
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 != "" {
|
if hubspotClientID := b.getEnv("CONNECTOR_HUBSPOT_CLIENT_ID"); hubspotClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "HUBSPOT",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "HUBSPOT",
|
||||||
ClientID: hubspotClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_HUBSPOT_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: hubspotClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_HUBSPOT_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if docusignClientID := b.getEnv("CONNECTOR_DOCUSIGN_CLIENT_ID"); docusignClientID != "" {
|
if docusignClientID := b.getEnv("CONNECTOR_DOCUSIGN_CLIENT_ID"); docusignClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "DOCUSIGN",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "DOCUSIGN",
|
||||||
ClientID: docusignClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_DOCUSIGN_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: docusignClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_DOCUSIGN_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if notionClientID := b.getEnv("CONNECTOR_NOTION_CLIENT_ID"); notionClientID != "" {
|
if notionClientID := b.getEnv("CONNECTOR_NOTION_CLIENT_ID"); notionClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "NOTION",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "NOTION",
|
||||||
ClientID: notionClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_NOTION_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: notionClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_NOTION_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if githubClientID := b.getEnv("CONNECTOR_GITHUB_CLIENT_ID"); githubClientID != "" {
|
if githubClientID := b.getEnv("CONNECTOR_GITHUB_CLIENT_ID"); githubClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "GITHUB",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "GITHUB",
|
||||||
ClientID: githubClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_GITHUB_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: githubClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_GITHUB_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sentryClientID := b.getEnv("CONNECTOR_SENTRY_CLIENT_ID"); sentryClientID != "" {
|
if sentryClientID := b.getEnv("CONNECTOR_SENTRY_CLIENT_ID"); sentryClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "SENTRY",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "SENTRY",
|
||||||
ClientID: sentryClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_SENTRY_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: sentryClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_SENTRY_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if intercomClientID := b.getEnv("CONNECTOR_INTERCOM_CLIENT_ID"); intercomClientID != "" {
|
if intercomClientID := b.getEnv("CONNECTOR_INTERCOM_CLIENT_ID"); intercomClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "INTERCOM",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "INTERCOM",
|
||||||
ClientID: intercomClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_INTERCOM_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: intercomClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_INTERCOM_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if brexClientID := b.getEnv("CONNECTOR_BREX_CLIENT_ID"); brexClientID != "" {
|
if brexClientID := b.getEnv("CONNECTOR_BREX_CLIENT_ID"); brexClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "BREX",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "BREX",
|
||||||
ClientID: brexClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_BREX_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: brexClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_BREX_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if googleWorkspaceClientID := b.getEnv("CONNECTOR_GOOGLE_WORKSPACE_CLIENT_ID"); googleWorkspaceClientID != "" {
|
if googleWorkspaceClientID := b.getEnv("CONNECTOR_GOOGLE_WORKSPACE_CLIENT_ID"); googleWorkspaceClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "GOOGLE_WORKSPACE",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "GOOGLE_WORKSPACE",
|
||||||
ClientID: googleWorkspaceClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_GOOGLE_WORKSPACE_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: googleWorkspaceClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_GOOGLE_WORKSPACE_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if microsoft365ClientID := b.getEnv("CONNECTOR_MICROSOFT_365_CLIENT_ID"); microsoft365ClientID != "" {
|
if microsoft365ClientID := b.getEnv("CONNECTOR_MICROSOFT_365_CLIENT_ID"); microsoft365ClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "MICROSOFT_365",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "MICROSOFT_365",
|
||||||
ClientID: microsoft365ClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_MICROSOFT_365_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: microsoft365ClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_MICROSOFT_365_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, provider := range []string{
|
for _, provider := range []string{
|
||||||
@@ -374,28 +404,34 @@ func (b *Builder) Build() (*probodconfig.FullConfig, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: provider,
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: provider,
|
||||||
ClientID: clientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_" + provider + "_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
|
ClientID: clientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_" + provider + "_CLIENT_SECRET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vercel needs the operator-supplied integration slug to resolve the
|
// Vercel needs the operator-supplied integration slug to resolve the
|
||||||
// templated AuthURL ("https://vercel.com/integrations/{integration_slug}/new").
|
// templated AuthURL ("https://vercel.com/integrations/{integration_slug}/new").
|
||||||
if vercelClientID := b.getEnv("CONNECTOR_VERCEL_CLIENT_ID"); vercelClientID != "" {
|
if vercelClientID := b.getEnv("CONNECTOR_VERCEL_CLIENT_ID"); vercelClientID != "" {
|
||||||
cfg.Probod.Connectors = append(cfg.Probod.Connectors, probodconfig.ConnectorConfig{
|
cfg.Probod.Connectors = append(
|
||||||
Provider: "VERCEL",
|
cfg.Probod.Connectors,
|
||||||
Protocol: "oauth2",
|
probodconfig.ConnectorConfig{
|
||||||
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
Provider: "VERCEL",
|
||||||
ClientID: vercelClientID,
|
Protocol: "oauth2",
|
||||||
ClientSecret: b.getEnv("CONNECTOR_VERCEL_CLIENT_SECRET"),
|
RawConfig: probodconfig.ConnectorConfigOAuth2{
|
||||||
IntegrationSlug: b.getEnv("CONNECTOR_VERCEL_INTEGRATION_SLUG"),
|
ClientID: vercelClientID,
|
||||||
|
ClientSecret: b.getEnv("CONNECTOR_VERCEL_CLIENT_SECRET"),
|
||||||
|
IntegrationSlug: b.getEnv("CONNECTOR_VERCEL_INTEGRATION_SLUG"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
|
|||||||
@@ -32,10 +32,12 @@ func GenerateOAuth2SigningKey() (string, error) {
|
|||||||
return "", fmt.Errorf("generate RSA key: %w", err)
|
return "", fmt.Errorf("generate RSA key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyPEM := pem.EncodeToMemory(&pem.Block{
|
keyPEM := pem.EncodeToMemory(
|
||||||
Type: "RSA PRIVATE KEY",
|
&pem.Block{
|
||||||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
Type: "RSA PRIVATE KEY",
|
||||||
})
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return string(keyPEM), nil
|
return string(keyPEM), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,15 +62,19 @@ func GenerateSAMLCertificate() (cert string, key string, err error) {
|
|||||||
return "", "", fmt.Errorf("create certificate: %w", err)
|
return "", "", fmt.Errorf("create certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
certPEM := pem.EncodeToMemory(&pem.Block{
|
certPEM := pem.EncodeToMemory(
|
||||||
Type: "CERTIFICATE",
|
&pem.Block{
|
||||||
Bytes: certDER,
|
Type: "CERTIFICATE",
|
||||||
})
|
Bytes: certDER,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
keyPEM := pem.EncodeToMemory(&pem.Block{
|
keyPEM := pem.EncodeToMemory(
|
||||||
Type: "RSA PRIVATE KEY",
|
&pem.Block{
|
||||||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
Type: "RSA PRIVATE KEY",
|
||||||
})
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return string(certPEM), string(keyPEM), nil
|
return string(certPEM), string(keyPEM), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"codeberg.org/miekg/dns"
|
||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/crypto/cipher"
|
"go.probo.inc/probo/pkg/crypto/cipher"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
|
||||||
"codeberg.org/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
@@ -245,10 +245,12 @@ func (c *Client) doUploadRequest(
|
|||||||
writer := multipart.NewWriter(&buf)
|
writer := multipart.NewWriter(&buf)
|
||||||
|
|
||||||
// Part 1: operations
|
// Part 1: operations
|
||||||
operationsJSON, err := json.Marshal(graphQLRequest{
|
operationsJSON, err := json.Marshal(
|
||||||
Query: query,
|
graphQLRequest{
|
||||||
Variables: variables,
|
Query: query,
|
||||||
})
|
Variables: variables,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot marshal operations: %w", err)
|
return nil, fmt.Errorf("cannot marshal operations: %w", err)
|
||||||
}
|
}
|
||||||
@@ -258,9 +260,11 @@ func (c *Client) doUploadRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Part 2: map
|
// Part 2: map
|
||||||
mapJSON, err := json.Marshal(map[string][]string{
|
mapJSON, err := json.Marshal(
|
||||||
"0": {varPath},
|
map[string][]string{
|
||||||
})
|
"0": {varPath},
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot marshal map: %w", err)
|
return nil, fmt.Errorf("cannot marshal map: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,10 +338,12 @@ INSERT INTO connectors (
|
|||||||
|
|
||||||
if c.Provider == ConnectorProviderSlack {
|
if c.Provider == ConnectorProviderSlack {
|
||||||
if slackConn, ok := c.Connection.(*connector.SlackConnection); ok {
|
if slackConn, ok := c.Connection.(*connector.SlackConnection); ok {
|
||||||
_ = c.SetSettings(&SlackConnectorSettings{
|
_ = c.SetSettings(
|
||||||
Channel: slackConn.Settings.Channel,
|
&SlackConnectorSettings{
|
||||||
ChannelID: slackConn.Settings.ChannelID,
|
Channel: slackConn.Settings.Channel,
|
||||||
})
|
ChannelID: slackConn.Settings.ChannelID,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,10 +556,12 @@ WHERE
|
|||||||
|
|
||||||
if c.Provider == ConnectorProviderSlack {
|
if c.Provider == ConnectorProviderSlack {
|
||||||
if slackConn, ok := c.Connection.(*connector.SlackConnection); ok {
|
if slackConn, ok := c.Connection.(*connector.SlackConnection); ok {
|
||||||
_ = c.SetSettings(&SlackConnectorSettings{
|
_ = c.SetSettings(
|
||||||
Channel: slackConn.Settings.Channel,
|
&SlackConnectorSettings{
|
||||||
ChannelID: slackConn.Settings.ChannelID,
|
Channel: slackConn.Settings.Channel,
|
||||||
})
|
ChannelID: slackConn.Settings.ChannelID,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/connector"
|
"go.probo.inc/probo/pkg/connector"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -822,8 +822,7 @@ VALUES (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_framework_ref_unique" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_framework_ref_unique" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
@@ -892,8 +891,7 @@ WHERE %s
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_framework_ref_unique" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_framework_ref_unique" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,8 +72,7 @@ VALUES (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_policies_pkey" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "controls_policies_pkey" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -458,8 +458,7 @@ INSERT INTO custom_domains (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "custom_domains_domain_key" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "custom_domains_domain_key" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -453,8 +453,7 @@ INSERT INTO processing_activity_data_protection_impact_assessments (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "processing_activity_dpias_processing_activity_id_snapshot_id_uniq" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "processing_activity_dpias_processing_activity_id_snapshot_id_uniq" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,13 +130,16 @@ WHEN NOT MATCHED BY SOURCE
|
|||||||
|
|
||||||
result := make(DocumentDefaultApprovers, 0, len(approverProfileIDs))
|
result := make(DocumentDefaultApprovers, 0, len(approverProfileIDs))
|
||||||
for _, profileID := range approverProfileIDs {
|
for _, profileID := range approverProfileIDs {
|
||||||
result = append(result, &DocumentDefaultApprover{
|
result = append(
|
||||||
DocumentID: documentID,
|
result,
|
||||||
ApproverProfileID: profileID,
|
&DocumentDefaultApprover{
|
||||||
OrganizationID: organizationID,
|
DocumentID: documentID,
|
||||||
CreatedAt: now,
|
ApproverProfileID: profileID,
|
||||||
UpdatedAt: now,
|
OrganizationID: organizationID,
|
||||||
})
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
*das = result
|
*das = result
|
||||||
|
|||||||
@@ -273,8 +273,7 @@ VALUES (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" {
|
if pgErr.Code == "23505" {
|
||||||
if pgErr.ConstraintName == "document_versions_document_id_major_minor_key" || pgErr.ConstraintName == "document_one_active_version_idx" {
|
if pgErr.ConstraintName == "document_versions_document_id_major_minor_key" || pgErr.ConstraintName == "document_one_active_version_idx" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
@@ -589,9 +588,13 @@ LIMIT 1
|
|||||||
FOR UPDATE OF dv SKIP LOCKED;
|
FOR UPDATE OF dv SKIP LOCKED;
|
||||||
`
|
`
|
||||||
|
|
||||||
rows, err := conn.Query(ctx, q, pgx.StrictNamedArgs{
|
rows, err := conn.Query(
|
||||||
"max_pdf_attempts": maxAttempts,
|
ctx,
|
||||||
})
|
q,
|
||||||
|
pgx.StrictNamedArgs{
|
||||||
|
"max_pdf_attempts": maxAttempts,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot query document versions: %w", err)
|
return fmt.Errorf("cannot query document versions: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,19 +328,22 @@ func (ds DocumentVersionApprovalDecisions) BulkInsert(
|
|||||||
|
|
||||||
rows := make([][]any, 0, len(ds))
|
rows := make([][]any, 0, len(ds))
|
||||||
for _, d := range ds {
|
for _, d := range ds {
|
||||||
rows = append(rows, []any{
|
rows = append(
|
||||||
d.ID,
|
rows,
|
||||||
scope.GetTenantID(),
|
[]any{
|
||||||
d.OrganizationID,
|
d.ID,
|
||||||
d.QuorumID,
|
scope.GetTenantID(),
|
||||||
d.ApproverID,
|
d.OrganizationID,
|
||||||
d.State,
|
d.QuorumID,
|
||||||
d.Comment,
|
d.ApproverID,
|
||||||
d.ElectronicSignatureID,
|
d.State,
|
||||||
d.DecidedAt,
|
d.Comment,
|
||||||
d.CreatedAt,
|
d.ElectronicSignatureID,
|
||||||
d.UpdatedAt,
|
d.DecidedAt,
|
||||||
})
|
d.CreatedAt,
|
||||||
|
d.UpdatedAt,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := conn.CopyFrom(
|
_, err := conn.CopyFrom(
|
||||||
|
|||||||
@@ -216,8 +216,7 @@ INSERT INTO document_version_signatures (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "policy_version_signatures_policy_version_id_signed_by_key" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "policy_version_signatures_policy_version_id_signed_by_key" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,20 +176,23 @@ func (emails Emails) BulkInsert(
|
|||||||
|
|
||||||
rows := make([][]any, 0, len(emails))
|
rows := make([][]any, 0, len(emails))
|
||||||
for _, e := range emails {
|
for _, e := range emails {
|
||||||
rows = append(rows, []any{
|
rows = append(
|
||||||
e.ID,
|
rows,
|
||||||
e.RecipientEmail,
|
[]any{
|
||||||
e.RecipientName,
|
e.ID,
|
||||||
e.SenderName,
|
e.RecipientEmail,
|
||||||
e.ReplyTo,
|
e.RecipientName,
|
||||||
e.UnsubscribeURL,
|
e.SenderName,
|
||||||
e.MailingListUpdateID,
|
e.ReplyTo,
|
||||||
e.Subject,
|
e.UnsubscribeURL,
|
||||||
e.TextBody,
|
e.MailingListUpdateID,
|
||||||
e.HtmlBody,
|
e.Subject,
|
||||||
e.CreatedAt,
|
e.TextBody,
|
||||||
e.UpdatedAt,
|
e.HtmlBody,
|
||||||
})
|
e.CreatedAt,
|
||||||
|
e.UpdatedAt,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := conn.CopyFrom(
|
_, err := conn.CopyFrom(
|
||||||
|
|||||||
@@ -204,8 +204,7 @@ VALUES (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "evidences_reference_id_key" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "evidences_reference_id_key" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,26 +20,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
EvidenceState uint8
|
EvidenceState string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EvidenceStateRequested EvidenceState = iota
|
EvidenceStateRequested EvidenceState = "REQUESTED"
|
||||||
EvidenceStateFulfilled
|
EvidenceStateFulfilled EvidenceState = "FULFILLED"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (es EvidenceState) MarshalText() ([]byte, error) {
|
func (es EvidenceState) MarshalText() ([]byte, error) {
|
||||||
return []byte(es.String()), nil
|
return []byte(es), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *EvidenceState) UnmarshalText(data []byte) error {
|
func (es *EvidenceState) UnmarshalText(data []byte) error {
|
||||||
val := string(data)
|
val := EvidenceState(data)
|
||||||
|
|
||||||
switch val {
|
switch val {
|
||||||
case EvidenceStateRequested.String():
|
case EvidenceStateRequested, EvidenceStateFulfilled:
|
||||||
*es = EvidenceStateRequested
|
*es = val
|
||||||
case EvidenceStateFulfilled.String():
|
|
||||||
*es = EvidenceStateFulfilled
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid EvidenceState value: %q", val)
|
return fmt.Errorf("invalid EvidenceState value: %q", val)
|
||||||
}
|
}
|
||||||
@@ -48,16 +46,7 @@ func (es *EvidenceState) UnmarshalText(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (es EvidenceState) String() string {
|
func (es EvidenceState) String() string {
|
||||||
var val string
|
return string(es)
|
||||||
|
|
||||||
switch es {
|
|
||||||
case EvidenceStateRequested:
|
|
||||||
val = "REQUESTED"
|
|
||||||
case EvidenceStateFulfilled:
|
|
||||||
val = "FULFILLED"
|
|
||||||
}
|
|
||||||
|
|
||||||
return val
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *EvidenceState) Scan(value any) error {
|
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) {
|
func (es EvidenceState) Value() (driver.Value, error) {
|
||||||
return es.String(), nil
|
return string(es), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,26 +20,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
EvidenceType uint8
|
EvidenceType string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EvidenceTypeFile EvidenceType = iota
|
EvidenceTypeFile EvidenceType = "FILE"
|
||||||
EvidenceTypeLink
|
EvidenceTypeLink EvidenceType = "LINK"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (et EvidenceType) MarshalText() ([]byte, error) {
|
func (et EvidenceType) MarshalText() ([]byte, error) {
|
||||||
return []byte(et.String()), nil
|
return []byte(et), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (et *EvidenceType) UnmarshalText(data []byte) error {
|
func (et *EvidenceType) UnmarshalText(data []byte) error {
|
||||||
val := string(data)
|
val := EvidenceType(data)
|
||||||
|
|
||||||
switch val {
|
switch val {
|
||||||
case EvidenceTypeFile.String():
|
case EvidenceTypeFile, EvidenceTypeLink:
|
||||||
*et = EvidenceTypeFile
|
*et = val
|
||||||
case EvidenceTypeLink.String():
|
|
||||||
*et = EvidenceTypeLink
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid EvidenceType value: %q", val)
|
return fmt.Errorf("invalid EvidenceType value: %q", val)
|
||||||
}
|
}
|
||||||
@@ -48,16 +46,7 @@ func (et *EvidenceType) UnmarshalText(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (et EvidenceType) String() string {
|
func (et EvidenceType) String() string {
|
||||||
var val string
|
return string(et)
|
||||||
|
|
||||||
switch et {
|
|
||||||
case EvidenceTypeFile:
|
|
||||||
val = "FILE"
|
|
||||||
case EvidenceTypeLink:
|
|
||||||
val = "LINK"
|
|
||||||
}
|
|
||||||
|
|
||||||
return val
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (et *EvidenceType) Scan(value any) error {
|
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) {
|
func (et EvidenceType) Value() (driver.Value, error) {
|
||||||
return et.String(), nil
|
return string(et), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,8 +232,7 @@ VALUES (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "files_file_key_key" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "files_file_key_key" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,8 +336,7 @@ VALUES (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "frameworks_org_ref_unique" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "frameworks_org_ref_unique" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,8 +204,7 @@ VALUES (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && strings.Contains(pgErr.ConstraintName, "email_address") {
|
if pgErr.Code == "23505" && strings.Contains(pgErr.ConstraintName, "email_address") {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,9 @@ import (
|
|||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -630,8 +629,7 @@ VALUES (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "mitigations_org_ref_unique" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "mitigations_org_ref_unique" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,8 +72,7 @@ VALUES (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "measures_documents_pkey" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "measures_documents_pkey" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
MeasureState uint8
|
MeasureState string
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MeasureStateNotStarted MeasureState = iota
|
MeasureStateNotStarted MeasureState = "NOT_STARTED"
|
||||||
MeasureStateInProgress
|
MeasureStateInProgress MeasureState = "IN_PROGRESS"
|
||||||
MeasureStateNotApplicable
|
MeasureStateNotApplicable MeasureState = "NOT_APPLICABLE"
|
||||||
MeasureStateImplemented
|
MeasureStateImplemented MeasureState = "IMPLEMENTED"
|
||||||
MeasureStateUnknown
|
MeasureStateUnknown MeasureState = "UNKNOWN"
|
||||||
MeasureStateNotImplemented
|
MeasureStateNotImplemented MeasureState = "NOT_IMPLEMENTED"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MeasureStates() []MeasureState {
|
func MeasureStates() []MeasureState {
|
||||||
@@ -44,25 +44,17 @@ func MeasureStates() []MeasureState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ms MeasureState) MarshalText() ([]byte, error) {
|
func (ms MeasureState) MarshalText() ([]byte, error) {
|
||||||
return []byte(ms.String()), nil
|
return []byte(ms), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MeasureState) UnmarshalText(data []byte) error {
|
func (ms *MeasureState) UnmarshalText(data []byte) error {
|
||||||
val := string(data)
|
val := MeasureState(data)
|
||||||
|
|
||||||
switch val {
|
switch val {
|
||||||
case MeasureStateNotStarted.String():
|
case MeasureStateNotStarted, MeasureStateInProgress,
|
||||||
*ms = MeasureStateNotStarted
|
MeasureStateNotApplicable, MeasureStateImplemented,
|
||||||
case MeasureStateInProgress.String():
|
MeasureStateUnknown, MeasureStateNotImplemented:
|
||||||
*ms = MeasureStateInProgress
|
*ms = val
|
||||||
case MeasureStateNotApplicable.String():
|
|
||||||
*ms = MeasureStateNotApplicable
|
|
||||||
case MeasureStateImplemented.String():
|
|
||||||
*ms = MeasureStateImplemented
|
|
||||||
case MeasureStateUnknown.String():
|
|
||||||
*ms = MeasureStateUnknown
|
|
||||||
case MeasureStateNotImplemented.String():
|
|
||||||
*ms = MeasureStateNotImplemented
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid MeasureState value: %q", val)
|
return fmt.Errorf("invalid MeasureState value: %q", val)
|
||||||
}
|
}
|
||||||
@@ -71,24 +63,7 @@ func (ms *MeasureState) UnmarshalText(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ms MeasureState) String() string {
|
func (ms MeasureState) String() string {
|
||||||
var val string
|
return string(ms)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MeasureState) Scan(value any) error {
|
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) {
|
func (ms MeasureState) Value() (driver.Value, error) {
|
||||||
return ms.String(), nil
|
return string(ms), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ VALUES (@id, @organization_id, @used_at, @expires_at)
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, query, args)
|
_, err := conn.Exec(ctx, query, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok && pgErr.Code == "23505" && pgErr.ConstraintName == "iam_saml_assertions_pkey" {
|
||||||
if errors.As(err, &pgErr) && pgErr.Code == "23505" && pgErr.ConstraintName == "iam_saml_assertions_pkey" {
|
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -333,8 +333,7 @@ INSERT INTO iam_saml_configurations (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "idx_saml_config_domain_org_unique" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "idx_saml_config_domain_org_unique" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,8 +268,7 @@ INSERT INTO iam_scim_configurations (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "iam_scim_configurations_organization_unique" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "iam_scim_configurations_organization_unique" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,9 @@ import (
|
|||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -252,8 +251,7 @@ RETURNING rank, priority_rank;
|
|||||||
|
|
||||||
err := conn.QueryRow(ctx, q, args).Scan(&t.Rank, &t.PriorityRank)
|
err := conn.QueryRow(ctx, q, args).Scan(&t.Rank, &t.PriorityRank)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "tasks_reference_id_unique" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "tasks_reference_id_unique" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,8 +94,7 @@ INSERT INTO iam_tokens(
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "iam_tokens_hashed_value_unique" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "iam_tokens_hashed_value_unique" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -452,8 +452,7 @@ INSERT INTO processing_activity_transfer_impact_assessments (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "processing_activity_tias_processing_activity_id_snapshot_id_uniq" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "processing_activity_tias_processing_activity_id_snapshot_id_uniq" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -327,8 +327,7 @@ INSERT INTO trust_centers (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "trust_centers_slug_key" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "trust_centers_slug_key" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,8 +208,7 @@ INSERT INTO trust_center_accesses (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "trust_center_accesses_trust_center_id_email_key" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "trust_center_accesses_trust_center_id_email_key" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,8 +271,7 @@ INSERT INTO trust_center_document_accesses (
|
|||||||
|
|
||||||
_, err := conn.Exec(ctx, q, args)
|
_, err := conn.Exec(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" {
|
if pgErr.Code == "23505" {
|
||||||
switch pgErr.ConstraintName {
|
switch pgErr.ConstraintName {
|
||||||
case "trust_center_document_accesse_trust_center_access_id_docume_key",
|
case "trust_center_document_accesse_trust_center_access_id_docume_key",
|
||||||
|
|||||||
@@ -171,8 +171,7 @@ RETURNING rank;
|
|||||||
|
|
||||||
err := conn.QueryRow(ctx, q, args).Scan(&t.Rank)
|
err := conn.QueryRow(ctx, q, args).Scan(&t.Rank)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var pgErr *pgconn.PgError
|
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok {
|
||||||
if errors.As(err, &pgErr) {
|
|
||||||
if pgErr.Code == "23505" && pgErr.ConstraintName == "trust_center_references_trust_center_id_rank_key" {
|
if pgErr.Code == "23505" && pgErr.ConstraintName == "trust_center_references_trust_center_id_rank_key" {
|
||||||
return ErrResourceAlreadyExists
|
return ErrResourceAlreadyExists
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,11 @@ import (
|
|||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
"go.gearno.de/kit/worker"
|
"go.gearno.de/kit/worker"
|
||||||
"go.gearno.de/x/ref"
|
"go.gearno.de/x/ref"
|
||||||
|
emails "go.probo.inc/probo/packages/emails"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/filemanager"
|
"go.probo.inc/probo/pkg/filemanager"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/mail"
|
"go.probo.inc/probo/pkg/mail"
|
||||||
|
|
||||||
emails "go.probo.inc/probo/packages/emails"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EmailPresenterConfigFunc resolves the emails.PresenterConfig for the
|
// EmailPresenterConfigFunc resolves the emails.PresenterConfig for the
|
||||||
|
|||||||
@@ -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) {
|
func (s AuthService) GetMagicLinkEmail(ctx context.Context, tokenString string) (mail.Addr, error) {
|
||||||
payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString)
|
payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errExpired *statelesstoken.ErrExpiredToken
|
if _, ok := errors.AsType[*statelesstoken.ErrExpiredToken](err); ok {
|
||||||
if errors.As(err, &errExpired) {
|
|
||||||
return mail.Nil, NewExpiredTokenError()
|
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)
|
payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errExpired *statelesstoken.ErrExpiredToken
|
if _, ok := errors.AsType[*statelesstoken.ErrExpiredToken](err); ok {
|
||||||
if errors.As(err, &errExpired) {
|
|
||||||
return nil, nil, nil, NewExpiredTokenError()
|
return nil, nil, nil, NewExpiredTokenError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,12 +101,11 @@ func (a *Authorizer) authorize(ctx context.Context, tx pg.Tx, params AuthorizePa
|
|||||||
*params.Session,
|
*params.Session,
|
||||||
membership.ID,
|
membership.ID,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*ErrSessionNotFound](err); ok {
|
||||||
errSessionNotFound *ErrSessionNotFound
|
return NewAssumptionRequiredError(params.Principal, membership.ID)
|
||||||
errSessionExpired *ErrSessionExpired
|
}
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errSessionNotFound) || errors.As(err, &errSessionExpired) {
|
if _, ok := errors.AsType[*ErrSessionExpired](err); ok {
|
||||||
return NewAssumptionRequiredError(params.Principal, membership.ID)
|
return NewAssumptionRequiredError(params.Principal, membership.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/iam/policy"
|
"go.probo.inc/probo/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
admin "google.golang.org/api/admin/directory/v1"
|
|
||||||
"google.golang.org/api/option"
|
|
||||||
|
|
||||||
scimclient "go.probo.inc/probo/pkg/iam/scim/bridge/client"
|
scimclient "go.probo.inc/probo/pkg/iam/scim/bridge/client"
|
||||||
"go.probo.inc/probo/pkg/iam/scim/bridge/provider"
|
"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)
|
var _ provider.Provider = (*Provider)(nil)
|
||||||
|
|||||||
@@ -396,8 +396,8 @@ func mapError(err error) error {
|
|||||||
return &llm.ErrStreamingRequired{Err: err}
|
return &llm.ErrStreamingRequired{Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiErr *anthropic.Error
|
apiErr, ok := errors.AsType[*anthropic.Error](err)
|
||||||
if !errors.As(err, &apiErr) {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -329,9 +329,8 @@ func mapStopReason(reason types.StopReason) llm.FinishReason {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mapError(err error) error {
|
func mapError(err error) error {
|
||||||
var respErr *smithyhttp.ResponseError
|
respErr, ok := errors.AsType[*smithyhttp.ResponseError](err)
|
||||||
if !errors.As(err, &respErr) {
|
if !ok {
|
||||||
// Check for common error types by message content.
|
|
||||||
msg := err.Error()
|
msg := err.Error()
|
||||||
if strings.Contains(msg, "throttling") || strings.Contains(msg, "ThrottlingException") {
|
if strings.Contains(msg, "throttling") || strings.Contains(msg, "ThrottlingException") {
|
||||||
return &llm.ErrRateLimit{Err: err}
|
return &llm.ErrRateLimit{Err: err}
|
||||||
|
|||||||
@@ -206,10 +206,13 @@ func (a *StreamAccumulator) Response() *ChatCompletionResponse {
|
|||||||
|
|
||||||
var parts []Part
|
var parts []Part
|
||||||
if thinking := a.thinking.String(); thinking != "" {
|
if thinking := a.thinking.String(); thinking != "" {
|
||||||
parts = append(parts, ThinkingPart{
|
parts = append(
|
||||||
Text: thinking,
|
parts,
|
||||||
Signature: a.thinkingSignature,
|
ThinkingPart{
|
||||||
})
|
Text: thinking,
|
||||||
|
Signature: a.thinkingSignature,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
parts = append(parts, TextPart{Text: a.content.String()})
|
parts = append(parts, TextPart{Text: a.content.String()})
|
||||||
|
|||||||
@@ -213,9 +213,10 @@ func buildMessages(messages []llm.Message) []openai.ChatCompletionMessageParamUn
|
|||||||
case llm.ImagePart:
|
case llm.ImagePart:
|
||||||
parts = append(
|
parts = append(
|
||||||
parts,
|
parts,
|
||||||
openai.ImageContentPart(openai.ChatCompletionContentPartImageImageURLParam{
|
openai.ImageContentPart(
|
||||||
URL: p.URL,
|
openai.ChatCompletionContentPartImageImageURLParam{
|
||||||
},
|
URL: p.URL,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
case llm.FilePart:
|
case llm.FilePart:
|
||||||
@@ -381,8 +382,8 @@ func mapFinishReason(reason string) llm.FinishReason {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mapError(err error) error {
|
func mapError(err error) error {
|
||||||
var apiErr *openai.Error
|
apiErr, ok := errors.AsType[*openai.Error](err)
|
||||||
if !errors.As(err, &apiErr) {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,9 +514,11 @@ func isReasoningModel(model string) bool {
|
|||||||
func buildFilePart(p llm.FilePart) openai.ChatCompletionContentPartUnionParam {
|
func buildFilePart(p llm.FilePart) openai.ChatCompletionContentPartUnionParam {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(p.MimeType, "image/"):
|
case strings.HasPrefix(p.MimeType, "image/"):
|
||||||
return openai.ImageContentPart(openai.ChatCompletionContentPartImageImageURLParam{
|
return openai.ImageContentPart(
|
||||||
URL: fmt.Sprintf("data:%s;base64,%s", p.MimeType, p.Data),
|
openai.ChatCompletionContentPartImageImageURLParam{
|
||||||
})
|
URL: fmt.Sprintf("data:%s;base64,%s", p.MimeType, p.Data),
|
||||||
|
},
|
||||||
|
)
|
||||||
case strings.HasPrefix(p.MimeType, "text/"):
|
case strings.HasPrefix(p.MimeType, "text/"):
|
||||||
decoded, err := base64.StdEncoding.DecodeString(p.Data)
|
decoded, err := base64.StdEncoding.DecodeString(p.Data)
|
||||||
if err != nil {
|
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)))
|
return openai.TextContentPart(fmt.Sprintf("File: %s\n\n%s", p.Filename, string(decoded)))
|
||||||
default:
|
default:
|
||||||
return openai.FileContentPart(openai.ChatCompletionContentPartFileFileParam{
|
return openai.FileContentPart(
|
||||||
FileData: param.NewOpt(fmt.Sprintf("data:%s;base64,%s", p.MimeType, p.Data)),
|
openai.ChatCompletionContentPartFileFileParam{
|
||||||
Filename: param.NewOpt(p.Filename),
|
FileData: param.NewOpt(fmt.Sprintf("data:%s;base64,%s", p.MimeType, p.Data)),
|
||||||
})
|
Filename: param.NewOpt(p.Filename),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ func (s FileService) UploadAndSaveFile(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
fileValidator *filevalidation.FileValidator,
|
fileValidator *filevalidation.FileValidator,
|
||||||
s3Metadata map[string]string,
|
s3Metadata map[string]string,
|
||||||
req *FileUpload) (*coredata.File, error) {
|
req *FileUpload,
|
||||||
|
) (*coredata.File, error) {
|
||||||
objectKey, err := uuid.NewV7()
|
objectKey, err := uuid.NewV7()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot generate object key: %w", err)
|
return nil, fmt.Errorf("cannot generate object key: %w", err)
|
||||||
|
|||||||
@@ -29,9 +29,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.probo.inc/probo/packages/emails"
|
|
||||||
pemutil "go.probo.inc/probo/pkg/crypto/pem"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
proxyproto "github.com/pires/go-proxyproto"
|
proxyproto "github.com/pires/go-proxyproto"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@@ -43,6 +40,7 @@ import (
|
|||||||
"go.gearno.de/kit/unit"
|
"go.gearno.de/kit/unit"
|
||||||
"go.gearno.de/kit/worker"
|
"go.gearno.de/kit/worker"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"go.probo.inc/probo/packages/emails"
|
||||||
"go.probo.inc/probo/pkg/accessreview"
|
"go.probo.inc/probo/pkg/accessreview"
|
||||||
"go.probo.inc/probo/pkg/awsconfig"
|
"go.probo.inc/probo/pkg/awsconfig"
|
||||||
"go.probo.inc/probo/pkg/baseurl"
|
"go.probo.inc/probo/pkg/baseurl"
|
||||||
@@ -53,6 +51,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/crypto/cipher"
|
"go.probo.inc/probo/pkg/crypto/cipher"
|
||||||
"go.probo.inc/probo/pkg/crypto/keys"
|
"go.probo.inc/probo/pkg/crypto/keys"
|
||||||
"go.probo.inc/probo/pkg/crypto/passwdhash"
|
"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/esign"
|
||||||
"go.probo.inc/probo/pkg/evidencedescriber"
|
"go.probo.inc/probo/pkg/evidencedescriber"
|
||||||
"go.probo.inc/probo/pkg/file"
|
"go.probo.inc/probo/pkg/file"
|
||||||
@@ -379,11 +378,14 @@ func (impl *Implm) Run(
|
|||||||
hasActive = true
|
hasActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
oauth2SigningKeys = append(oauth2SigningKeys, oauth2server.SigningKey{
|
oauth2SigningKeys = append(
|
||||||
PrivateKey: rsaKey,
|
oauth2SigningKeys,
|
||||||
KID: kid,
|
oauth2server.SigningKey{
|
||||||
Active: keyCfg.Active,
|
PrivateKey: rsaKey,
|
||||||
})
|
KID: kid,
|
||||||
|
Active: keyCfg.Active,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasActive {
|
if !hasActive {
|
||||||
@@ -1112,10 +1114,9 @@ func (impl *Implm) runTrustCenterServer(
|
|||||||
cert, err := certSelector.GetCertificate(hello)
|
cert, err := certSelector.GetCertificate(hello)
|
||||||
// Silently reject connections without SNI (load balancers, health checks, scanners)
|
// Silently reject connections without SNI (load balancers, health checks, scanners)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var noSNIErr *certmanager.NoSNIError
|
if _, ok := errors.AsType[*certmanager.NoSNIError](err); ok {
|
||||||
if errors.As(err, &noSNIErr) {
|
return nil, nil
|
||||||
return nil, nil
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
@@ -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)
|
return nil, fmt.Errorf("cannot marshal table cell attrs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cells = append(cells, Node{
|
cells = append(
|
||||||
Type: cellType,
|
cells,
|
||||||
Attrs: attrs,
|
Node{
|
||||||
Content: content,
|
Type: cellType,
|
||||||
})
|
Attrs: attrs,
|
||||||
|
Content: content,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cells, nil
|
return cells, nil
|
||||||
|
|||||||
@@ -62,32 +62,31 @@ func NewAPIKeyMiddleware(svc *iam.Service, tokenSecret string) func(next http.Ha
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
apiKey, err := svc.APIKeyService.GetAPIKey(ctx, keyID)
|
apiKey, err := svc.APIKeyService.GetAPIKey(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrPersonalAPIKeyNotFound](err); ok {
|
||||||
errPersonalAPIKeyNotFound *iam.ErrPersonalAPIKeyNotFound
|
next.ServeHTTP(w, r)
|
||||||
errPersonalAPIKeyExpired *iam.ErrPersonalAPIKeyExpired
|
return
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errPersonalAPIKeyNotFound) || errors.As(err, &errPersonalAPIKeyExpired) {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
panic(fmt.Errorf("cannot get personal API key: %w", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := svc.AccountService.GetIdentity(ctx, apiKey.IdentityID)
|
if _, ok := errors.AsType[*iam.ErrPersonalAPIKeyExpired](err); ok {
|
||||||
if err != nil {
|
next.ServeHTTP(w, r)
|
||||||
var errIdentityNotFound *iam.ErrIdentityNotFound
|
return
|
||||||
if errors.As(err, &errIdentityNotFound) {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
panic(fmt.Errorf("cannot get identity: %w", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = ContextWithAPIKey(ctx, apiKey)
|
||||||
ctx = ContextWithIdentity(ctx, identity)
|
ctx = ContextWithIdentity(ctx, identity)
|
||||||
|
|
||||||
|
|||||||
@@ -65,36 +65,37 @@ func NewSessionMiddleware(svc *iam.Service, cookieConfig securecookie.Config) fu
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := svc.SessionService.GetSession(ctx, sessionID)
|
session, err := svc.SessionService.GetSession(ctx, sessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrSessionNotFound](err); ok {
|
||||||
errSessionNotFound *iam.ErrSessionNotFound
|
securecookie.Clear(w, cookieConfig)
|
||||||
errSessionExpired *iam.ErrSessionExpired
|
next.ServeHTTP(w, r)
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errSessionNotFound) || errors.As(err, &errSessionExpired) {
|
return
|
||||||
securecookie.Clear(w, cookieConfig)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
panic(fmt.Errorf("cannot get session: %w", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := svc.AccountService.GetIdentity(ctx, session.IdentityID)
|
if _, ok := errors.AsType[*iam.ErrSessionExpired](err); ok {
|
||||||
if err != nil {
|
securecookie.Clear(w, cookieConfig)
|
||||||
var errIdentityNotFound *iam.ErrIdentityNotFound
|
next.ServeHTTP(w, r)
|
||||||
if errors.As(err, &errIdentityNotFound) {
|
|
||||||
securecookie.Clear(w, cookieConfig)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
panic(fmt.Errorf("cannot get identity: %w", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
userAgent := r.UserAgent()
|
||||||
// TODO: will work well when no layer 7 proxy is in front of the server
|
// TODO: will work well when no layer 7 proxy is in front of the server
|
||||||
var ipAddress net.IP
|
var ipAddress net.IP
|
||||||
|
|||||||
@@ -79,13 +79,11 @@ func NewAuthorizeFunc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := svc.Authorizer.Authorize(ctx, params); err != nil {
|
if err := svc.Authorizer.Authorize(ctx, params); err != nil {
|
||||||
var errAssumptionRequired *iam.ErrAssumptionRequired
|
if _, ok := errors.AsType[*iam.ErrAssumptionRequired](err); ok {
|
||||||
if errors.As(err, &errAssumptionRequired) {
|
|
||||||
return gqlutils.AssumptionRequired(ctx, err)
|
return gqlutils.AssumptionRequired(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errInsufficientPermissions *iam.ErrInsufficientPermissions
|
if _, ok := errors.AsType[*iam.ErrInsufficientPermissions](err); ok {
|
||||||
if errors.As(err, &errInsufficientPermissions) {
|
|
||||||
return gqlutils.Forbidden(ctx, err)
|
return gqlutils.Forbidden(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,23 +152,27 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
|
|
||||||
node, err := loadNode(ctx, id)
|
node, err := loadNode(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok {
|
||||||
errOrganizationNotFound *iam.ErrOrganizationNotFound
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
errIdentityNotFound *iam.ErrIdentityNotFound
|
}
|
||||||
errSessionNotFound *iam.ErrSessionNotFound
|
|
||||||
errProfileNotFound *iam.ErrProfileNotFound
|
|
||||||
errMembershipNotFound *iam.ErrMembershipNotFound
|
|
||||||
errInvitationNotFound *iam.ErrInvitationNotFound
|
|
||||||
|
|
||||||
isNotFoundErr = errors.As(err, &errOrganizationNotFound) ||
|
if _, ok := errors.AsType[*iam.ErrIdentityNotFound](err); ok {
|
||||||
errors.As(err, &errIdentityNotFound) ||
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
errors.As(err, &errSessionNotFound) ||
|
}
|
||||||
errors.As(err, &errProfileNotFound) ||
|
|
||||||
errors.As(err, &errMembershipNotFound) ||
|
|
||||||
errors.As(err, &errInvitationNotFound)
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,16 +36,11 @@ func (r *mutationResolver) InviteUser(ctx context.Context, input types.InviteUse
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok {
|
||||||
errOrganizationNotFound *iam.ErrOrganizationNotFound
|
|
||||||
errUserAlreadyExists *iam.ErrUserAlreadyExists
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errOrganizationNotFound) {
|
|
||||||
return nil, gqlutils.NotFound(ctx, err)
|
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)
|
return nil, gqlutils.Conflict(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
childSession, err := r.iam.SessionService.GetActiveSessionForMembership(ctx, session.ID, obj.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errSessionNotFound *iam.ErrSessionNotFound
|
if _, ok := errors.AsType[*iam.ErrSessionNotFound](err); ok {
|
||||||
if errors.As(err, &errSessionNotFound) {
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,8 +263,7 @@ func (r *organizationResolver) ScimConfiguration(ctx context.Context, obj *types
|
|||||||
|
|
||||||
config, err := r.iam.OrganizationService.GetSCIMConfiguration(ctx, obj.ID)
|
config, err := r.iam.OrganizationService.GetSCIMConfiguration(ctx, obj.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var notFound *iam.ErrNoSCIMConfigurationFound
|
if _, ok := errors.AsType[*iam.ErrNoSCIMConfigurationFound](err); ok {
|
||||||
if errors.As(err, ¬Found) {
|
|
||||||
return nil, nil
|
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)
|
profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, obj.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *iam.ErrProfileNotFound
|
if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); ok {
|
||||||
if errors.As(err, &errNotFound) {
|
|
||||||
return nil, gqlutils.NotFound(ctx, err)
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ func (r *mutationResolver) CreateUser(ctx context.Context, input types.CreateUse
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *iam.ErrUserAlreadyExists
|
if _, ok := errors.AsType[*iam.ErrUserAlreadyExists](err); ok {
|
||||||
if errors.As(err, &errAlreadyExists) {
|
|
||||||
return nil, gqlutils.Conflict(ctx, err)
|
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)
|
err := r.iam.OrganizationService.RemoveUser(ctx, input.OrganizationID, input.ProfileID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrUserManagedBySCIM](err); ok {
|
||||||
errManagedBySCIM *iam.ErrUserManagedBySCIM
|
|
||||||
errLastActiveOwner *iam.ErrLastActiveOwner
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errManagedBySCIM) {
|
|
||||||
return nil, gqlutils.Conflict(ctx, err)
|
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)
|
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)
|
identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *iam.ErrIdentityNotFound
|
if _, ok := errors.AsType[*iam.ErrIdentityNotFound](err); ok {
|
||||||
if errors.As(err, &errNotFound) {
|
|
||||||
return nil, gqlutils.NotFound(ctx, err)
|
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)
|
organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *iam.ErrOrganizationNotFound
|
if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok {
|
||||||
if errors.As(err, &errNotFound) {
|
|
||||||
return nil, gqlutils.NotFound(ctx, err)
|
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)
|
membership, err := r.iam.AccountService.GetMembershipForOrganization(ctx, obj.Identity.ID, obj.Organization.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *iam.ErrMembershipNotFound
|
if _, ok := errors.AsType[*iam.ErrMembershipNotFound](err); ok {
|
||||||
if errors.As(err, &errNotFound) {
|
|
||||||
return nil, gqlutils.NotFound(ctx, err)
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,7 @@ func (r *mutationResolver) CreateSAMLConfiguration(ctx context.Context, input ty
|
|||||||
req,
|
req,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errSAMLConfigurationEmailDomainAlreadyExists *iam.ErrSAMLConfigurationEmailDomainAlreadyExists
|
if _, ok := errors.AsType[*iam.ErrSAMLConfigurationEmailDomainAlreadyExists](err); ok {
|
||||||
if errors.As(err, &errSAMLConfigurationEmailDomainAlreadyExists) {
|
|
||||||
return nil, gqlutils.Conflict(ctx, err)
|
return nil, gqlutils.Conflict(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,8 +131,7 @@ func (h *SCIMHandler) BearerTokenMiddleware(next http.Handler) http.Handler {
|
|||||||
|
|
||||||
config, err := h.iam.SCIMService.ValidateToken(r.Context(), token)
|
config, err := h.iam.SCIMService.ValidateToken(r.Context(), token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var invalidToken *scimservice.ErrSCIMInvalidToken
|
if _, ok := errors.AsType[*scimservice.ErrSCIMInvalidToken](err); ok {
|
||||||
if errors.As(err, &invalidToken) {
|
|
||||||
httpserver.RenderError(w, http.StatusUnauthorized, errors.New("invalid token"))
|
httpserver.RenderError(w, http.StatusUnauthorized, errors.New("invalid token"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -149,8 +148,7 @@ func (h *SCIMHandler) BearerTokenMiddleware(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rc *scimRequestContext) logAndWrapError(err error, logMsg string) error {
|
func (rc *scimRequestContext) logAndWrapError(err error, logMsg string) error {
|
||||||
var scimErr scimerrors.ScimError
|
if scimErr, ok := errors.AsType[scimerrors.ScimError](err); ok {
|
||||||
if errors.As(err, &scimErr) {
|
|
||||||
errMsg := scimErr.Detail
|
errMsg := scimErr.Detail
|
||||||
|
|
||||||
// Don't reference profileID for 404 errors - the resource doesn't exist
|
// Don't reference profileID for 404 errors - the resource doesn't exist
|
||||||
|
|||||||
@@ -121,8 +121,7 @@ func (r *sCIMBridgeResolver) ScimConfiguration(ctx context.Context, obj *types.S
|
|||||||
|
|
||||||
scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, obj.ScimConfiguration.ID)
|
scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, obj.ScimConfiguration.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errNoSCIMConfigurationFound *iam.ErrNoSCIMConfigurationFound
|
if _, ok := errors.AsType[*iam.ErrNoSCIMConfigurationFound](err); ok {
|
||||||
if errors.As(err, &errNoSCIMConfigurationFound) {
|
|
||||||
return nil, nil
|
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)
|
organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok {
|
||||||
if errors.As(err, &errOrganizationNotFound) {
|
|
||||||
return nil, nil
|
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)
|
bridge, err := r.iam.OrganizationService.GetSCIMBridgeByID(ctx, obj.Bridge.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errSCIMBridgeNotFound *iam.ErrSCIMBridgeNotFound
|
if _, ok := errors.AsType[*iam.ErrSCIMBridgeNotFound](err); ok {
|
||||||
if errors.As(err, &errSCIMBridgeNotFound) {
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,11 @@ import (
|
|||||||
func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) {
|
func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) {
|
||||||
identity, err := r.iam.AuthService.CheckCredentials(ctx, input.Email, input.Password)
|
identity, err := r.iam.AuthService.CheckCredentials(ctx, input.Email, input.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errInvalidPassword *iam.ErrInvalidPassword
|
if _, ok := errors.AsType[*iam.ErrInvalidPassword](err); ok {
|
||||||
if errors.As(err, &errInvalidPassword) {
|
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errInvalidCredentials *iam.ErrInvalidCredentials
|
if _, ok := errors.AsType[*iam.ErrInvalidCredentials](err); ok {
|
||||||
if errors.As(err, &errInvalidCredentials) {
|
|
||||||
return nil, &gqlerror.Error{
|
return nil, &gqlerror.Error{
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
Extensions: map[string]any{
|
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)
|
_, _, err = r.iam.SessionService.OpenPasswordChildSessionForOrganization(ctx, session.ID, *input.OrganizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Here session middleware already took care of expired/nil root session so we only handle membership related errors
|
// Here session middleware already took care of expired/nil root session so we only handle membership related errors
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrMembershipNotFound](err); ok {
|
||||||
errMembershipNotFound *iam.ErrMembershipNotFound
|
return nil, gqlutils.Forbiddenf(ctx, "forbidden")
|
||||||
errUserInactive *iam.ErrUserInactive
|
}
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errMembershipNotFound) || errors.As(err, &errUserInactive) {
|
if _, ok := errors.AsType[*iam.ErrUserInactive](err); ok {
|
||||||
return nil, gqlutils.Forbiddenf(ctx, "forbidden")
|
return nil, gqlutils.Forbiddenf(ctx, "forbidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,13 +110,11 @@ func (r *mutationResolver) SignUp(ctx context.Context, input types.SignUpInput)
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errIdentityAlreadyExists *iam.ErrIdentityAlreadyExists
|
if _, ok := errors.AsType[*iam.ErrIdentityAlreadyExists](err); ok {
|
||||||
if errors.As(err, &errIdentityAlreadyExists) {
|
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errSignupDisabled *iam.ErrSignupDisabled
|
if _, ok := errors.AsType[*iam.ErrSignupDisabled](err); ok {
|
||||||
if errors.As(err, &errSignupDisabled) {
|
|
||||||
return nil, gqlutils.Forbidden(ctx, err)
|
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)
|
err := r.iam.SessionService.CloseSession(ctx, session.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var ErrSessionNotFound *iam.ErrSessionNotFound
|
if _, ok := errors.AsType[*iam.ErrSessionNotFound](err); ok {
|
||||||
if errors.As(err, &ErrSessionNotFound) {
|
|
||||||
return &types.SignOutPayload{}, nil
|
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
|
// Sign out any other account before activating a new one
|
||||||
err := r.iam.SessionService.CloseSession(ctx, session.ID)
|
err := r.iam.SessionService.CloseSession(ctx, session.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var ErrSessionNotFound *iam.ErrSessionNotFound
|
if _, ok := errors.AsType[*iam.ErrSessionNotFound](err); !ok {
|
||||||
if !errors.As(err, &ErrSessionNotFound) {
|
|
||||||
r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err))
|
r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err))
|
||||||
return nil, gqlutils.Internal(ctx)
|
return nil, gqlutils.Internal(ctx)
|
||||||
}
|
}
|
||||||
@@ -184,17 +177,15 @@ func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.Acti
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok {
|
||||||
errInvalidToken *iam.ErrInvalidToken
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
errInvitationNotFound *iam.ErrInvitationNotFound
|
}
|
||||||
errInvitationExpired *iam.ErrInvitationExpired
|
|
||||||
|
|
||||||
isInvalidErr = errors.As(err, &errInvalidToken) ||
|
if _, ok := errors.AsType[*iam.ErrInvitationNotFound](err); ok {
|
||||||
errors.As(err, &errInvitationNotFound) ||
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
errors.As(err, &errInvitationExpired)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
if isInvalidErr {
|
if _, ok := errors.AsType[*iam.ErrInvitationExpired](err); ok {
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,8 +267,7 @@ func (r *mutationResolver) ResetPassword(ctx context.Context, input types.ResetP
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errInvalidToken *iam.ErrInvalidToken
|
if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok {
|
||||||
if errors.As(err, &errInvalidToken) {
|
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
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) {
|
func (r *mutationResolver) VerifyEmail(ctx context.Context, input types.VerifyEmailInput) (*types.VerifyEmailPayload, error) {
|
||||||
err := r.iam.AccountService.VerifyEmail(ctx, input.Token)
|
err := r.iam.AccountService.VerifyEmail(ctx, input.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok {
|
||||||
errInvalidToken *iam.ErrInvalidToken
|
|
||||||
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
||||||
errEmailAlreadyVerified *iam.ErrEmailAlreadyVerified
|
|
||||||
errEmailVerificationMismatch *iam.ErrEmailVerificationMismatch
|
|
||||||
|
|
||||||
isInvalidErr = errors.As(err, &errInvalidToken) ||
|
|
||||||
errors.As(err, &errEmailVerificationMismatch)
|
|
||||||
)
|
|
||||||
|
|
||||||
if isInvalidErr {
|
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
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)
|
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)
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,16 +326,11 @@ func (r *mutationResolver) ChangePassword(ctx context.Context, input types.Chang
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrInvalidPassword](err); ok {
|
||||||
errInvalidPassword *iam.ErrInvalidPassword
|
|
||||||
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errInvalidPassword) {
|
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
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)
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,16 +357,11 @@ func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEm
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrInvalidPassword](err); ok {
|
||||||
errInvalidPassword *iam.ErrInvalidPassword
|
|
||||||
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errInvalidPassword) {
|
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
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)
|
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)
|
childSession, membership, err := r.iam.SessionService.AssumeOrganizationSession(ctx, rootSession.ID, input.OrganizationID, input.Continue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrMembershipNotFound](err); ok {
|
||||||
errMembershipNotFound *iam.ErrMembershipNotFound
|
|
||||||
errPasswordAuthenticationRequired *iam.ErrPasswordAuthenticationRequired
|
|
||||||
errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired
|
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case errors.As(err, &errMembershipNotFound):
|
|
||||||
return nil, gqlutils.NotFound(ctx, err)
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
case errors.As(err, &errPasswordAuthenticationRequired):
|
if errPasswordAuthenticationRequired, ok := errors.AsType[*iam.ErrPasswordAuthenticationRequired](err); ok {
|
||||||
return &types.AssumeOrganizationSessionPayload{
|
return &types.AssumeOrganizationSessionPayload{
|
||||||
Result: types.PasswordRequired{
|
Result: types.PasswordRequired{
|
||||||
Reason: types.ReauthenticationReason(errPasswordAuthenticationRequired.Reason),
|
Reason: types.ReauthenticationReason(errPasswordAuthenticationRequired.Reason),
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
case errors.As(err, &errSAMLAuthenticationRequired):
|
if errSAMLAuthenticationRequired, ok := errors.AsType[*iam.ErrSAMLAuthenticationRequired](err); ok {
|
||||||
return &types.AssumeOrganizationSessionPayload{
|
return &types.AssumeOrganizationSessionPayload{
|
||||||
Result: types.SAMLAuthenticationRequired{
|
Result: types.SAMLAuthenticationRequired{
|
||||||
Reason: types.ReauthenticationReason(errSAMLAuthenticationRequired.Reason),
|
Reason: types.ReauthenticationReason(errSAMLAuthenticationRequired.Reason),
|
||||||
},
|
},
|
||||||
}, nil
|
}, 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{
|
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)
|
err := r.iam.SessionService.RevokeSession(ctx, identity.ID, input.SessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var ErrSessionExpired *iam.ErrSessionExpired
|
if _, ok := errors.AsType[*iam.ErrSessionExpired](err); ok {
|
||||||
if errors.As(err, &ErrSessionExpired) {
|
|
||||||
return &types.RevokeSessionPayload{Success: true}, nil
|
return &types.RevokeSessionPayload{Success: true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,13 +55,11 @@ func NewRecoverFunc(logger *log.Logger) mcpgenmcp.RecoverFunc {
|
|||||||
// those. Unknown errors are logged and replaced with a generic internal error
|
// those. Unknown errors are logged and replaced with a generic internal error
|
||||||
// to avoid leaking implementation details to the client.
|
// to avoid leaking implementation details to the client.
|
||||||
func sanitizeError(ctx context.Context, logger *log.Logger, err error) error {
|
func sanitizeError(ctx context.Context, logger *log.Logger, err error) error {
|
||||||
var permissionDeniedErr *iam.ErrInsufficientPermissions
|
if _, ok := errors.AsType[*iam.ErrInsufficientPermissions](err); ok {
|
||||||
if errors.As(err, &permissionDeniedErr) {
|
|
||||||
return fmt.Errorf("permission denied")
|
return fmt.Errorf("permission denied")
|
||||||
}
|
}
|
||||||
|
|
||||||
var assumptionRequiredErr *iam.ErrAssumptionRequired
|
if _, ok := errors.AsType[*iam.ErrAssumptionRequired](err); ok {
|
||||||
if errors.As(err, &assumptionRequiredErr) {
|
|
||||||
return fmt.Errorf("assumption required")
|
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")
|
return fmt.Errorf("resource is in use")
|
||||||
}
|
}
|
||||||
|
|
||||||
var validationErrors validator.ValidationErrors
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||||
if errors.As(err, &validationErrors) {
|
|
||||||
return validationErrors
|
return validationErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
var validationError *validator.ValidationError
|
if validationError, ok := errors.AsType[*validator.ValidationError](err); ok {
|
||||||
if errors.As(err, &validationError) {
|
|
||||||
return validationError
|
return validationError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
package mcp_v1
|
package mcp_v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"go.gearno.de/kit/httpserver"
|
"go.gearno.de/kit/httpserver"
|
||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
|
|||||||
@@ -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) {
|
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)
|
profile, err := r.iamSvc.OrganizationService.GetProfile(ctx, input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *iam.ErrProfileNotFound
|
if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); ok {
|
||||||
if errors.As(err, &errNotFound) {
|
|
||||||
return nil, types.GetUserOutput{}, fmt.Errorf("user not found: %w", err)
|
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,
|
ContractEndDate: contractEnd,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errAlreadyExists *iam.ErrUserAlreadyExists
|
if _, ok := errors.AsType[*iam.ErrUserAlreadyExists](err); ok {
|
||||||
if errors.As(err, &errAlreadyExists) {
|
|
||||||
return nil, types.CreateUserOutput{}, fmt.Errorf("user with email already exists: %w", err)
|
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,
|
ProfileID: input.ProfileID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrOrganizationNotFound](err); ok {
|
||||||
errOrgNotFound *iam.ErrOrganizationNotFound
|
|
||||||
errUserExists *iam.ErrUserAlreadyExists
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errOrgNotFound) {
|
|
||||||
return nil, types.InviteUserOutput{}, fmt.Errorf("organization not found: %w", err)
|
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)
|
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)
|
err := r.iamSvc.OrganizationService.RemoveUser(ctx, input.OrganizationID, input.ProfileID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var (
|
if _, ok := errors.AsType[*iam.ErrUserManagedBySCIM](err); ok {
|
||||||
errManagedBySCIM *iam.ErrUserManagedBySCIM
|
|
||||||
errLastOwner *iam.ErrLastActiveOwner
|
|
||||||
)
|
|
||||||
|
|
||||||
if errors.As(err, &errManagedBySCIM) {
|
|
||||||
return nil, types.RemoveUserOutput{}, fmt.Errorf("user is managed by SCIM and cannot be removed: %w", err)
|
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)
|
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)
|
config, err := r.iamSvc.OrganizationService.GetSCIMConfiguration(ctx, input.OrganizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *iam.ErrNoSCIMConfigurationFound
|
if _, ok := errors.AsType[*iam.ErrNoSCIMConfigurationFound](err); ok {
|
||||||
if errors.As(err, &errNotFound) {
|
|
||||||
return nil, types.GetSCIMConfigurationOutput{}, fmt.Errorf("SCIM configuration not found")
|
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)
|
bridge, err := r.iamSvc.OrganizationService.GetSCIMBridgeByID(ctx, input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errNotFound *iam.ErrSCIMBridgeNotFound
|
if _, ok := errors.AsType[*iam.ErrSCIMBridgeNotFound](err); ok {
|
||||||
if errors.As(err, &errNotFound) {
|
|
||||||
return nil, types.GetSCIMBridgeOutput{}, fmt.Errorf("SCIM bridge %s not found", input.ID)
|
return nil, types.GetSCIMBridgeOutput{}, fmt.Errorf("SCIM bridge %s not found", input.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ package trust_v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
|
|
||||||
|
"errors"
|
||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
"go.probo.inc/probo/pkg/baseurl"
|
"go.probo.inc/probo/pkg/baseurl"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"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)
|
email, err := r.iam.AuthService.GetMagicLinkEmail(ctx, input.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errExpiredToken *iam.ErrExpiredToken
|
if _, ok := errors.AsType[*iam.ErrExpiredToken](err); ok {
|
||||||
if errors.As(err, &errExpiredToken) {
|
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errInvalidToken *iam.ErrInvalidToken
|
if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok {
|
||||||
if errors.As(err, &errInvalidToken) {
|
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
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)
|
identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errExpiredToken *iam.ErrExpiredToken
|
if _, ok := errors.AsType[*iam.ErrExpiredToken](err); ok {
|
||||||
if errors.As(err, &errExpiredToken) {
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidToken *iam.ErrInvalidToken
|
if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok {
|
||||||
if errors.As(err, &errInvalidToken) {
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(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)
|
identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errExpiredToken *iam.ErrExpiredToken
|
if _, ok := errors.AsType[*iam.ErrExpiredToken](err); ok {
|
||||||
if errors.As(err, &errExpiredToken) {
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalidToken *iam.ErrInvalidToken
|
if _, ok := errors.AsType[*iam.ErrInvalidToken](err); ok {
|
||||||
if errors.As(err, &errInvalidToken) {
|
return nil, gqlutils.Invalid(ctx, err)
|
||||||
return nil, gqlutils.Invalid(ctx, err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err))
|
r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err))
|
||||||
|
|
||||||
|
|||||||
@@ -143,11 +143,9 @@ func Conflictf(ctx context.Context, format string, a ...any) *gqlerror.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Invalid(ctx context.Context, err error) *gqlerror.Error {
|
func Invalid(ctx context.Context, err error) *gqlerror.Error {
|
||||||
var errValidation *validator.ValidationError
|
|
||||||
|
|
||||||
var details map[string]any
|
var details map[string]any
|
||||||
|
|
||||||
if errors.As(err, &errValidation) {
|
if errValidation, ok := errors.AsType[*validator.ValidationError](err); ok {
|
||||||
details = map[string]any{
|
details = map[string]any{
|
||||||
"cause": errValidation.Code,
|
"cause": errValidation.Code,
|
||||||
"field": errValidation.Field,
|
"field": errValidation.Field,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user