diff --git a/pkg/connector/provider/crisp.go b/pkg/connector/provider/crisp.go index 187bc8ff7..3fd90e0fd 100644 --- a/pkg/connector/provider/crisp.go +++ b/pkg/connector/provider/crisp.go @@ -37,6 +37,11 @@ func crispRegistration() *Registration { // deactivated until Crisp validates the production plugin and // activates with no code change once the token is set. ManagedAPIKey: true, + // The per-website plugin API also needs the plugin ID (a distinct value + // from the token identifier), supplied via bootstrap alongside the + // token. Require it so Crisp stays hidden until both are configured + // rather than surfacing as connectable and failing at connect time. + RequiresManagedResourceID: true, // Crisp authenticates with the plugin token presented as HTTP Basic, // the credential being the verbatim "identifier:key" pair. // APIKeyBasicAuthUserPass base64-encodes it (the empty-password diff --git a/pkg/connector/provider/registry.go b/pkg/connector/provider/registry.go index d938372f7..bd2b3ea09 100644 --- a/pkg/connector/provider/registry.go +++ b/pkg/connector/provider/registry.go @@ -304,6 +304,31 @@ func (r *Registry) ManagedResourceID(p coredata.ConnectorProvider) (string, bool return id, ok } +// ManagedConnectorReady reports whether a ManagedAPIKey provider is fully +// configured for this deployment: its Probo-held key is set and, when the +// provider also requires a resource ID (RequiresManagedResourceID, e.g. the +// Crisp plugin ID), that is set too. A provider that is not ready is kept out +// of the driver catalog, since connecting it would fail at verify time. It is +// false for non-managed and unregistered providers. +func (r *Registry) ManagedConnectorReady(p coredata.ConnectorProvider) bool { + reg, ok := r.Get(p) + if !ok || !reg.ManagedAPIKey { + return false + } + + if _, ok := r.ManagedAPIKey(p); !ok { + return false + } + + if reg.RequiresManagedResourceID { + if _, ok := r.ManagedResourceID(p); !ok { + return false + } + } + + return true +} + // 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) diff --git a/pkg/connector/provider/registry_test.go b/pkg/connector/provider/registry_test.go index 642a18216..2e30e184a 100644 --- a/pkg/connector/provider/registry_test.go +++ b/pkg/connector/provider/registry_test.go @@ -266,6 +266,35 @@ func TestCrispIsManagedAPIKey(t *testing.T) { assert.True(t, reg.ManagedAPIKey) assert.False(t, reg.SupportsAPIKey) assert.True(t, reg.APIKeyBasicAuthUserPass) + assert.True(t, reg.RequiresManagedResourceID, "crisp needs the plugin ID before it can connect") +} + +// TestRegistry_ManagedConnectorReady pins that a provider requiring a resource +// ID (Crisp's plugin ID) is reported ready, and thus surfaced in the catalog, +// only once BOTH the managed key and the resource ID are configured. +func TestRegistry_ManagedConnectorReady(t *testing.T) { + t.Parallel() + + t.Run("crisp needs both key and resource id", func(t *testing.T) { + t.Parallel() + + r := provider.NewBuiltinRegistry() + assert.False(t, r.ManagedConnectorReady(coredata.ConnectorProviderCrisp), "unconfigured") + + r.SetManagedAPIKey(coredata.ConnectorProviderCrisp, "identifier:secret") + assert.False(t, r.ManagedConnectorReady(coredata.ConnectorProviderCrisp), "key set but plugin id missing") + + r.SetManagedResourceID(coredata.ConnectorProviderCrisp, "plugin-id") + assert.True(t, r.ManagedConnectorReady(coredata.ConnectorProviderCrisp), "key and plugin id set") + }) + + t.Run("non-managed provider is never ready", func(t *testing.T) { + t.Parallel() + + r := provider.NewBuiltinRegistry() + r.SetManagedAPIKey(coredata.ConnectorProviderTally, "some-key") + assert.False(t, r.ManagedConnectorReady(coredata.ConnectorProviderTally)) + }) } // TestRegistry_RejectsManagedPlusCustomerCredential pins that a diff --git a/pkg/connector/provider/types.go b/pkg/connector/provider/types.go index 9a5b34af3..b15d3399c 100644 --- a/pkg/connector/provider/types.go +++ b/pkg/connector/provider/types.go @@ -124,6 +124,15 @@ type Registration struct { // injected key is presented on the wire. ManagedAPIKey bool + // RequiresManagedResourceID marks a ManagedAPIKey provider that also needs + // a Probo-supplied resource ID (Crisp's plugin ID, registered via + // (*Registry).SetManagedResourceID) before a connection can succeed. Such a + // provider stays out of the driver catalog until BOTH the managed key and + // the resource ID are configured, so the operator never sees it as + // connectable while a connect attempt would fail at verify time. See + // (*Registry).ManagedConnectorReady. + RequiresManagedResourceID bool + // BuildProbeURL derives a per-connector probe URL when the API host or // path depends on connector settings (e.g. a customer subdomain or // instance URL). Nil for providers with a static ProbeURL. diff --git a/pkg/server/api/console/v1/base_resolvers.go b/pkg/server/api/console/v1/base_resolvers.go index a9235f2de..73275f912 100644 --- a/pkg/server/api/console/v1/base_resolvers.go +++ b/pkg/server/api/console/v1/base_resolvers.go @@ -575,10 +575,12 @@ func (r *queryResolver) AccessReviewDrivers(ctx context.Context) ([]*types.Conne clientCredentialsSupported := reg.SupportsClientCredentials // ManagedAPIKey (Model B, e.g. Crisp) providers are connectable only - // once the operator configures the Probo-held key; until then they - // stay hidden, so such a provider ships deactivated. - _, hasManaged := r.providerRegistry.ManagedAPIKey(provider) - apiKeyManaged := reg.ManagedAPIKey && hasManaged + // once the operator configures the Probo-held key (and any required + // resource ID, e.g. Crisp's plugin ID); until then they stay hidden, so + // such a provider ships deactivated. Gating on full readiness keeps a + // half-configured provider out of the catalog rather than surfacing it + // and failing at connect time. + apiKeyManaged := r.providerRegistry.ManagedConnectorReady(provider) // Skip providers that cannot be connected in this deployment: no // OAuth client credentials configured and no key-based fallback