From 557028e327f5278e6892aa777662f11d1c3c2fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Sibiril?= <81782+aureliensibiril@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:55:33 +0200 Subject: [PATCH] Hide managed connectors until fully configured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A managed (Model B) connector like Crisp needs both the Probo-held key and a resource ID (the plugin ID) to connect, but the driver catalog gated visibility on the key alone. A deployment that set the key without the plugin ID (reachable through raw JSON config; the bootstrap env path already requires both) would show Crisp as connectable and then fail every attempt with an internal error. Add a RequiresManagedResourceID flag to the registration and a Registry.ManagedConnectorReady check that requires both before a managed provider enters the catalog, so a half-configured provider stays hidden instead of dead-ending at connect. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com> --- pkg/connector/provider/crisp.go | 5 ++++ pkg/connector/provider/registry.go | 25 ++++++++++++++++++ pkg/connector/provider/registry_test.go | 29 +++++++++++++++++++++ pkg/connector/provider/types.go | 9 +++++++ pkg/server/api/console/v1/base_resolvers.go | 10 ++++--- 5 files changed, 74 insertions(+), 4 deletions(-) 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