Registration.ExtraSettings was a single flat list, but the API-key and client-credentials connect dialogs need different fields whenever a provider offers both paths, because a different create resolver and a different driver sits behind each. Replace it with APIKeyExtraSettings and ClientCredentialsExtraSettings, and split the GraphQL surface to match so a client cannot render one path's settings on the other. This fixes two connectors that could not be connected at all. 1Password declared accountId and region only, which are the client-credentials shape. The API-key dialog therefore rendered those two fields, mapAPIKeyExtraSettingToField returned nil for both so buildExtraFields discarded them, and the SCIM-bridge driver failed on an empty SCIMBridgeURL. The console already mapped scimBridgeUrl, but no registration declared that key, so the branch was dead. It now declares scimBridgeUrl on the API-key path and accountId + region on client credentials. Langfuse declared baseUrl as required, but mapAPIKeyExtraSettingToField had no LANGFUSE case, so buildExtraFields dropped the value the customer typed and the mutation failed with "langfuseBaseUrl is required". Every other extra-settings provider had a case. The GraphQL input field, the settings struct, the probe builder and the driver were all already correct; only the console mapping was missing. buildExtraFields now takes the settings list explicitly instead of reading it off the provider, so each dialog passes its own path's list and cannot silently iterate the other one. Register rejects a settings list for a path the provider does not offer, and an empty or duplicate setting key within one list. A key repeated across the two lists is allowed: that is how a dual-path provider declares a setting both dialogs need. The new resolver tests walk the whole chain the console walks, from the key a Registration declares through the mutation input field to the persisted settings struct, so a key renamed on one side and not the other fails in CI instead of at connect time. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
671 lines
22 KiB
Go
671 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.94
|
|
|
|
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/complianceportal/management"
|
|
"go.probo.inc/probo/pkg/coredata"
|
|
"go.probo.inc/probo/pkg/gid"
|
|
"go.probo.inc/probo/pkg/itam"
|
|
"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.CompliancePortalEntityType:
|
|
action = management.ActionCompliancePortalGet
|
|
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
|
|
compliancePortal, err := r.management.Get(ctx, scope, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewCompliancePortal(compliancePortal), nil
|
|
}
|
|
case coredata.CompliancePortalAccessEntityType:
|
|
action = management.ActionCompliancePortalAccessGet
|
|
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
|
|
compliancePortalAccess, err := r.management.GetAccess(ctx, scope, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewCompliancePortalAccess(compliancePortalAccess), 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.DeviceEntityType:
|
|
action = itam.ActionDeviceGet
|
|
loadNode = func(ctx context.Context, scope *coredata.Scope, id gid.GID) (types.Node, error) {
|
|
device, err := r.itam.GetDevice(ctx, scope, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewDevice(device), 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{}
|
|
}
|
|
|
|
var documentationURL *string
|
|
if reg.DocumentationURL != "" {
|
|
documentationURL = new(reg.DocumentationURL)
|
|
}
|
|
|
|
// The two settings lists are surfaced separately, never merged: a
|
|
// provider offering both connect paths (1Password) needs different
|
|
// fields on each, so a client that saw one flat list would render the
|
|
// wrong fields on one of the two dialogs.
|
|
infos = append(infos, &types.ConnectorProviderInfo{
|
|
Provider: provider,
|
|
DisplayName: reg.DisplayName,
|
|
DocumentationURL: documentationURL,
|
|
OauthConfigured: oauthConfigured,
|
|
APIKeySupported: apiKeySupported,
|
|
APIKeyManaged: apiKeyManaged,
|
|
ClientCredentialsSupported: clientCredentialsSupported,
|
|
Oauth2Scopes: scopes,
|
|
APIKeyExtraSettings: connectorProviderSettingInfos(reg.APIKeyExtraSettings),
|
|
ClientCredentialsExtraSettings: connectorProviderSettingInfos(reg.ClientCredentialsExtraSettings),
|
|
})
|
|
}
|
|
|
|
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 }
|
|
)
|