Verify Crisp website ownership before connecting
Crisp is a managed (Model B) connector: Probo holds one plugin token server-side and each connection carries only a Website ID. Nothing stops one organization from entering another organization's Website ID, so prove control of the website before creating the connection. Probo derives a per-(organization, website) verification code as an HMAC over the token secret and exposes it through a new crispVerificationCode query. The customer pastes it into the Probo plugin's per-website settings; at connect time the resolver reads the setting back through the managed plugin token and requires a constant-time match before any row is written. The managed key and plugin ID come from bootstrap, so the connector stays hidden until the deployment configures them. The settings fetch is injected so the create-time gate's branch wiring is unit-tested (mismatch and not-subscribed reject, internal errors stay generic, a matching code passes), and the managed-versus-client key resolution is covered too. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
@@ -42,6 +42,18 @@ import (
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
providers map[coredata.ConnectorProvider]*Registration
|
||||
// managedAPIKeys holds the Probo-supplied API key for providers with
|
||||
// ManagedAPIKey registrations (e.g. Crisp's marketplace plugin token).
|
||||
// Populated by probod from bootstrap config via SetManagedAPIKey; empty
|
||||
// until the operator configures the credential.
|
||||
managedAPIKeys map[coredata.ConnectorProvider]string
|
||||
// managedResourceIDs holds an optional Probo-supplied resource identifier
|
||||
// for a ManagedAPIKey provider, distinct from the credential. Crisp needs
|
||||
// it: the plugin token's Basic identifier is not the plugin ID, yet the
|
||||
// per-website plugin API (used for ownership verification) requires the
|
||||
// plugin ID in the path. Populated by probod via SetManagedResourceID;
|
||||
// empty for providers that need no such identifier.
|
||||
managedResourceIDs map[coredata.ConnectorProvider]string
|
||||
}
|
||||
|
||||
// NewRegistry returns an empty *Registry. Production code uses
|
||||
@@ -49,7 +61,9 @@ type Registry struct {
|
||||
// empty Registry and register only the providers they need.
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
providers: make(map[coredata.ConnectorProvider]*Registration),
|
||||
providers: make(map[coredata.ConnectorProvider]*Registration),
|
||||
managedAPIKeys: make(map[coredata.ConnectorProvider]string),
|
||||
managedResourceIDs: make(map[coredata.ConnectorProvider]string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +112,14 @@ func (r *Registry) Register(reg *Registration) error {
|
||||
return fmt.Errorf("cannot register connector provider %q: APIKeyBasicAuth, APIKeyBasicAuthUserPass, APIKeyHeader, and APIKeyAuthScheme are mutually exclusive", reg.Provider)
|
||||
}
|
||||
|
||||
// ManagedAPIKey injects a Probo-held key and ignores any customer
|
||||
// credential, so pairing it with SupportsAPIKey/SupportsClientCredentials
|
||||
// would advertise a credential field whose value is silently discarded —
|
||||
// the same silent-winner class rejected above. Reject it at startup.
|
||||
if reg.ManagedAPIKey && (reg.SupportsAPIKey || reg.SupportsClientCredentials) {
|
||||
return fmt.Errorf("cannot register connector provider %q: ManagedAPIKey is mutually exclusive with SupportsAPIKey and SupportsClientCredentials", reg.Provider)
|
||||
}
|
||||
|
||||
// BuildTokenURLForDomain and BuildTokenURLForSite both build the token
|
||||
// endpoint host, but from different sources (a callback param vs. the
|
||||
// signed state). CompleteWithState checks them in order, so setting both
|
||||
@@ -224,6 +246,64 @@ func (r *Registry) APIKeyUsesBasicAuthUserPass(p coredata.ConnectorProvider) boo
|
||||
return false
|
||||
}
|
||||
|
||||
// SetManagedAPIKey records the Probo-supplied API key for a
|
||||
// ManagedAPIKey provider (e.g. Crisp). probod calls this from bootstrap
|
||||
// config so the create-connector resolver can inject the key and the
|
||||
// driver catalog can surface the provider. An empty key is treated as
|
||||
// "not configured": it is not stored, keeping the provider hidden.
|
||||
func (r *Registry) SetManagedAPIKey(p coredata.ConnectorProvider, key string) {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.managedAPIKeys[p] = key
|
||||
}
|
||||
|
||||
// ManagedAPIKey returns the Probo-supplied API key configured for a
|
||||
// ManagedAPIKey provider and whether one is set. The boolean is false
|
||||
// (and the string empty) until the operator configures the credential
|
||||
// via bootstrap, which is what keeps such a provider deactivated.
|
||||
func (r *Registry) ManagedAPIKey(p coredata.ConnectorProvider) (string, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
key, ok := r.managedAPIKeys[p]
|
||||
|
||||
return key, ok
|
||||
}
|
||||
|
||||
// SetManagedResourceID records an optional Probo-supplied resource
|
||||
// identifier for a ManagedAPIKey provider (e.g. the Crisp plugin ID used
|
||||
// by the per-website plugin API). probod calls this from bootstrap config
|
||||
// alongside SetManagedAPIKey. An empty id is treated as "not configured":
|
||||
// it is not stored.
|
||||
func (r *Registry) SetManagedResourceID(p coredata.ConnectorProvider, id string) {
|
||||
if id == "" {
|
||||
return
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
r.managedResourceIDs[p] = id
|
||||
}
|
||||
|
||||
// ManagedResourceID returns the Probo-supplied resource identifier
|
||||
// configured for a ManagedAPIKey provider and whether one is set. The
|
||||
// boolean is false (and the string empty) until the operator configures it
|
||||
// via bootstrap.
|
||||
func (r *Registry) ManagedResourceID(p coredata.ConnectorProvider) (string, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
id, ok := r.managedResourceIDs[p]
|
||||
|
||||
return id, ok
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
Reference in New Issue
Block a user