Files
probo/pkg/server/api/console/v1/base_resolvers.go
Aurélien Sibiril 557028e327 Hide managed connectors until fully configured
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>
2026-07-11 17:55:33 +02:00

660 lines
22 KiB
Go

package console_v1
// This file will be automatically regenerated based on the schema, any resolver
// implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.93
import (
"context"
"errors"
"slices"
"strings"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/accessreview"
"go.probo.inc/probo/pkg/agentrun"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/server/api/authn"
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
"go.probo.inc/probo/pkg/server/api/console/v1/types"
"go.probo.inc/probo/pkg/server/gqlutils"
)
// Node is the resolver for the node field.
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
var (
loadNode func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error)
action string
)
switch id.EntityType() {
case coredata.OrganizationEntityType:
action = probo.ActionOrganizationGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
organization, err := r.probo.Organizations.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewOrganization(organization), nil
}
case coredata.ThirdPartyEntityType:
action = probo.ActionThirdPartyGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
thirdParty, err := r.probo.ThirdParties.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewThirdParty(thirdParty), nil
}
case coredata.FrameworkEntityType:
action = probo.ActionFrameworkGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
framework, err := r.probo.Frameworks.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewFramework(framework), nil
}
case coredata.MeasureEntityType:
action = probo.ActionMeasureGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
measure, err := r.probo.Measures.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewMeasure(measure), nil
}
case coredata.TaskEntityType:
action = probo.ActionTaskGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
task, err := r.probo.Tasks.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewTask(task), nil
}
case coredata.EvidenceEntityType:
action = probo.ActionEvidenceList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
evidence, err := r.probo.Evidences.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewEvidence(evidence), nil
}
case coredata.DocumentEntityType:
action = probo.ActionDocumentGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
document, err := r.probo.Documents.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewDocument(document), nil
}
case coredata.ControlEntityType:
action = probo.ActionControlList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
control, err := r.probo.Controls.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewControl(control), nil
}
case coredata.ApplicabilityStatementEntityType:
action = probo.ActionApplicabilityStatementGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
applicabilityStatement, err := r.probo.StatementsOfApplicability.GetApplicabilityStatement(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewApplicabilityStatement(applicabilityStatement), nil
}
case coredata.RiskEntityType:
action = probo.ActionRiskGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
risk, err := r.probo.Risks.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewRisk(risk), nil
}
case coredata.RiskAssessmentEntityType:
action = probo.ActionRiskAssessmentGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
ra, err := r.riskManagement.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewRiskAssessment(ra), nil
}
case coredata.RiskAssessmentNodeEntityType:
action = probo.ActionRiskAssessmentNodeGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
n, err := r.riskManagement.GetNode(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewRiskAssessmentNode(n), nil
}
case coredata.RiskAssessmentProcessEntityType:
action = probo.ActionRiskAssessmentProcessGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
p, err := r.riskManagement.GetProcess(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewRiskAssessmentProcess(p), nil
}
case coredata.RiskAssessmentThreatEntityType:
action = probo.ActionRiskAssessmentThreatGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
t, err := r.riskManagement.GetThreat(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewRiskAssessmentThreat(t), nil
}
case coredata.RiskAssessmentScopeEntityType:
action = probo.ActionRiskAssessmentScopeGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
s, err := r.riskManagement.GetScope(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewRiskAssessmentScope(s), nil
}
case coredata.RiskAssessmentBoundaryEntityType:
action = probo.ActionRiskAssessmentBoundaryGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
b, err := r.riskManagement.GetBoundary(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewRiskAssessmentBoundary(b), nil
}
case coredata.RiskAssessmentScenarioEntityType:
action = probo.ActionRiskAssessmentScenarioGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
s, err := r.riskManagement.GetScenario(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewRiskAssessmentScenario(s), nil
}
case coredata.ThirdPartyComplianceReportEntityType:
action = probo.ActionThirdPartyComplianceReportGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
thirdPartyComplianceReport, err := r.probo.ThirdPartyComplianceReports.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewThirdPartyComplianceReport(thirdPartyComplianceReport), nil
}
case coredata.ThirdPartyContactEntityType:
action = probo.ActionThirdPartyContactGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
thirdPartyContact, err := r.probo.ThirdPartyContacts.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewThirdPartyContact(thirdPartyContact), nil
}
case coredata.ThirdPartyServiceEntityType:
action = probo.ActionThirdPartyServiceGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
thirdPartyService, err := r.probo.ThirdPartyServices.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewThirdPartyService(thirdPartyService), nil
}
case coredata.DocumentVersionEntityType:
action = probo.ActionDocumentVersionList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
documentVersion, err := r.probo.Documents.GetVersion(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewDocumentVersion(documentVersion), nil
}
case coredata.DocumentVersionSignatureEntityType:
action = probo.ActionDocumentVersionSignatureList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
documentVersionSignature, err := r.probo.Documents.GetVersionSignature(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewDocumentVersionSignature(documentVersionSignature), nil
}
case coredata.AssetEntityType:
action = probo.ActionAssetList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
asset, err := r.probo.Assets.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewAsset(asset), nil
}
case coredata.DatumEntityType:
action = probo.ActionDatumList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
datum, err := r.probo.Data.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewDatum(datum), nil
}
case coredata.AuditEntityType:
action = probo.ActionAuditList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
audit, err := r.probo.Audits.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewAudit(audit), nil
}
case coredata.FindingEntityType:
action = probo.ActionFindingList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
finding, err := r.probo.Findings.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewFinding(finding), nil
}
case coredata.ObligationEntityType:
action = probo.ActionObligationList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
obligation, err := r.probo.Obligations.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewObligation(obligation), nil
}
case coredata.ProcessingActivityEntityType:
action = probo.ActionProcessingActivityList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
processingActivity, err := r.probo.ProcessingActivities.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewProcessingActivity(processingActivity), nil
}
case coredata.DataProtectionImpactAssessmentEntityType:
// TODO: add action
// action = probo.ActionDataProtectionImpactAssessmentGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
dpia, err := r.probo.DataProtectionImpactAssessments.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewDataProtectionImpactAssessment(dpia), nil
}
case coredata.TransferImpactAssessmentEntityType:
// TODO: add action
//action = probo.ActionTransferImpactAssessmentGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
tia, err := r.probo.TransferImpactAssessments.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewTransferImpactAssessment(tia), nil
}
case coredata.TrustCenterEntityType:
action = probo.ActionTrustCenterGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
trustCenter, err := r.probo.TrustCenters.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewTrustCenter(trustCenter), nil
}
case coredata.TrustCenterAccessEntityType:
action = probo.ActionTrustCenterAccessGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
trustCenterAccess, err := r.probo.TrustCenterAccesses.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewTrustCenterAccess(trustCenterAccess), nil
}
case coredata.RightsRequestEntityType:
action = probo.ActionRightsRequestGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
rightsRequest, err := r.probo.RightsRequests.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewRightsRequest(rightsRequest), nil
}
case coredata.StatementOfApplicabilityEntityType:
action = probo.ActionStatementOfApplicabilityGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
statementOfApplicability, err := r.probo.StatementsOfApplicability.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewStatementOfApplicability(statementOfApplicability), nil
}
case coredata.WebhookSubscriptionEntityType:
action = probo.ActionWebhookSubscriptionGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
wc, err := r.probo.WebhookSubscriptions.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewWebhookSubscription(wc), nil
}
case coredata.AgentRunEntityType:
action = agentrun.ActionAgentRunGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
run, err := r.agentRun.Get(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewAgentRun(run), nil
}
case coredata.AccessReviewCampaignEntityType:
action = accessreview.ActionCampaignGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
campaign, err := r.accessReview.GetCampaign(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewAccessReviewCampaign(campaign), nil
}
case coredata.AccessReviewCampaignSourceEntityType:
action = accessreview.ActionCampaignGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
campaignSource, err := r.accessReview.GetCampaignSource(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewAccessReviewCampaignSource(campaignSource), nil
}
case coredata.AccessReviewSourceEntityType:
action = accessreview.ActionSourceGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
source, err := r.accessReview.GetSource(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewAccessReviewSource(source), nil
}
case coredata.AccessReviewEntryEntityType:
action = accessreview.ActionEntryGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
entry, err := r.accessReview.GetEntry(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewAccessReviewEntry(entry), nil
}
case coredata.CookieBannerEntityType:
action = probo.ActionCookieBannerGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
banner, err := r.cookieBanner.GetCookieBanner(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewCookieBanner(banner), nil
}
case coredata.CookieCategoryEntityType:
action = probo.ActionCookieCategoryGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
category, err := r.cookieBanner.GetCookieCategory(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewCookieCategory(category), nil
}
case coredata.CookieConsentRecordEntityType:
action = probo.ActionCookieConsentRecordList
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
record, err := r.cookieBanner.GetCookieConsentRecord(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewCookieConsentRecord(record), nil
}
case coredata.TrackerPatternEntityType:
action = probo.ActionTrackerPatternGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
pattern, err := r.cookieBanner.GetTrackerPattern(ctx, scope, id)
if err != nil {
return nil, err
}
return types.NewTrackerPatternNode(pattern), nil
}
case coredata.CookieBannerVersionEntityType:
action = probo.ActionCookieBannerVersionGet
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
version, err := r.cookieBanner.GetCookieBannerVersion(ctx, scope, id)
if err != nil {
return nil, err
}
return &types.CookieBannerVersion{
ID: version.ID,
Version: version.Version,
State: string(version.State),
CreatedAt: version.CreatedAt,
UpdatedAt: version.UpdatedAt,
}, nil
}
default:
}
scope, err := r.authorize(ctx, id, action)
if err != nil {
return nil, err
}
node, err := loadNode(ctx, scope, id)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot load node", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return node, nil
}
// Viewer is the resolver for the viewer field.
func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) {
identity := authn.IdentityFromContext(ctx)
session := authn.SessionFromContext(ctx)
apiKey := authn.APIKeyFromContext(ctx)
var viewerID gid.GID
if session != nil {
viewerID = session.ID
} else if apiKey != nil {
viewerID = apiKey.ID
} else {
viewerID = identity.ID
}
return &types.Viewer{ID: viewerID}, nil
}
// CommonThirdParties is the resolver for the commonThirdParties field.
func (r *queryResolver) CommonThirdParties(ctx context.Context, name string) ([]*types.CommonThirdParty, error) {
identity := authn.IdentityFromContext(ctx)
if _, err := r.authorize(ctx, identity.ID, probo.ActionCommonThirdPartyList); err != nil {
return nil, err
}
parties, err := r.thirdParty.Search(ctx, name)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot search common third parties", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
result := make([]*types.CommonThirdParty, len(parties))
for i, p := range parties {
result[i] = types.NewCommonThirdParty(p)
}
return result, nil
}
// AccessReviewDrivers is the resolver for the accessReviewDrivers field.
func (r *queryResolver) AccessReviewDrivers(ctx context.Context) ([]*types.ConnectorProviderInfo, error) {
identity := authn.IdentityFromContext(ctx)
if _, err := r.authorize(ctx, identity.ID, accessreview.ActionDriverCatalogList); err != nil {
return nil, err
}
registrations := r.providerRegistry.All()
infos := make([]*types.ConnectorProviderInfo, 0, len(registrations))
for _, reg := range registrations {
if reg == nil || reg.NewDriver == nil {
continue
}
provider := reg.Provider
_, oauthErr := r.connectorRegistry.Get(string(provider))
oauthConfigured := oauthErr == nil
apiKeySupported := reg.SupportsAPIKey
clientCredentialsSupported := reg.SupportsClientCredentials
// ManagedAPIKey (Model B, e.g. Crisp) providers are connectable only
// 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
// (API key, managed API key, or client credentials) supported.
if !oauthConfigured && !apiKeySupported && !clientCredentialsSupported && !apiKeyManaged {
continue
}
scopes := r.providerRegistry.ProviderOAuth2Scopes(provider)
if scopes == nil {
scopes = []string{}
}
extraSettings := make([]*types.ConnectorProviderSettingInfo, 0, len(reg.ExtraSettings))
for _, setting := range reg.ExtraSettings {
extraSettings = append(
extraSettings,
&types.ConnectorProviderSettingInfo{
Key: setting.Key,
Label: setting.Label,
Required: setting.Required,
},
)
}
infos = append(infos, &types.ConnectorProviderInfo{
Provider: provider,
DisplayName: reg.DisplayName,
OauthConfigured: oauthConfigured,
APIKeySupported: apiKeySupported,
APIKeyManaged: apiKeyManaged,
ClientCredentialsSupported: clientCredentialsSupported,
Oauth2Scopes: scopes,
ExtraSettings: extraSettings,
})
}
slices.SortFunc(
infos,
func(a, b *types.ConnectorProviderInfo) int {
return strings.Compare(a.DisplayName, b.DisplayName)
},
)
return infos, nil
}
// CrispVerificationCode is the resolver for the crispVerificationCode field. It
// returns the deterministic ownership-verification code the customer must paste
// into the Probo plugin's per-website settings in their Crisp dashboard before
// connecting that website. Authorized against the organization with the same
// action as the create mutation because the code is organization-bound. This is
// a UI-only helper; MCP/CLI/n8n are intentionally not extended.
func (r *queryResolver) CrispVerificationCode(ctx context.Context, organizationID gid.GID, websiteID string) (string, error) {
if _, err := r.authorize(ctx, organizationID, probo.ActionConnectorCreate); err != nil {
return "", err
}
websiteID = strings.TrimSpace(websiteID)
if websiteID == "" {
return "", gqlutils.Invalidf(ctx, "websiteId is required")
}
return computeCrispVerificationCode(r.tokenSecret, organizationID.String(), websiteID), nil
}
// Mutation returns schema.MutationResolver implementation.
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
// Query returns schema.QueryResolver implementation.
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
type (
mutationResolver struct{ *Resolver }
queryResolver struct{ *Resolver }
)