Consolidate connector provider dispatch behind a typed *Registry
The console previously dispatched per-provider logic through a fan of init()-side-effect maps (driver names, OAuth2 metadata, probe URLs, display names, settings switches), spread across pkg/connector, pkg/accessreview/drivers and the console v1 resolvers. Adding a new provider required edits in every one of those places and a corresponding switch arm in CreateConnectorRequest. The same per-provider knowledge also leaked into Helm templates as hand-rolled environment-variable blocks per connector. This commit collapses the dispatch surface into a single typed *provider.Registry. The registry is constructed once by NewBuiltinRegistry at probod startup and threaded as an explicit dependency into every consumer (accessreview service, console v1 resolver, OAuth2 wiring). There is no package-level state. Each provider lives in one file under pkg/connector/provider/ that exposes a private xxxRegistration() *Registration constructor; NewBuiltinRegistry enumerates them. CreateConnectorRequest loses its per-provider settings fields and takes a single RawSettings json.RawMessage produced by the per-provider MarshalSettings closure. The 1Password SCIM bridge URL is validated at create time (http(s) scheme + non-empty host) so a malformed value fails fast at the resolver boundary. The Helm chart gains probo.connectorEnv and probo.connectorSecretEntries templates so adding a connector requires zero Helm changes. Access-review name resolution moves into the same Registration value to keep one authoritative dispatch table. Tests cover every Registration (DisplayName, NewDriver wired), Register error paths (nil, empty Provider, empty DisplayName, duplicate), All / ProviderDisplayName / ProviderOAuth2Scopes / ProbeURL hit and miss paths, the ApplyOAuth2Defaults templating and PKCE branches, and ConnectorSettings[T] round-trip plus malformed-JSON error path. The pre-refactor ApplyProviderDefaults test in pkg/connector is replaced by the equivalent in pkg/connector/provider. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
78
pkg/connector/provider/apply.go
Normal file
78
pkg/connector/provider/apply.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
"go.gearno.de/kit/httpclient"
|
||||
"go.probo.inc/probo/pkg/connector"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
// ApplyOAuth2Defaults sets the redirect URI on c and applies static
|
||||
// provider defaults (auth URL, token URL, extra params, token endpoint
|
||||
// auth, PKCE) onto an OAuth2Connector, and wires an SSRF-protected
|
||||
// HTTP client for the token exchange request. Static metadata is
|
||||
// pulled from r; only ClientID and ClientSecret come from deployment
|
||||
// config.
|
||||
//
|
||||
// Operator-supplied placeholders in the static AuthURL (e.g. Vercel's
|
||||
// "{integration_slug}") are substituted from c.AuthURLParams; the
|
||||
// substitution is a no-op when no placeholders are configured.
|
||||
func (r *Registry) ApplyOAuth2Defaults(p string, redirectURI string, c *connector.OAuth2Connector) {
|
||||
c.RedirectURI = redirectURI
|
||||
c.HTTPClient = httpclient.DefaultClient(httpclient.WithSSRFProtection())
|
||||
|
||||
reg, ok := r.Get(coredata.ConnectorProvider(p))
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
c.AuthURL = reg.AuthURL
|
||||
c.TokenURL = reg.TokenURL
|
||||
c.TokenEndpointAuth = reg.TokenEndpointAuth
|
||||
c.SupportsIncrementalAuth = reg.SupportsIncrementalAuth
|
||||
c.RequiresPKCE = reg.RequiresPKCE
|
||||
|
||||
// Deep copy ExtraAuthParams so per-connector mutations (e.g.
|
||||
// incremental auth, scope overrides) cannot alias back into the
|
||||
// shared registry map.
|
||||
if len(reg.ExtraAuthParams) > 0 {
|
||||
extra := make(map[string]string, len(reg.ExtraAuthParams))
|
||||
maps.Copy(extra, reg.ExtraAuthParams)
|
||||
c.ExtraAuthParams = extra
|
||||
}
|
||||
|
||||
// Resolve operator-supplied placeholders in the static AuthURL
|
||||
// (for example Vercel's "{integration_slug}"). Providers without
|
||||
// placeholders are unaffected; the loop is a no-op when
|
||||
// AuthURLParams is empty.
|
||||
for k, v := range c.AuthURLParams {
|
||||
c.AuthURL = strings.ReplaceAll(c.AuthURL, "{"+k+"}", v)
|
||||
}
|
||||
}
|
||||
|
||||
// ProbeURL returns the registered probe URL for provider p, or the
|
||||
// empty string if no probe URL is configured.
|
||||
func (r *Registry) ProbeURL(p string) string {
|
||||
reg, ok := r.Get(coredata.ConnectorProvider(p))
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return reg.ProbeURL
|
||||
}
|
||||
88
pkg/connector/provider/apply_test.go
Normal file
88
pkg/connector/provider/apply_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.probo.inc/probo/pkg/connector"
|
||||
"go.probo.inc/probo/pkg/connector/provider"
|
||||
)
|
||||
|
||||
// TestApplyOAuth2Defaults_AuthURLTemplating verifies that operator-supplied
|
||||
// AuthURLParams (for example Vercel's "{integration_slug}") are substituted
|
||||
// into the static provider AuthURL when the connector is initialized.
|
||||
// Providers without placeholders are unaffected.
|
||||
func TestApplyOAuth2Defaults_AuthURLTemplating(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("placeholder is substituted when AuthURLParams is supplied", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewBuiltinRegistry()
|
||||
c := &connector.OAuth2Connector{
|
||||
ClientID: "id",
|
||||
ClientSecret: "secret",
|
||||
AuthURLParams: map[string]string{
|
||||
"integration_slug": "acme",
|
||||
},
|
||||
}
|
||||
|
||||
// VERCEL uses a templated AuthURL with the
|
||||
// "{integration_slug}" placeholder.
|
||||
r.ApplyOAuth2Defaults("VERCEL", "https://example.com/cb", c)
|
||||
|
||||
assert.Equal(t, "https://vercel.com/integrations/acme/new", c.AuthURL)
|
||||
assert.Equal(t, "https://api.vercel.com/v2/oauth/access_token", c.TokenURL)
|
||||
})
|
||||
|
||||
t.Run("placeholder remains literal when AuthURLParams is empty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewBuiltinRegistry()
|
||||
c := &connector.OAuth2Connector{
|
||||
ClientID: "id",
|
||||
ClientSecret: "secret",
|
||||
}
|
||||
|
||||
r.ApplyOAuth2Defaults("VERCEL", "https://example.com/cb", c)
|
||||
|
||||
// No substitution requested; the placeholder is preserved
|
||||
// verbatim so a misconfiguration is visible at the
|
||||
// authorization step rather than silently masked.
|
||||
assert.Equal(t, "https://vercel.com/integrations/{integration_slug}/new", c.AuthURL)
|
||||
})
|
||||
}
|
||||
|
||||
// TestApplyOAuth2Defaults_PKCEDefaults asserts that the registered
|
||||
// PAGERDUTY provider defaults flip RequiresPKCE on so the downstream
|
||||
// Initiate/Complete flow generates a verifier and replays it.
|
||||
func TestApplyOAuth2Defaults_PKCEDefaults(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, p := range []string{"PAGERDUTY"} {
|
||||
t.Run(p, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewBuiltinRegistry()
|
||||
c := &connector.OAuth2Connector{ClientID: "id", ClientSecret: "secret"}
|
||||
r.ApplyOAuth2Defaults(p, "https://example.com/cb", c)
|
||||
assert.True(t, c.RequiresPKCE,
|
||||
"provider %s must enable PKCE so Initiate generates a verifier", p)
|
||||
})
|
||||
}
|
||||
}
|
||||
60
pkg/connector/provider/asana.go
Normal file
60
pkg/connector/provider/asana.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func asanaRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderAsana,
|
||||
DisplayName: "Asana",
|
||||
AuthURL: "https://app.asana.com/-/oauth_authorize",
|
||||
TokenURL: "https://app.asana.com/-/oauth_token",
|
||||
ProbeURL: "https://app.asana.com/api/1.0/users/me",
|
||||
OAuth2Scopes: []string{"workspaces:read", "users:read"},
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.AsanaConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read asana connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.WorkspaceGID == "" {
|
||||
return nil, fmt.Errorf("cannot create asana driver: workspace_gid is required")
|
||||
}
|
||||
|
||||
return drivers.NewAsanaDriver(c, s.WorkspaceGID), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.AsanaConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read asana connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewAsanaNameResolver(c, s.WorkspaceGID)
|
||||
},
|
||||
SetOrganizationSettings: func(c *coredata.Connector, workspaceGID string) error {
|
||||
return c.SetSettings(&coredata.AsanaConnectorSettings{WorkspaceGID: workspaceGID})
|
||||
},
|
||||
}
|
||||
}
|
||||
62
pkg/connector/provider/bitbucket.go
Normal file
62
pkg/connector/provider/bitbucket.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func bitbucketRegistration() *Registration {
|
||||
// Bitbucket scopes are pinned on the OAuth consumer at registration
|
||||
// time (`account` for workspace membership). They are not passed in
|
||||
// the authorize URL.
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderBitbucket,
|
||||
DisplayName: "Bitbucket",
|
||||
AuthURL: "https://bitbucket.org/site/oauth2/authorize",
|
||||
TokenURL: "https://bitbucket.org/site/oauth2/access_token",
|
||||
ProbeURL: "https://api.bitbucket.org/2.0/user",
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.BitbucketConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read bitbucket connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.Workspace == "" {
|
||||
return nil, fmt.Errorf("cannot create bitbucket driver: workspace is required")
|
||||
}
|
||||
|
||||
return drivers.NewBitbucketDriver(c, s.Workspace), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.BitbucketConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read bitbucket connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewBitbucketNameResolver(c, s.Workspace)
|
||||
},
|
||||
SetOrganizationSettings: func(c *coredata.Connector, workspace string) error {
|
||||
return c.SetSettings(&coredata.BitbucketConnectorSettings{Workspace: workspace})
|
||||
},
|
||||
}
|
||||
}
|
||||
42
pkg/connector/provider/brex.go
Normal file
42
pkg/connector/provider/brex.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func brexRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderBrex,
|
||||
DisplayName: "Brex",
|
||||
AuthURL: "https://accounts-api.brex.com/oauth2/default/v1/authorize",
|
||||
TokenURL: "https://accounts-api.brex.com/oauth2/default/v1/token",
|
||||
ProbeURL: "https://platform.brexapis.com/v2/users/me",
|
||||
OAuth2Scopes: []string{"openid", "offline_access", "users.readonly"},
|
||||
SupportsAPIKey: true,
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewBrexDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewBrexNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
58
pkg/connector/provider/builtin.go
Normal file
58
pkg/connector/provider/builtin.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
// NewBuiltinRegistry returns a *Registry populated with every
|
||||
// connector provider compiled into the binary. It panics on duplicate
|
||||
// registration or invalid Registration metadata — both are programmer
|
||||
// errors caught at process start, not at runtime. Probod calls this
|
||||
// once at startup and threads the *Registry into every consumer.
|
||||
func NewBuiltinRegistry() *Registry {
|
||||
r := NewRegistry()
|
||||
for _, reg := range []*Registration{
|
||||
asanaRegistration(),
|
||||
bitbucketRegistration(),
|
||||
brexRegistration(),
|
||||
clickupRegistration(),
|
||||
cloudflareRegistration(),
|
||||
docusignRegistration(),
|
||||
githubRegistration(),
|
||||
gitlabRegistration(),
|
||||
googleWorkspaceRegistration(),
|
||||
herokuRegistration(),
|
||||
hubspotRegistration(),
|
||||
intercomRegistration(),
|
||||
linearRegistration(),
|
||||
microsoft365Registration(),
|
||||
mondayRegistration(),
|
||||
netlifyRegistration(),
|
||||
notionRegistration(),
|
||||
onePasswordRegistration(),
|
||||
openaiRegistration(),
|
||||
pagerdutyRegistration(),
|
||||
resendRegistration(),
|
||||
sentryRegistration(),
|
||||
slackRegistration(),
|
||||
supabaseRegistration(),
|
||||
tallyRegistration(),
|
||||
vercelRegistration(),
|
||||
} {
|
||||
if err := r.Register(reg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
60
pkg/connector/provider/clickup.go
Normal file
60
pkg/connector/provider/clickup.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func clickupRegistration() *Registration {
|
||||
// ClickUp OAuth flow has no scope granularity, so OAuth2Scopes is empty.
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderClickUp,
|
||||
DisplayName: "ClickUp",
|
||||
AuthURL: "https://app.clickup.com/api",
|
||||
TokenURL: "https://api.clickup.com/api/v2/oauth/token",
|
||||
ProbeURL: "https://api.clickup.com/api/v2/user",
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.ClickUpConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read clickup connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.TeamID == "" {
|
||||
return nil, fmt.Errorf("cannot create clickup driver: team_id is required")
|
||||
}
|
||||
|
||||
return drivers.NewClickUpDriver(c, s.TeamID), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.ClickUpConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read clickup connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewClickUpNameResolver(c, s.TeamID)
|
||||
},
|
||||
SetOrganizationSettings: func(c *coredata.Connector, teamID string) error {
|
||||
return c.SetSettings(&coredata.ClickUpConnectorSettings{TeamID: teamID})
|
||||
},
|
||||
}
|
||||
}
|
||||
39
pkg/connector/provider/cloudflare.go
Normal file
39
pkg/connector/provider/cloudflare.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func cloudflareRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderCloudflare,
|
||||
DisplayName: "Cloudflare",
|
||||
ProbeURL: "https://api.cloudflare.com/client/v4/user/tokens/verify",
|
||||
SupportsAPIKey: true,
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewCloudflareDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewCloudflareNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
43
pkg/connector/provider/docusign.go
Normal file
43
pkg/connector/provider/docusign.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func docusignRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderDocuSign,
|
||||
DisplayName: "DocuSign",
|
||||
AuthURL: "https://account.docusign.com/oauth/auth",
|
||||
TokenURL: "https://account.docusign.com/oauth/token",
|
||||
TokenEndpointAuth: "basic-form",
|
||||
ProbeURL: "https://account.docusign.com/oauth/userinfo",
|
||||
OAuth2Scopes: []string{"signature"},
|
||||
SupportsAPIKey: true,
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewDocuSignDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewDocuSignNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
72
pkg/connector/provider/github.go
Normal file
72
pkg/connector/provider/github.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func githubRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderGitHub,
|
||||
DisplayName: "GitHub",
|
||||
AuthURL: "https://github.com/login/oauth/authorize",
|
||||
TokenURL: "https://github.com/login/oauth/access_token",
|
||||
ProbeURL: "https://api.github.com/user",
|
||||
OAuth2Scopes: []string{"read:org"},
|
||||
SupportsAPIKey: true,
|
||||
ExtraSettings: []ExtraSetting{
|
||||
{Key: "organization", Label: "Organization", Required: true},
|
||||
},
|
||||
MarshalSettings: func(in *SettingsInput) (json.RawMessage, error) {
|
||||
if in == nil || in.GitHubOrganization == nil || *in.GitHubOrganization == "" {
|
||||
return nil, fmt.Errorf("cannot create github connector: githubOrganization is required")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.GitHubConnectorSettings{Organization: *in.GitHubOrganization})
|
||||
},
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.GitHubConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read github connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.Organization == "" {
|
||||
return nil, fmt.Errorf("cannot create github driver: organization is required")
|
||||
}
|
||||
|
||||
return drivers.NewGitHubDriver(c, s.Organization, logger.Named("github")), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.GitHubConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read github connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewGitHubNameResolver(c, s.Organization)
|
||||
},
|
||||
SetOrganizationSettings: func(c *coredata.Connector, org string) error {
|
||||
return c.SetSettings(&coredata.GitHubConnectorSettings{Organization: org})
|
||||
},
|
||||
}
|
||||
}
|
||||
60
pkg/connector/provider/gitlab.go
Normal file
60
pkg/connector/provider/gitlab.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func gitlabRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderGitLab,
|
||||
DisplayName: "GitLab",
|
||||
AuthURL: "https://gitlab.com/oauth/authorize",
|
||||
TokenURL: "https://gitlab.com/oauth/token",
|
||||
ProbeURL: "https://gitlab.com/api/v4/user",
|
||||
OAuth2Scopes: []string{"read_api"},
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.GitLabConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read gitlab connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.GroupID == "" {
|
||||
return nil, fmt.Errorf("cannot create gitlab driver: group_id is required")
|
||||
}
|
||||
|
||||
return drivers.NewGitLabDriver(c, s.GroupID), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.GitLabConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read gitlab connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewGitLabNameResolver(c, s.GroupID)
|
||||
},
|
||||
SetOrganizationSettings: func(c *coredata.Connector, groupID string) error {
|
||||
return c.SetSettings(&coredata.GitLabConnectorSettings{GroupID: groupID})
|
||||
},
|
||||
}
|
||||
}
|
||||
50
pkg/connector/provider/google_workspace.go
Normal file
50
pkg/connector/provider/google_workspace.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func googleWorkspaceRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderGoogleWorkspace,
|
||||
DisplayName: "Google Workspace",
|
||||
AuthURL: "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
TokenURL: "https://oauth2.googleapis.com/token",
|
||||
ExtraAuthParams: map[string]string{
|
||||
"access_type": "offline",
|
||||
"prompt": "consent",
|
||||
},
|
||||
SupportsIncrementalAuth: true,
|
||||
ProbeURL: "https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer&maxResults=1",
|
||||
OAuth2Scopes: []string{
|
||||
"https://www.googleapis.com/auth/admin.directory.user.readonly",
|
||||
"https://www.googleapis.com/auth/admin.directory.group.member.readonly",
|
||||
"https://www.googleapis.com/auth/admin.directory.customer.readonly",
|
||||
},
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewGoogleWorkspaceDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewGoogleWorkspaceNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
60
pkg/connector/provider/heroku.go
Normal file
60
pkg/connector/provider/heroku.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func herokuRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderHeroku,
|
||||
DisplayName: "Heroku",
|
||||
AuthURL: "https://id.heroku.com/oauth/authorize",
|
||||
TokenURL: "https://id.heroku.com/oauth/token",
|
||||
ProbeURL: "https://api.heroku.com/account",
|
||||
OAuth2Scopes: []string{"read"},
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.HerokuConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read heroku connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.TeamID == "" {
|
||||
return nil, fmt.Errorf("cannot create heroku driver: team_id is required")
|
||||
}
|
||||
|
||||
return drivers.NewHerokuDriver(c, s.TeamID), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.HerokuConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read heroku connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewHerokuNameResolver(c, s.TeamID)
|
||||
},
|
||||
SetOrganizationSettings: func(c *coredata.Connector, teamID string) error {
|
||||
return c.SetSettings(&coredata.HerokuConnectorSettings{TeamID: teamID})
|
||||
},
|
||||
}
|
||||
}
|
||||
42
pkg/connector/provider/hubspot.go
Normal file
42
pkg/connector/provider/hubspot.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func hubspotRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderHubSpot,
|
||||
DisplayName: "HubSpot",
|
||||
AuthURL: "https://app.hubspot.com/oauth/authorize",
|
||||
TokenURL: "https://api.hubapi.com/oauth/v1/token",
|
||||
ProbeURL: "https://api.hubapi.com/account-info/v3/details",
|
||||
OAuth2Scopes: []string{"settings.users.read"},
|
||||
SupportsAPIKey: true,
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewHubSpotDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewHubSpotNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
41
pkg/connector/provider/intercom.go
Normal file
41
pkg/connector/provider/intercom.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func intercomRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderIntercom,
|
||||
DisplayName: "Intercom",
|
||||
AuthURL: "https://app.intercom.com/oauth",
|
||||
TokenURL: "https://api.intercom.io/auth/eagle/token",
|
||||
ProbeURL: "https://api.intercom.io/me",
|
||||
SupportsAPIKey: true,
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewIntercomDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewIntercomNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
41
pkg/connector/provider/linear.go
Normal file
41
pkg/connector/provider/linear.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func linearRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderLinear,
|
||||
DisplayName: "Linear",
|
||||
AuthURL: "https://linear.app/oauth/authorize",
|
||||
TokenURL: "https://api.linear.app/oauth/token",
|
||||
ProbeURL: "https://api.linear.app/graphql",
|
||||
OAuth2Scopes: []string{"read"},
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewLinearDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewLinearNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
51
pkg/connector/provider/microsoft_365.go
Normal file
51
pkg/connector/provider/microsoft_365.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func microsoft365Registration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderMicrosoft365,
|
||||
DisplayName: "Microsoft 365",
|
||||
AuthURL: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
||||
TokenURL: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
||||
ExtraAuthParams: map[string]string{
|
||||
"prompt": "consent",
|
||||
},
|
||||
ProbeURL: "https://graph.microsoft.com/v1.0/organization?$top=1",
|
||||
OAuth2Scopes: []string{
|
||||
"openid",
|
||||
"profile",
|
||||
"offline_access",
|
||||
"https://graph.microsoft.com/User.Read.All",
|
||||
"https://graph.microsoft.com/Directory.Read.All",
|
||||
"https://graph.microsoft.com/RoleManagement.Read.Directory",
|
||||
},
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewMicrosoft365Driver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewMicrosoft365NameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
44
pkg/connector/provider/monday.go
Normal file
44
pkg/connector/provider/monday.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func mondayRegistration() *Registration {
|
||||
// Monday.com's primary API is GraphQL POST, and the auth subdomain
|
||||
// does not expose a Bearer-protected GET userinfo endpoint, so
|
||||
// ProbeURL is empty. The probe handler skips empty entries; an
|
||||
// invalid token surfaces at the next /v2 query.
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderMonday,
|
||||
DisplayName: "Monday.com",
|
||||
AuthURL: "https://auth.monday.com/oauth2/authorize",
|
||||
TokenURL: "https://auth.monday.com/oauth2/token",
|
||||
OAuth2Scopes: []string{"users:read", "account:read"},
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewMondayDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewMondayNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
60
pkg/connector/provider/netlify.go
Normal file
60
pkg/connector/provider/netlify.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func netlifyRegistration() *Registration {
|
||||
// Netlify OAuth flow has no scope granularity, so OAuth2Scopes is empty.
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderNetlify,
|
||||
DisplayName: "Netlify",
|
||||
AuthURL: "https://app.netlify.com/authorize",
|
||||
TokenURL: "https://api.netlify.com/oauth/token",
|
||||
ProbeURL: "https://api.netlify.com/api/v1/user",
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.NetlifyConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read netlify connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.AccountSlug == "" {
|
||||
return nil, fmt.Errorf("cannot create netlify driver: account_slug is required")
|
||||
}
|
||||
|
||||
return drivers.NewNetlifyDriver(c, s.AccountSlug), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.NetlifyConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read netlify connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewNetlifyNameResolver(c, s.AccountSlug)
|
||||
},
|
||||
SetOrganizationSettings: func(c *coredata.Connector, accountSlug string) error {
|
||||
return c.SetSettings(&coredata.NetlifyConnectorSettings{AccountSlug: accountSlug})
|
||||
},
|
||||
}
|
||||
}
|
||||
43
pkg/connector/provider/notion.go
Normal file
43
pkg/connector/provider/notion.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func notionRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderNotion,
|
||||
DisplayName: "Notion",
|
||||
AuthURL: "https://api.notion.com/v1/oauth/authorize",
|
||||
TokenURL: "https://api.notion.com/v1/oauth/token",
|
||||
ExtraAuthParams: map[string]string{"owner": "user"},
|
||||
TokenEndpointAuth: "basic-json",
|
||||
ProbeURL: "https://api.notion.com/v1/users/me",
|
||||
SupportsAPIKey: true,
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewNotionDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewNotionNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
100
pkg/connector/provider/one_password.go
Normal file
100
pkg/connector/provider/one_password.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/connector"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func onePasswordRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderOnePassword,
|
||||
DisplayName: "1Password",
|
||||
ProbeURL: "https://events.1password.com/api/v1/auditevents",
|
||||
SupportsAPIKey: true,
|
||||
SupportsClientCredentials: true,
|
||||
ExtraSettings: []ExtraSetting{
|
||||
{Key: "accountId", Label: "Account ID", Required: true},
|
||||
{Key: "region", Label: "Region", Required: true},
|
||||
},
|
||||
// 1Password has two settings shapes selected by protocol:
|
||||
// - Client-credentials: AccountID + Region (Users API driver).
|
||||
// - API key: SCIMBridgeURL (SCIM-bridge driver).
|
||||
// MarshalSettings picks the shape based on which input fields
|
||||
// are populated. The resolvers ensure that only one path is
|
||||
// possible for any given request.
|
||||
MarshalSettings: func(in *SettingsInput) (json.RawMessage, error) {
|
||||
if in == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if in.OnePasswordAccountID != nil && in.OnePasswordRegion != nil {
|
||||
if *in.OnePasswordAccountID == "" || *in.OnePasswordRegion == "" {
|
||||
return nil, fmt.Errorf("cannot create 1password connector: onePasswordAccountId and onePasswordRegion must be non-empty")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.OnePasswordUsersAPISettings{
|
||||
AccountID: *in.OnePasswordAccountID,
|
||||
Region: *in.OnePasswordRegion,
|
||||
})
|
||||
}
|
||||
|
||||
if in.OnePasswordSCIMBridgeURL != nil && *in.OnePasswordSCIMBridgeURL != "" {
|
||||
u, err := url.Parse(*in.OnePasswordSCIMBridgeURL)
|
||||
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
|
||||
return nil, fmt.Errorf("cannot create 1password connector: onePasswordScimBridgeURL must be an http(s) URL")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.OnePasswordConnectorSettings{
|
||||
SCIMBridgeURL: *in.OnePasswordSCIMBridgeURL,
|
||||
})
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
// Client credentials grant uses the Users API driver; the
|
||||
// authorization-code grant uses the SCIM-bridge driver.
|
||||
if conn.GrantType() == string(connector.OAuth2GrantTypeClientCredentials) {
|
||||
s, err := coredata.ConnectorSettings[coredata.OnePasswordUsersAPISettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read 1password users api settings: %w", err)
|
||||
}
|
||||
|
||||
return drivers.NewOnePasswordUsersAPIDriver(c, s.AccountID, s.Region), nil
|
||||
}
|
||||
|
||||
s, err := coredata.ConnectorSettings[coredata.OnePasswordConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read 1password connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.SCIMBridgeURL == "" {
|
||||
return nil, fmt.Errorf("cannot create 1password driver: scim_bridge_url is required")
|
||||
}
|
||||
|
||||
return drivers.NewOnePasswordDriver(c, s.SCIMBridgeURL), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
101
pkg/connector/provider/one_password_test.go
Normal file
101
pkg/connector/provider/one_password_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.gearno.de/kit/httpclient"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/connector"
|
||||
"go.probo.inc/probo/pkg/connector/provider"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
// TestOnePassword_NewDriver_DispatchByGrantType is the pre-merge gate
|
||||
// for the 1Password closure. The OnePassword registration dispatches
|
||||
// between two drivers based on the connector's OAuth2 grant type —
|
||||
// this test asserts both paths construct without error from a
|
||||
// coredata.Connector shaped for each grant type.
|
||||
func TestOnePassword_NewDriver_DispatchByGrantType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewBuiltinRegistry()
|
||||
reg, ok := r.Get(coredata.ConnectorProviderOnePassword)
|
||||
require.True(t, ok, "1Password provider must be registered")
|
||||
require.NotNil(t, reg.NewDriver, "1Password NewDriver closure must be wired")
|
||||
|
||||
t.Run("client_credentials uses Users API driver", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
raw, err := json.Marshal(&coredata.OnePasswordUsersAPISettings{
|
||||
AccountID: "test-account",
|
||||
Region: "us",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
conn := &coredata.Connector{
|
||||
Provider: coredata.ConnectorProviderOnePassword,
|
||||
RawSettings: raw,
|
||||
Connection: &connector.OAuth2Connection{
|
||||
GrantType: connector.OAuth2GrantTypeClientCredentials,
|
||||
},
|
||||
}
|
||||
|
||||
drv, err := reg.NewDriver(context.Background(), httpclient.DefaultClient(httpclient.WithSSRFProtection()), conn, nil)
|
||||
require.NoError(t, err)
|
||||
assert.IsType(t, &drivers.OnePasswordUsersAPIDriver{}, drv)
|
||||
})
|
||||
|
||||
t.Run("authorization_code uses SCIM-bridge driver", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
raw, err := json.Marshal(&coredata.OnePasswordConnectorSettings{
|
||||
SCIMBridgeURL: "https://scim.example.test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
conn := &coredata.Connector{
|
||||
Provider: coredata.ConnectorProviderOnePassword,
|
||||
RawSettings: raw,
|
||||
Connection: &connector.OAuth2Connection{
|
||||
GrantType: connector.OAuth2GrantTypeAuthorizationCode,
|
||||
},
|
||||
}
|
||||
|
||||
drv, err := reg.NewDriver(context.Background(), httpclient.DefaultClient(httpclient.WithSSRFProtection()), conn, nil)
|
||||
require.NoError(t, err)
|
||||
assert.IsType(t, &drivers.OnePasswordDriver{}, drv)
|
||||
})
|
||||
|
||||
t.Run("authorization_code without scim_bridge_url errors", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
conn := &coredata.Connector{
|
||||
Provider: coredata.ConnectorProviderOnePassword,
|
||||
Connection: &connector.OAuth2Connection{
|
||||
GrantType: connector.OAuth2GrantTypeAuthorizationCode,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := reg.NewDriver(context.Background(), httpclient.DefaultClient(httpclient.WithSSRFProtection()), conn, nil)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "scim_bridge_url is required")
|
||||
})
|
||||
}
|
||||
39
pkg/connector/provider/openai.go
Normal file
39
pkg/connector/provider/openai.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func openaiRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderOpenAI,
|
||||
DisplayName: "OpenAI",
|
||||
ProbeURL: "https://api.openai.com/v1/models",
|
||||
SupportsAPIKey: true,
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewOpenAIDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewOpenAINameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
54
pkg/connector/provider/pagerduty.go
Normal file
54
pkg/connector/provider/pagerduty.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func pagerdutyRegistration() *Registration {
|
||||
// PagerDuty Scoped OAuth requires PKCE (RFC 7636). The customer
|
||||
// subdomain surfaces as a callback query parameter (or
|
||||
// occasionally in the token response body) and is persisted on
|
||||
// PagerDutyConnectorSettings by the OAuth callback handler.
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderPagerDuty,
|
||||
DisplayName: "PagerDuty",
|
||||
AuthURL: "https://identity.pagerduty.com/oauth/authorize",
|
||||
TokenURL: "https://identity.pagerduty.com/oauth/token",
|
||||
ProbeURL: "https://api.pagerduty.com/users/me",
|
||||
OAuth2Scopes: []string{"users.read"},
|
||||
RequiresPKCE: true,
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
// PagerDuty's REST API uses the regional api.pagerduty.com host;
|
||||
// the driver does not consume the per-tenant subdomain.
|
||||
return drivers.NewPagerDutyDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, _ *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.PagerDutyConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read pagerduty connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewPagerDutyNameResolver(s.Subdomain)
|
||||
},
|
||||
}
|
||||
}
|
||||
134
pkg/connector/provider/registry.go
Normal file
134
pkg/connector/provider/registry.go
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// Package provider holds one Go file per connector provider. Each file
|
||||
// exposes a private constructor that returns a *Registration; the
|
||||
// builtin set is assembled by NewBuiltinRegistry, which probod calls
|
||||
// once at startup and threads as an explicit *Registry into every
|
||||
// consumer. The registry carries no package-level state.
|
||||
//
|
||||
// pkg/connector/provider is a sub-package of pkg/connector. The
|
||||
// child may import its parent (it does — for the *OAuth2Connector
|
||||
// type in apply.go); the parent must not import this child. Cycles
|
||||
// with pkg/coredata are avoided because the back-edge runs:
|
||||
// provider -> connector -> coredata -> (no further imports back).
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
// Registry holds the per-provider *Registration set used by the rest
|
||||
// of the system to look up display names, OAuth2 metadata, driver
|
||||
// constructors, and so on. It is safe for concurrent use.
|
||||
//
|
||||
// All consumers receive a *Registry constructed by NewBuiltinRegistry
|
||||
// at probod startup; no package-level singleton exists.
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
providers map[coredata.ConnectorProvider]*Registration
|
||||
}
|
||||
|
||||
// NewRegistry returns an empty *Registry. Production code uses
|
||||
// NewBuiltinRegistry; tests and specialised callers can construct an
|
||||
// empty Registry and register only the providers they need.
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
providers: make(map[coredata.ConnectorProvider]*Registration),
|
||||
}
|
||||
}
|
||||
|
||||
// Register adds a Registration to r. It returns an error on nil or
|
||||
// incomplete Registration metadata or on duplicate registration so
|
||||
// callers (in particular NewBuiltinRegistry) can decide whether the
|
||||
// condition is a programmer error worth crashing on or a recoverable
|
||||
// state worth surfacing.
|
||||
func (r *Registry) Register(reg *Registration) error {
|
||||
if reg == nil {
|
||||
return fmt.Errorf("cannot register connector provider: nil Registration")
|
||||
}
|
||||
|
||||
if reg.Provider == "" {
|
||||
return fmt.Errorf("cannot register connector provider: missing Provider")
|
||||
}
|
||||
|
||||
if reg.DisplayName == "" {
|
||||
return fmt.Errorf("cannot register connector provider %q: missing DisplayName", reg.Provider)
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if _, dup := r.providers[reg.Provider]; dup {
|
||||
return fmt.Errorf("cannot register connector provider %q: duplicate registration", reg.Provider)
|
||||
}
|
||||
|
||||
r.providers[reg.Provider] = reg
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the Registration for the given provider, or false if
|
||||
// no provider is registered under that key.
|
||||
func (r *Registry) Get(p coredata.ConnectorProvider) (*Registration, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
reg, ok := r.providers[p]
|
||||
|
||||
return reg, ok
|
||||
}
|
||||
|
||||
// All returns every Registration currently in r. Order is not stable;
|
||||
// callers must sort when determinism matters.
|
||||
func (r *Registry) All() []*Registration {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
out := make([]*Registration, 0, len(r.providers))
|
||||
for _, reg := range r.providers {
|
||||
out = append(out, reg)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ProviderDisplayName returns the human-readable label for the
|
||||
// provider, falling back to the raw constant string when no display
|
||||
// name is registered.
|
||||
func (r *Registry) ProviderDisplayName(p coredata.ConnectorProvider) string {
|
||||
if reg, ok := r.Get(p); ok && reg.DisplayName != "" {
|
||||
return reg.DisplayName
|
||||
}
|
||||
|
||||
return string(p)
|
||||
}
|
||||
|
||||
// ProviderOAuth2Scopes returns the OAuth2 scopes the access review
|
||||
// driver for the given provider needs to list user accounts. Returns
|
||||
// nil for providers that do not need any scopes (Notion, Intercom)
|
||||
// or for non-access-review providers.
|
||||
func (r *Registry) ProviderOAuth2Scopes(p coredata.ConnectorProvider) []string {
|
||||
if reg, ok := r.Get(p); ok {
|
||||
// Return a copy so callers cannot mutate the shared, concurrently
|
||||
// read registration slice held by this long-lived registry.
|
||||
return slices.Clone(reg.OAuth2Scopes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
156
pkg/connector/provider/registry_test.go
Normal file
156
pkg/connector/provider/registry_test.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.probo.inc/probo/pkg/connector/provider"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
// TestEveryProviderRegistered asserts that every
|
||||
// coredata.ConnectorProvider constant has a matching Registration in
|
||||
// the registry, that the registration carries the minimum metadata
|
||||
// (Provider, DisplayName), and that the access-review NewDriver
|
||||
// closure is wired — so the provider can actually drive a review.
|
||||
func TestEveryProviderRegistered(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewBuiltinRegistry()
|
||||
|
||||
for _, p := range coredata.ConnectorProviders() {
|
||||
t.Run(string(p), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
reg, ok := r.Get(p)
|
||||
require.Truef(t, ok, "provider %q has no Registration", p)
|
||||
require.NotNil(t, reg, "provider %q Registration is nil", p)
|
||||
require.Equalf(t, p, reg.Provider, "provider %q has mismatching Registration.Provider", p)
|
||||
assert.NotEmptyf(t, reg.DisplayName, "provider %q has empty DisplayName", p)
|
||||
assert.NotNilf(t, reg.NewDriver, "provider %q has nil NewDriver", p)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegistry_Register exercises the validation and duplicate-detection
|
||||
// paths on Register. Programmer errors at NewBuiltinRegistry time —
|
||||
// nil, empty Provider, empty DisplayName, duplicate — must all surface
|
||||
// as errors rather than silently registering a malformed entry.
|
||||
func TestRegistry_Register(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("nil Registration", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewRegistry()
|
||||
err := r.Register(nil)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "nil Registration")
|
||||
})
|
||||
|
||||
t.Run("empty Provider", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewRegistry()
|
||||
err := r.Register(&provider.Registration{DisplayName: "X"})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "missing Provider")
|
||||
})
|
||||
|
||||
t.Run("empty DisplayName", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewRegistry()
|
||||
err := r.Register(&provider.Registration{Provider: coredata.ConnectorProviderSlack})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "missing DisplayName")
|
||||
})
|
||||
|
||||
t.Run("duplicate registration", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewRegistry()
|
||||
require.NoError(t, r.Register(&provider.Registration{
|
||||
Provider: coredata.ConnectorProviderSlack,
|
||||
DisplayName: "Slack",
|
||||
}))
|
||||
err := r.Register(&provider.Registration{
|
||||
Provider: coredata.ConnectorProviderSlack,
|
||||
DisplayName: "Slack-bis",
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "duplicate registration")
|
||||
})
|
||||
|
||||
t.Run("valid Registration round-trips through Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewRegistry()
|
||||
want := &provider.Registration{
|
||||
Provider: coredata.ConnectorProviderSlack,
|
||||
DisplayName: "Slack",
|
||||
}
|
||||
require.NoError(t, r.Register(want))
|
||||
|
||||
got, ok := r.Get(coredata.ConnectorProviderSlack)
|
||||
require.True(t, ok)
|
||||
assert.Same(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
// TestRegistry_All asserts the registry returns the same number of
|
||||
// entries that have been registered. The builtin registry is the
|
||||
// canonical source of truth: every coredata.ConnectorProvider has
|
||||
// exactly one matching Registration, no more.
|
||||
func TestRegistry_All(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewBuiltinRegistry()
|
||||
assert.Len(t, r.All(), len(coredata.ConnectorProviders()))
|
||||
}
|
||||
|
||||
// TestRegistry_ProviderDisplayName covers the fallback path: an
|
||||
// unregistered provider returns its raw constant string.
|
||||
func TestRegistry_ProviderDisplayName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewBuiltinRegistry()
|
||||
assert.Equal(t, "Slack", r.ProviderDisplayName(coredata.ConnectorProviderSlack))
|
||||
assert.Equal(t, "UNKNOWN", r.ProviderDisplayName(coredata.ConnectorProvider("UNKNOWN")))
|
||||
}
|
||||
|
||||
// TestRegistry_ProviderOAuth2Scopes covers the nil path for an
|
||||
// unregistered provider and the populated path for a registered one.
|
||||
func TestRegistry_ProviderOAuth2Scopes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewBuiltinRegistry()
|
||||
assert.NotEmpty(t, r.ProviderOAuth2Scopes(coredata.ConnectorProviderSlack))
|
||||
assert.Nil(t, r.ProviderOAuth2Scopes(coredata.ConnectorProvider("UNKNOWN")))
|
||||
}
|
||||
|
||||
// TestRegistry_ProbeURL covers the registered and unregistered paths.
|
||||
// Slack ships a probe URL in its Registration; an unknown provider
|
||||
// returns the empty string.
|
||||
func TestRegistry_ProbeURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := provider.NewBuiltinRegistry()
|
||||
assert.NotEmpty(t, r.ProbeURL("SLACK"))
|
||||
assert.Empty(t, r.ProbeURL("UNKNOWN"))
|
||||
}
|
||||
39
pkg/connector/provider/resend.go
Normal file
39
pkg/connector/provider/resend.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func resendRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderResend,
|
||||
DisplayName: "Resend",
|
||||
ProbeURL: "https://api.resend.com/domains",
|
||||
SupportsAPIKey: true,
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewResendDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, _ *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewResendNameResolver()
|
||||
},
|
||||
}
|
||||
}
|
||||
69
pkg/connector/provider/sentry.go
Normal file
69
pkg/connector/provider/sentry.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func sentryRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderSentry,
|
||||
DisplayName: "Sentry",
|
||||
AuthURL: "https://sentry.io/oauth/authorize/",
|
||||
TokenURL: "https://sentry.io/oauth/token/",
|
||||
ProbeURL: "https://sentry.io/api/0/organizations/",
|
||||
OAuth2Scopes: []string{"org:read", "member:read"},
|
||||
SupportsAPIKey: true,
|
||||
ExtraSettings: []ExtraSetting{
|
||||
{Key: "organizationSlug", Label: "Organization Slug", Required: true},
|
||||
},
|
||||
MarshalSettings: func(in *SettingsInput) (json.RawMessage, error) {
|
||||
if in == nil || in.SentryOrganizationSlug == nil || *in.SentryOrganizationSlug == "" {
|
||||
return nil, fmt.Errorf("cannot create sentry connector: sentryOrganizationSlug is required")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.SentryConnectorSettings{OrganizationSlug: *in.SentryOrganizationSlug})
|
||||
},
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.SentryConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read sentry connector settings: %w", err)
|
||||
}
|
||||
|
||||
// OrganizationSlug may be empty for OAuth connections; the driver auto-discovers it.
|
||||
return drivers.NewSentryDriver(c, s.OrganizationSlug), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.SentryConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read sentry connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewSentryNameResolver(c, s.OrganizationSlug)
|
||||
},
|
||||
SetOrganizationSettings: func(c *coredata.Connector, slug string) error {
|
||||
return c.SetSettings(&coredata.SentryConnectorSettings{OrganizationSlug: slug})
|
||||
},
|
||||
}
|
||||
}
|
||||
41
pkg/connector/provider/slack.go
Normal file
41
pkg/connector/provider/slack.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func slackRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderSlack,
|
||||
DisplayName: "Slack",
|
||||
AuthURL: "https://slack.com/oauth/v2/authorize",
|
||||
TokenURL: "https://slack.com/api/oauth.v2.access",
|
||||
ProbeURL: "https://slack.com/api/users.list?limit=1",
|
||||
OAuth2Scopes: []string{"users:read", "users:read.email"},
|
||||
NewDriver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
return drivers.NewSlackDriver(c), nil
|
||||
},
|
||||
NewNameResolver: func(_ context.Context, c *http.Client, _ *coredata.Connector, _ *log.Logger) drivers.NameResolver {
|
||||
return drivers.NewSlackNameResolver(c)
|
||||
},
|
||||
}
|
||||
}
|
||||
66
pkg/connector/provider/supabase.go
Normal file
66
pkg/connector/provider/supabase.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func supabaseRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderSupabase,
|
||||
DisplayName: "Supabase",
|
||||
ProbeURL: "https://api.supabase.com/v1/organizations",
|
||||
SupportsAPIKey: true,
|
||||
ExtraSettings: []ExtraSetting{
|
||||
{Key: "organizationSlug", Label: "Organization Slug", Required: true},
|
||||
},
|
||||
MarshalSettings: func(in *SettingsInput) (json.RawMessage, error) {
|
||||
if in == nil || in.SupabaseOrganizationSlug == nil || *in.SupabaseOrganizationSlug == "" {
|
||||
return nil, fmt.Errorf("cannot create supabase connector: supabaseOrganizationSlug is required")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.SupabaseConnectorSettings{OrganizationSlug: *in.SupabaseOrganizationSlug})
|
||||
},
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.SupabaseConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read supabase connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.OrganizationSlug == "" {
|
||||
return nil, fmt.Errorf("cannot create supabase driver: organization_slug is required")
|
||||
}
|
||||
|
||||
return drivers.NewSupabaseDriver(c, s.OrganizationSlug), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, _ *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.SupabaseConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read supabase connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewSupabaseNameResolver(s.OrganizationSlug)
|
||||
},
|
||||
}
|
||||
}
|
||||
66
pkg/connector/provider/tally.go
Normal file
66
pkg/connector/provider/tally.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func tallyRegistration() *Registration {
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderTally,
|
||||
DisplayName: "Tally",
|
||||
ProbeURL: "https://api.tally.so/me",
|
||||
SupportsAPIKey: true,
|
||||
ExtraSettings: []ExtraSetting{
|
||||
{Key: "organizationId", Label: "Organization ID", Required: true},
|
||||
},
|
||||
MarshalSettings: func(in *SettingsInput) (json.RawMessage, error) {
|
||||
if in == nil || in.TallyOrganizationID == nil || *in.TallyOrganizationID == "" {
|
||||
return nil, fmt.Errorf("cannot create tally connector: tallyOrganizationId is required")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.TallyConnectorSettings{OrganizationID: *in.TallyOrganizationID})
|
||||
},
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.TallyConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read tally connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.OrganizationID == "" {
|
||||
return nil, fmt.Errorf("cannot create tally driver: organization_id is required")
|
||||
}
|
||||
|
||||
return drivers.NewTallyDriver(c, s.OrganizationID), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.TallyConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read tally connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewTallyNameResolver(c, s.OrganizationID)
|
||||
},
|
||||
}
|
||||
}
|
||||
101
pkg/connector/provider/types.go
Normal file
101
pkg/connector/provider/types.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
// Registration is the per-provider metadata + factory bundle. Each
|
||||
// provider returns one of these from a private constructor (e.g.
|
||||
// slackRegistration) that NewBuiltinRegistry assembles into the
|
||||
// runtime *Registry. Fields are grouped by concern: identity, OAuth2
|
||||
// metadata, supported protocols, extra settings, and factory closures.
|
||||
type Registration struct {
|
||||
// Identity.
|
||||
Provider coredata.ConnectorProvider
|
||||
DisplayName string
|
||||
|
||||
// OAuth2 metadata.
|
||||
AuthURL string
|
||||
TokenURL string
|
||||
ExtraAuthParams map[string]string
|
||||
TokenEndpointAuth string // "post-form" (default), "basic-form", or "basic-json"
|
||||
SupportsIncrementalAuth bool
|
||||
OAuth2Scopes []string
|
||||
ProbeURL string
|
||||
// RequiresPKCE enables RFC 7636 PKCE (S256) on the authorization
|
||||
// request and replays the verifier on the token exchange. Default
|
||||
// false; non-PKCE providers are unaffected.
|
||||
RequiresPKCE bool
|
||||
// AuthURLParams are operator-supplied placeholders substituted
|
||||
// into the static provider AuthURL (e.g. Vercel's
|
||||
// "{integration_slug}"). Empty for the vast majority of providers.
|
||||
AuthURLParams map[string]string
|
||||
|
||||
// Protocol support / GraphQL surface.
|
||||
SupportsAPIKey bool
|
||||
SupportsClientCredentials bool
|
||||
ExtraSettings []ExtraSetting
|
||||
|
||||
// Factory closures — wired by Stages 2 and 3.
|
||||
NewDriver func(context.Context, *http.Client, *coredata.Connector, *log.Logger) (drivers.Driver, error)
|
||||
NewNameResolver func(context.Context, *http.Client, *coredata.Connector, *log.Logger) drivers.NameResolver
|
||||
SetOrganizationSettings func(*coredata.Connector, string) error
|
||||
// MarshalSettings normalises the per-provider extra settings into
|
||||
// the JSON blob persisted on coredata.Connector.RawSettings.
|
||||
//
|
||||
// SECURITY CONTRACT: returned errors are surfaced verbatim to the
|
||||
// client via gqlutils.Invalid. They must contain only field names
|
||||
// and structural information — never user-supplied values,
|
||||
// secrets, or driver-internal details. Use a static string per
|
||||
// validation failure ("sentryOrganizationSlug is required",
|
||||
// "onePasswordRegion must be one of …"), never interpolate input.
|
||||
MarshalSettings func(*SettingsInput) (json.RawMessage, error)
|
||||
}
|
||||
|
||||
// SettingsInput is the union of every optional per-provider field
|
||||
// available on the GraphQL CreateAPIKeyConnectorInput and
|
||||
// CreateClientCredentialsConnectorInput types. The resolver populates
|
||||
// it once from the gqlgen input; each provider's MarshalSettings
|
||||
// reads only the fields it cares about.
|
||||
//
|
||||
// Adding a new provider with extra settings: add the optional field
|
||||
// here + the corresponding optional field on the GraphQL input + the
|
||||
// read in the per-provider MarshalSettings closure.
|
||||
type SettingsInput struct {
|
||||
TallyOrganizationID *string
|
||||
SentryOrganizationSlug *string
|
||||
SupabaseOrganizationSlug *string
|
||||
GitHubOrganization *string
|
||||
OnePasswordSCIMBridgeURL *string
|
||||
OnePasswordAccountID *string
|
||||
OnePasswordRegion *string
|
||||
}
|
||||
|
||||
// ExtraSetting describes one extra per-provider settings field
|
||||
// surfaced on ConnectorProviderInfo for the frontend to render.
|
||||
type ExtraSetting struct {
|
||||
Key string
|
||||
Label string
|
||||
Required bool
|
||||
}
|
||||
61
pkg/connector/provider/vercel.go
Normal file
61
pkg/connector/provider/vercel.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/accessreview/drivers"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func vercelRegistration() *Registration {
|
||||
// Vercel uses a templated AuthURL: the operator supplies an
|
||||
// `integration-slug` config field which is resolved into the
|
||||
// "{integration_slug}" placeholder by ApplyOAuth2Defaults.
|
||||
// Vercel does not use OAuth scopes — capabilities are pinned on
|
||||
// the integration registration in the Vercel dashboard.
|
||||
return &Registration{
|
||||
Provider: coredata.ConnectorProviderVercel,
|
||||
DisplayName: "Vercel",
|
||||
AuthURL: "https://vercel.com/integrations/{integration_slug}/new",
|
||||
TokenURL: "https://api.vercel.com/v2/oauth/access_token",
|
||||
ProbeURL: "https://api.vercel.com/v2/user",
|
||||
NewDriver: func(_ context.Context, c *http.Client, conn *coredata.Connector, _ *log.Logger) (drivers.Driver, error) {
|
||||
s, err := coredata.ConnectorSettings[coredata.VercelConnectorSettings](conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read vercel connector settings: %w", err)
|
||||
}
|
||||
|
||||
if s.TeamID == "" {
|
||||
return nil, fmt.Errorf("cannot create vercel driver: team_id is required")
|
||||
}
|
||||
|
||||
return drivers.NewVercelDriver(c, s.TeamID), nil
|
||||
},
|
||||
NewNameResolver: func(ctx context.Context, c *http.Client, conn *coredata.Connector, logger *log.Logger) drivers.NameResolver {
|
||||
s, err := coredata.ConnectorSettings[coredata.VercelConnectorSettings](conn)
|
||||
if err != nil {
|
||||
logger.ErrorCtx(ctx, "cannot read vercel connector settings", log.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return drivers.NewVercelNameResolver(c, s.TeamID)
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user