Files
probo/pkg/server/api/console/v1/base_resolvers.go
Bryan Frimin 4b64e59da4 Introduce access-review source snapshot and normalize naming
Decouple each campaign from the live access-review sources it was started
with by introducing a per-campaign source snapshot table
(access_review_campaign_sources). The snapshot captures the source name,
category, and connector at start time, so a review remains coherent even
after the underlying source is edited or deleted. Fetch tracking becomes
an append-only log (access_review_campaign_source_fetch_attempts) that
preserves every attempt with its own status and error rather than
overwriting a single row.

Rename the shared access-review tables and enums to use a consistent
access_review_ prefix throughout:

  access_entries          → access_review_entries
  access_sources          → access_review_sources
  access_source_category  → access_review_source_category
  access_entry_*          → access_review_entry_*

The same rename propagates to every coredata type, service, GraphQL
schema, MCP specification, CLI command, frontend component, and e2e test.
The accessreview package gains dedicated actions.go and policies.go files
for its own IAM policy set, mirroring the agentrun package pattern.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-06-15 15:33:22 +02:00

611 lines
19 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.90
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/iam"
"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 = iam.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.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.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, probo.ActionAccessReviewDriverCatalogList); 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
// Skip providers that cannot be connected in this deployment: no
// OAuth client credentials configured and no key-based fallback
// (API key or client credentials) supported.
if !oauthConfigured && !apiKeySupported && !clientCredentialsSupported {
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,
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
}
// 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 }
type queryResolver struct{ *Resolver }