Files
probo/pkg/server/api/console/v1/access_review_campaign_resolvers.go
2026-07-30 15:43:47 +02:00

1131 lines
38 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"
"fmt"
"github.com/vikstrous/dataloadgen"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/accessreview"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/errorx"
"go.probo.inc/probo/pkg/page"
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/server/api/authn"
"go.probo.inc/probo/pkg/server/api/console/v1/dataloader"
"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"
)
// Organization is the resolver for the organization field.
func (r *accessReviewCampaignResolver) Organization(ctx context.Context, obj *types.AccessReviewCampaign) (*types.Organization, error) {
if _, err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil {
return nil, err
}
loaders := dataloader.FromContext(ctx)
organization, err := loaders.Organization.Load(ctx, obj.Organization.ID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewOrganization(organization), nil
}
// Sources is the resolver for the sources field.
func (r *accessReviewCampaignResolver) Sources(ctx context.Context, obj *types.AccessReviewCampaign) ([]*types.AccessReviewCampaignSource, error) {
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionCampaignGet)
if err != nil {
return nil, err
}
campaignSources, err := r.accessReview.ListCampaignSources(ctx, scope, obj.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot list campaign sources", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
result := make([]*types.AccessReviewCampaignSource, len(campaignSources))
for i, campaignSource := range campaignSources {
result[i] = types.NewAccessReviewCampaignSource(campaignSource)
}
return result, nil
}
// Entries is the resolver for the entries field.
func (r *accessReviewCampaignResolver) Entries(ctx context.Context, obj *types.AccessReviewCampaign, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewEntryOrder, filter *coredata.AccessReviewEntryFilter) (*types.AccessReviewEntryConnection, error) {
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionEntryList)
if err != nil {
return nil, err
}
pageOrderBy := page.OrderBy[coredata.AccessReviewEntryOrderField]{
Field: coredata.AccessReviewEntryOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.AccessReviewEntryOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
p, err := r.accessReview.ListEntriesForCampaignID(ctx, scope, obj.ID, cursor, filter)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot list access entries", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewAccessReviewEntryConnection(p, r, obj.ID, nil, filter), nil
}
// Statistics is the resolver for the statistics field.
func (r *accessReviewCampaignResolver) Statistics(ctx context.Context, obj *types.AccessReviewCampaign) (*types.AccessReviewStatistics, error) {
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionEntryList)
if err != nil {
return nil, err
}
stats, err := r.accessReview.CampaignStatistics(ctx, scope, obj.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get campaign statistics", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewAccessReviewStatistics(stats), nil
}
// Permission is the resolver for the permission field.
func (r *accessReviewCampaignResolver) Permission(ctx context.Context, obj *types.AccessReviewCampaign, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field.
func (r *accessReviewCampaignConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessReviewCampaignConnection) (int, error) {
scope, err := r.authorize(ctx, obj.ParentID, accessreview.ActionCampaignList)
if err != nil {
return 0, err
}
switch obj.Resolver.(type) {
case *organizationResolver:
count, err := r.accessReview.CountCampaignsForOrganizationID(ctx, scope, obj.ParentID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot count access review campaigns", log.Error(err))
return 0, gqlutils.Internal(ctx)
}
return count, nil
}
r.logger.ErrorCtx(ctx, "unsupported resolver", log.String("resolver", fmt.Sprintf("%T", obj.Resolver)))
return 0, gqlutils.Internal(ctx)
}
// Campaign is the resolver for the campaign field.
func (r *accessReviewCampaignSourceResolver) Campaign(ctx context.Context, obj *types.AccessReviewCampaignSource) (*types.AccessReviewCampaign, error) {
scope, err := r.authorize(ctx, obj.Campaign.ID, accessreview.ActionCampaignGet)
if err != nil {
return nil, err
}
campaign, err := r.accessReview.GetCampaign(ctx, scope, obj.Campaign.ID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot get access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewAccessReviewCampaign(campaign), nil
}
// Source is the resolver for the source field.
func (r *accessReviewCampaignSourceResolver) Source(ctx context.Context, obj *types.AccessReviewCampaignSource) (*types.AccessReviewSource, error) {
if obj.SourceID == nil {
return nil, nil
}
scope, err := r.authorize(ctx, obj.Campaign.ID, accessreview.ActionCampaignGet)
if err != nil {
return nil, err
}
source, err := r.accessReview.GetSource(ctx, scope, *obj.SourceID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, nil
}
r.logger.ErrorCtx(ctx, "cannot get access review source", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewAccessReviewSource(source), nil
}
// FetchAttempts is the resolver for the fetchAttempts field.
func (r *accessReviewCampaignSourceResolver) FetchAttempts(ctx context.Context, obj *types.AccessReviewCampaignSource, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewCampaignSourceFetchAttemptOrder) (*types.AccessReviewCampaignSourceFetchAttemptConnection, error) {
scope, err := r.authorize(ctx, obj.Campaign.ID, accessreview.ActionCampaignGet)
if err != nil {
return nil, err
}
pageOrderBy := page.OrderBy[coredata.AccessReviewCampaignSourceFetchAttemptOrderField]{
Field: coredata.AccessReviewCampaignSourceFetchAttemptOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.AccessReviewCampaignSourceFetchAttemptOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
p, err := r.accessReview.ListFetchAttemptsForCampaignSourceID(ctx, scope, obj.ID, cursor)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot list fetch attempts", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewAccessReviewCampaignSourceFetchAttemptConnection(p, r, obj.ID), nil
}
// Entries is the resolver for the entries field.
func (r *accessReviewCampaignSourceResolver) Entries(ctx context.Context, obj *types.AccessReviewCampaignSource, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewEntryOrder, filter *coredata.AccessReviewEntryFilter) (*types.AccessReviewEntryConnection, error) {
scope, err := r.authorize(ctx, obj.Campaign.ID, accessreview.ActionEntryList)
if err != nil {
return nil, err
}
pageOrderBy := page.OrderBy[coredata.AccessReviewEntryOrderField]{
Field: coredata.AccessReviewEntryOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.AccessReviewEntryOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
p, err := r.accessReview.ListEntriesForCampaignIDAndSourceID(ctx, scope, obj.Campaign.ID, obj.ID, cursor, filter)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot list access entries", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
sourceID := obj.ID
return types.NewAccessReviewEntryConnection(p, r, obj.Campaign.ID, &sourceID, filter), nil
}
// Statistics is the resolver for the statistics field.
func (r *accessReviewCampaignSourceResolver) Statistics(ctx context.Context, obj *types.AccessReviewCampaignSource) (*types.AccessReviewStatistics, error) {
scope, err := r.authorize(ctx, obj.Campaign.ID, accessreview.ActionEntryList)
if err != nil {
return nil, err
}
stats, err := r.accessReview.CampaignSourceStatistics(ctx, scope, obj.Campaign.ID, obj.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get source statistics", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewAccessReviewStatistics(stats), nil
}
// TotalCount is the resolver for the totalCount field.
func (r *accessReviewCampaignSourceFetchAttemptConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessReviewCampaignSourceFetchAttemptConnection) (int, error) {
scope, err := r.authorize(ctx, obj.ParentID, accessreview.ActionCampaignGet)
if err != nil {
return 0, err
}
count, err := r.accessReview.CountFetchAttemptsForCampaignSourceID(ctx, scope, obj.ParentID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot count fetch attempts", log.Error(err))
return 0, gqlutils.Internal(ctx)
}
return count, nil
}
// Campaign is the resolver for the campaign field.
func (r *accessReviewEntryResolver) Campaign(ctx context.Context, obj *types.AccessReviewEntry) (*types.AccessReviewCampaign, error) {
scope, err := r.authorize(ctx, obj.Campaign.ID, accessreview.ActionCampaignGet)
if err != nil {
return nil, err
}
campaign, err := r.accessReview.GetCampaign(ctx, scope, obj.Campaign.ID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot get access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewAccessReviewCampaign(campaign), nil
}
// CampaignSource is the resolver for the campaignSource field.
func (r *accessReviewEntryResolver) CampaignSource(ctx context.Context, obj *types.AccessReviewEntry) (*types.AccessReviewCampaignSource, error) {
scope, err := r.authorize(ctx, obj.Campaign.ID, accessreview.ActionCampaignGet)
if err != nil {
return nil, err
}
campaignSource, err := r.accessReview.GetCampaignSource(ctx, scope, obj.CampaignSource.ID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot get campaign source", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewAccessReviewCampaignSource(campaignSource), nil
}
// DecisionHistory is the resolver for the decisionHistory field.
func (r *accessReviewEntryResolver) DecisionHistory(ctx context.Context, obj *types.AccessReviewEntry) ([]*types.AccessReviewEntryDecisionHistoryEntry, error) {
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionEntryGet)
if err != nil {
return nil, err
}
histories, err := r.accessReview.EntryDecisionHistory(ctx, scope, obj.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get decision history", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
result := make([]*types.AccessReviewEntryDecisionHistoryEntry, len(histories))
for i, h := range histories {
result[i] = types.NewAccessReviewEntryDecisionHistoryEntry(h)
}
return result, nil
}
// Permission is the resolver for the permission field.
func (r *accessReviewEntryResolver) Permission(ctx context.Context, obj *types.AccessReviewEntry, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field.
func (r *accessReviewEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessReviewEntryConnection) (int, error) {
scope, err := r.authorize(ctx, obj.ParentID, accessreview.ActionEntryList)
if err != nil {
return 0, err
}
switch obj.Resolver.(type) {
case *accessReviewCampaignResolver:
count, err := r.accessReview.CountEntriesForCampaignID(ctx, scope, obj.ParentID, obj.Filter)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot count access entries", log.Error(err))
return 0, gqlutils.Internal(ctx)
}
return count, nil
case *accessReviewCampaignSourceResolver:
count, err := r.accessReview.CountEntriesForCampaignIDAndSourceID(ctx, scope, obj.ParentID, *obj.SourceID, obj.Filter)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot count access entries", log.Error(err))
return 0, gqlutils.Internal(ctx)
}
return count, nil
}
r.logger.ErrorCtx(ctx, "unsupported resolver", log.String("resolver", fmt.Sprintf("%T", obj.Resolver)))
return 0, gqlutils.Internal(ctx)
}
// Organization is the resolver for the organization field.
func (r *accessReviewSourceResolver) Organization(ctx context.Context, obj *types.AccessReviewSource) (*types.Organization, error) {
if _, err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil {
return nil, err
}
loaders := dataloader.FromContext(ctx)
organization, err := loaders.Organization.Load(ctx, obj.Organization.ID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot load organization", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewOrganization(organization), nil
}
// Connector is the resolver for the connector field.
func (r *accessReviewSourceResolver) Connector(ctx context.Context, obj *types.AccessReviewSource) (*types.Connector, error) {
if obj.ConnectorID == nil {
return nil, nil
}
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionSourceGet)
if err != nil {
return nil, err
}
connector, err := r.probo.Connectors.Get(ctx, scope, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, nil
}
r.logger.ErrorCtx(ctx, "cannot get connector", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewConnector(connector), nil
}
// ProviderOrganizations is the resolver for the providerOrganizations field.
func (r *accessReviewSourceResolver) ProviderOrganizations(ctx context.Context, obj *types.AccessReviewSource) ([]*types.ProviderOrganization, error) {
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionSourceGet)
if err != nil {
return nil, err
}
if obj.ConnectorID == nil {
return []*types.ProviderOrganization{}, nil
}
orgs, err := r.accessReview.ProviderOrganizations(ctx, scope, *obj.ConnectorID)
if err != nil {
return nil, err
}
result := make([]*types.ProviderOrganization, len(orgs))
for i, o := range orgs {
result[i] = &types.ProviderOrganization{Slug: o.Slug, DisplayName: o.DisplayName}
}
return result, nil
}
// NeedsConfiguration is the resolver for the needsConfiguration field.
//
// True when the provider has a picker UI (NeedsPicker) AND the user has
// not yet picked an org. 2-auto providers (PagerDuty, Vercel) always
// return false: the identifier is captured during the OAuth callback,
// not via a follow-up configure mutation.
func (r *accessReviewSourceResolver) NeedsConfiguration(ctx context.Context, obj *types.AccessReviewSource) (bool, error) {
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionSourceGet)
if err != nil {
return false, err
}
if obj.ConnectorID == nil {
return false, nil
}
needsConfiguration, err := r.accessReview.SourceNeedsConfiguration(ctx, scope, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return false, nil
}
r.logger.ErrorCtx(ctx, "cannot determine access source configuration", log.Error(err))
return false, gqlutils.Internal(ctx)
}
return needsConfiguration, nil
}
// ConnectionStatus is the resolver for the connectionStatus field.
//
// Returns RECONNECT_REQUIRED when the connector's stored OAuth grant is
// missing scopes required by the current provider registration (e.g. a newly
// added Graph permission), DISCONNECTED when the credential probe fails, and
// CONNECTED when the grant is usable as-is.
func (r *accessReviewSourceResolver) ConnectionStatus(ctx context.Context, obj *types.AccessReviewSource) (types.AccessReviewSourceConnectionStatus, error) {
if obj.ConnectorID == nil {
return types.AccessReviewSourceConnectionStatusNotApplicable, nil
}
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionSourceGet)
if err != nil {
return types.AccessReviewSourceConnectionStatusNotApplicable, err
}
httpClient, dbConnector, err := r.accessReview.ConnectorHTTPClient(ctx, scope, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return types.AccessReviewSourceConnectionStatusNotApplicable, nil
}
return types.AccessReviewSourceConnectionStatusDisconnected, nil
}
// Creating an HTTP client may succeed even with an expired or invalid
// credential (e.g. no refresh token available, or a dead API key).
// When the provider registers a probe, make a lightweight request to
// verify the credential is actually accepted.
if err := r.providerRegistry.ProbeConnection(ctx, httpClient, dbConnector); err != nil {
return types.AccessReviewSourceConnectionStatusDisconnected, nil
}
needsReconnect, err := r.accessReview.SourceNeedsReconnect(ctx, scope, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return types.AccessReviewSourceConnectionStatusNotApplicable, nil
}
r.logger.ErrorCtx(ctx, "cannot determine access source reconnect requirement", log.Error(err))
return types.AccessReviewSourceConnectionStatusNotApplicable, gqlutils.Internal(ctx)
}
if needsReconnect {
return types.AccessReviewSourceConnectionStatusReconnectRequired, nil
}
return types.AccessReviewSourceConnectionStatusConnected, nil
}
// SelectedOrganization is the resolver for the selectedOrganization field.
func (r *accessReviewSourceResolver) SelectedOrganization(ctx context.Context, obj *types.AccessReviewSource) (*string, error) {
scope, err := r.authorize(ctx, obj.ID, accessreview.ActionSourceGet)
if err != nil {
return nil, err
}
if obj.ConnectorID == nil {
return nil, nil
}
slug, err := r.accessReview.SelectedOrganizationSlug(ctx, scope, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, nil
}
r.logger.ErrorCtx(ctx, "cannot get selected organization", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
if slug == "" {
return nil, nil
}
return &slug, nil
}
// Permission is the resolver for the permission field.
func (r *accessReviewSourceResolver) Permission(ctx context.Context, obj *types.AccessReviewSource, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field.
func (r *accessReviewSourceConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessReviewSourceConnection) (int, error) {
scope, err := r.authorize(ctx, obj.ParentID, accessreview.ActionSourceList)
if err != nil {
return 0, err
}
switch obj.Resolver.(type) {
case *organizationResolver:
count, err := r.accessReview.CountSourcesForOrganizationID(ctx, scope, obj.ParentID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot count access sources", log.Error(err))
return 0, gqlutils.Internal(ctx)
}
return count, nil
}
r.logger.ErrorCtx(ctx, "unsupported resolver", log.String("resolver", fmt.Sprintf("%T", obj.Resolver)))
return 0, gqlutils.Internal(ctx)
}
// CreateAccessReviewSource is the resolver for the createAccessReviewSource field.
func (r *mutationResolver) CreateAccessReviewSource(ctx context.Context, input types.CreateAccessReviewSourceInput) (*types.CreateAccessReviewSourcePayload, error) {
scope, err := r.authorize(ctx, input.OrganizationID, accessreview.ActionSourceCreate)
if err != nil {
return nil, err
}
source, err := r.accessReview.CreateSource(
ctx,
scope,
accessreview.CreateAccessReviewSourceRequest{
OrganizationID: input.OrganizationID,
ConnectorID: input.ConnectorID,
Name: input.Name,
CsvData: input.CSVData,
},
)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot create access source", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
r.accessReview.AutoSelectDefaultOrganization(ctx, scope, source)
return &types.CreateAccessReviewSourcePayload{
AccessReviewSourceEdge: types.NewAccessReviewSourceEdge(source, coredata.AccessReviewSourceOrderFieldCreatedAt),
}, nil
}
// UpdateAccessReviewSource is the resolver for the updateAccessReviewSource field.
func (r *mutationResolver) UpdateAccessReviewSource(ctx context.Context, input types.UpdateAccessReviewSourceInput) (*types.UpdateAccessReviewSourcePayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewSourceID, accessreview.ActionSourceUpdate)
if err != nil {
return nil, err
}
source, err := r.accessReview.UpdateSource(
ctx,
scope,
accessreview.UpdateAccessReviewSourceRequest{
AccessReviewSourceID: input.AccessReviewSourceID,
Name: gqlutils.UnwrapOmittable(input.Name),
ConnectorID: gqlutils.UnwrapOmittable(input.ConnectorID),
CsvData: gqlutils.UnwrapOmittable(input.CSVData),
},
)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot update access source", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
// A connector was just (re)linked: default its org so the source is usable
// right away. Skipped on name/CSV-only updates to avoid a needless
// provider round-trip.
if input.ConnectorID.IsSet() {
r.accessReview.AutoSelectDefaultOrganization(ctx, scope, source)
}
return &types.UpdateAccessReviewSourcePayload{
AccessReviewSource: types.NewAccessReviewSource(source),
}, nil
}
// DeleteAccessReviewSource is the resolver for the deleteAccessReviewSource field.
func (r *mutationResolver) DeleteAccessReviewSource(ctx context.Context, input types.DeleteAccessReviewSourceInput) (*types.DeleteAccessReviewSourcePayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewSourceID, accessreview.ActionSourceDelete)
if err != nil {
return nil, err
}
if err := r.accessReview.DeleteSource(ctx, scope, input.AccessReviewSourceID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot delete access source", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.DeleteAccessReviewSourcePayload{
DeletedAccessReviewSourceID: input.AccessReviewSourceID,
}, nil
}
// ConfigureAccessReviewSource is the resolver for the configureAccessReviewSource field.
func (r *mutationResolver) ConfigureAccessReviewSource(ctx context.Context, input types.ConfigureAccessReviewSourceInput) (*types.ConfigureAccessReviewSourcePayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewSourceID, accessreview.ActionSourceUpdate)
if err != nil {
return nil, err
}
source, err := r.accessReview.ConfigureAccessReviewSource(
ctx,
scope,
accessreview.ConfigureAccessReviewSourceRequest{
AccessReviewSourceID: input.AccessReviewSourceID,
OrganizationSlug: input.OrganizationSlug,
},
)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot configure access source", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.ConfigureAccessReviewSourcePayload{
AccessReviewSource: types.NewAccessReviewSource(source),
}, nil
}
// CreateAccessReviewCampaign is the resolver for the createAccessReviewCampaign field.
func (r *mutationResolver) CreateAccessReviewCampaign(ctx context.Context, input types.CreateAccessReviewCampaignInput) (*types.CreateAccessReviewCampaignPayload, error) {
scope, err := r.authorize(ctx, input.OrganizationID, accessreview.ActionCampaignCreate)
if err != nil {
return nil, err
}
var description string
if input.Description != nil {
description = *input.Description
}
campaign, err := r.accessReview.CreateCampaign(
ctx,
scope,
accessreview.CreateAccessReviewCampaignRequest{
OrganizationID: input.OrganizationID,
Name: input.Name,
Description: description,
AccessReviewSourceIDs: input.AccessReviewSourceIds,
},
)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot create access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.CreateAccessReviewCampaignPayload{
AccessReviewCampaignEdge: types.NewAccessReviewCampaignEdge(campaign, coredata.AccessReviewCampaignOrderFieldCreatedAt),
}, nil
}
// UpdateAccessReviewCampaign is the resolver for the updateAccessReviewCampaign field.
func (r *mutationResolver) UpdateAccessReviewCampaign(ctx context.Context, input types.UpdateAccessReviewCampaignInput) (*types.UpdateAccessReviewCampaignPayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewCampaignID, accessreview.ActionCampaignUpdate)
if err != nil {
return nil, err
}
campaign, err := r.accessReview.UpdateCampaign(
ctx,
scope,
accessreview.UpdateAccessReviewCampaignRequest{
CampaignID: input.AccessReviewCampaignID,
Name: gqlutils.UnwrapOmittable(input.Name),
Description: gqlutils.UnwrapOmittable(input.Description),
AccessReviewSourceIDs: gqlutils.UnwrapOmittable(input.AccessReviewSourceIds),
},
)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
if errorx.AnyOf(err, accessreview.ErrCampaignNotDraft) {
return nil, gqlutils.Invalid(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot update access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.UpdateAccessReviewCampaignPayload{
AccessReviewCampaign: types.NewAccessReviewCampaign(campaign),
}, nil
}
// DeleteAccessReviewCampaign is the resolver for the deleteAccessReviewCampaign field.
func (r *mutationResolver) DeleteAccessReviewCampaign(ctx context.Context, input types.DeleteAccessReviewCampaignInput) (*types.DeleteAccessReviewCampaignPayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewCampaignID, accessreview.ActionCampaignDelete)
if err != nil {
return nil, err
}
if err := r.accessReview.DeleteCampaign(ctx, scope, input.AccessReviewCampaignID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
if errors.Is(err, accessreview.ErrCampaignNotDeletable) {
return nil, gqlutils.Invalid(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot delete access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.DeleteAccessReviewCampaignPayload{
DeletedAccessReviewCampaignID: input.AccessReviewCampaignID,
}, nil
}
// StartAccessReviewCampaign is the resolver for the startAccessReviewCampaign field.
func (r *mutationResolver) StartAccessReviewCampaign(ctx context.Context, input types.StartAccessReviewCampaignInput) (*types.StartAccessReviewCampaignPayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewCampaignID, accessreview.ActionCampaignStart)
if err != nil {
return nil, err
}
campaign, err := r.accessReview.StartCampaign(ctx, scope, input.AccessReviewCampaignID)
if err != nil {
if errorx.AnyOf(
err,
accessreview.ErrCampaignMissingSources,
accessreview.ErrCampaignNotDraft,
) {
return nil, gqlutils.Invalid(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot start access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.StartAccessReviewCampaignPayload{
AccessReviewCampaign: types.NewAccessReviewCampaign(campaign),
}, nil
}
// CloseAccessReviewCampaign is the resolver for the closeAccessReviewCampaign field.
func (r *mutationResolver) CloseAccessReviewCampaign(ctx context.Context, input types.CloseAccessReviewCampaignInput) (*types.CloseAccessReviewCampaignPayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewCampaignID, accessreview.ActionCampaignClose)
if err != nil {
return nil, err
}
campaign, err := r.accessReview.CloseCampaign(ctx, scope, input.AccessReviewCampaignID)
if err != nil {
if errors.Is(err, accessreview.ErrCampaignNotPendingActions) {
return nil, gqlutils.Invalid(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot close access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.CloseAccessReviewCampaignPayload{
AccessReviewCampaign: types.NewAccessReviewCampaign(campaign),
}, nil
}
// CancelAccessReviewCampaign is the resolver for the cancelAccessReviewCampaign field.
func (r *mutationResolver) CancelAccessReviewCampaign(ctx context.Context, input types.CancelAccessReviewCampaignInput) (*types.CancelAccessReviewCampaignPayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewCampaignID, accessreview.ActionCampaignCancel)
if err != nil {
return nil, err
}
campaign, err := r.accessReview.CancelCampaign(ctx, scope, input.AccessReviewCampaignID)
if err != nil {
if errorx.AnyOf(err, accessreview.ErrCampaignCompleted, accessreview.ErrCampaignCancelled) {
return nil, gqlutils.Invalid(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot cancel access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.CancelAccessReviewCampaignPayload{
AccessReviewCampaign: types.NewAccessReviewCampaign(campaign),
}, nil
}
// AddAccessReviewCampaignSource is the resolver for the addAccessReviewCampaignSource field.
func (r *mutationResolver) AddAccessReviewCampaignSource(ctx context.Context, input types.AddAccessReviewCampaignSourceInput) (*types.AddAccessReviewCampaignSourcePayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewCampaignID, accessreview.ActionCampaignAddSource)
if err != nil {
return nil, err
}
campaign, err := r.accessReview.AddCampaignSource(
ctx,
scope,
accessreview.AddCampaignSourceRequest{
CampaignID: input.AccessReviewCampaignID,
AccessReviewSourceID: input.AccessReviewSourceID,
},
)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
if errors.Is(err, accessreview.ErrCampaignNotDraft) {
return nil, gqlutils.Invalid(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot add scope source to access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.AddAccessReviewCampaignSourcePayload{
AccessReviewCampaign: types.NewAccessReviewCampaign(campaign),
}, nil
}
// RemoveAccessReviewCampaignSource is the resolver for the removeAccessReviewCampaignSource field.
func (r *mutationResolver) RemoveAccessReviewCampaignSource(ctx context.Context, input types.RemoveAccessReviewCampaignSourceInput) (*types.RemoveAccessReviewCampaignSourcePayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewCampaignID, accessreview.ActionCampaignRemoveSource)
if err != nil {
return nil, err
}
campaign, err := r.accessReview.RemoveCampaignSource(
ctx,
scope,
accessreview.RemoveCampaignSourceRequest{
CampaignID: input.AccessReviewCampaignID,
AccessReviewSourceID: input.AccessReviewSourceID,
},
)
if err != nil {
if errors.Is(err, accessreview.ErrCampaignNotDraft) {
return nil, gqlutils.Invalid(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot remove scope source from access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.RemoveAccessReviewCampaignSourcePayload{
AccessReviewCampaign: types.NewAccessReviewCampaign(campaign),
}, nil
}
// RecordAccessReviewEntryDecision is the resolver for the recordAccessReviewEntryDecision field.
func (r *mutationResolver) RecordAccessReviewEntryDecision(ctx context.Context, input types.RecordAccessReviewEntryDecisionInput) (*types.RecordAccessReviewEntryDecisionPayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewEntryID, accessreview.ActionEntryDecide)
if err != nil {
return nil, err
}
entry, err := r.accessReview.RecordDecision(
ctx,
scope,
accessreview.RecordAccessReviewEntryDecisionRequest{
EntryID: input.AccessReviewEntryID,
Decision: input.Decision,
DecisionNote: input.DecisionNote,
DecidedByID: &authn.IdentityFromContext(ctx).ID,
},
)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot record access entry decision", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.RecordAccessReviewEntryDecisionPayload{
AccessReviewEntry: types.NewAccessReviewEntry(entry),
}, nil
}
// RecordAccessReviewEntryDecisions is the resolver for the recordAccessReviewEntryDecisions field.
func (r *mutationResolver) RecordAccessReviewEntryDecisions(ctx context.Context, input types.RecordAccessReviewEntryDecisionsInput) (*types.RecordAccessReviewEntryDecisionsPayload, error) {
if len(input.Decisions) == 0 {
return &types.RecordAccessReviewEntryDecisionsPayload{
AccessReviewEntries: []*types.AccessReviewEntry{},
}, nil
}
const maxBatchSize = 100
if len(input.Decisions) > maxBatchSize {
return nil, fmt.Errorf("cannot record decisions: batch size %d exceeds maximum of %d", len(input.Decisions), maxBatchSize)
}
// Authorize each entry individually to prevent cross-org bypass.
for _, d := range input.Decisions {
_, err := r.authorize(ctx, d.AccessReviewEntryID, accessreview.ActionEntryDecide)
if err != nil {
return nil, err
}
}
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return nil, fmt.Errorf("no identity in context")
}
tenantID := input.Decisions[0].AccessReviewEntryID.TenantID()
scope := coredata.NewScope(tenantID)
decidedByID := &identity.ID
decisions := make([]accessreview.RecordAccessReviewEntryDecisionRequest, len(input.Decisions))
for i, d := range input.Decisions {
decisions[i] = accessreview.RecordAccessReviewEntryDecisionRequest{
EntryID: d.AccessReviewEntryID,
Decision: d.Decision,
DecisionNote: d.DecisionNote,
DecidedByID: decidedByID,
}
}
entries, err := r.accessReview.RecordDecisions(ctx, scope, decisions)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot record access entry decisions", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
accessEntries := make([]*types.AccessReviewEntry, len(entries))
for i, e := range entries {
accessEntries[i] = types.NewAccessReviewEntry(e)
}
return &types.RecordAccessReviewEntryDecisionsPayload{
AccessReviewEntries: accessEntries,
}, nil
}
// FlagAccessReviewEntry is the resolver for the flagAccessReviewEntry field.
func (r *mutationResolver) FlagAccessReviewEntry(ctx context.Context, input types.FlagAccessReviewEntryInput) (*types.FlagAccessReviewEntryPayload, error) {
scope, err := r.authorize(ctx, input.AccessReviewEntryID, accessreview.ActionEntryFlag)
if err != nil {
return nil, err
}
entry, err := r.accessReview.FlagEntry(ctx, scope, accessreview.FlagAccessReviewEntryRequest{
EntryID: input.AccessReviewEntryID,
Flags: input.Flags,
FlagReasons: input.FlagReasons,
})
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot flag access entry", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return &types.FlagAccessReviewEntryPayload{
AccessReviewEntry: types.NewAccessReviewEntry(entry),
}, nil
}
// AccessReviewCampaign returns schema.AccessReviewCampaignResolver implementation.
func (r *Resolver) AccessReviewCampaign() schema.AccessReviewCampaignResolver {
return &accessReviewCampaignResolver{r}
}
// AccessReviewCampaignConnection returns schema.AccessReviewCampaignConnectionResolver implementation.
func (r *Resolver) AccessReviewCampaignConnection() schema.AccessReviewCampaignConnectionResolver {
return &accessReviewCampaignConnectionResolver{r}
}
// AccessReviewCampaignSource returns schema.AccessReviewCampaignSourceResolver implementation.
func (r *Resolver) AccessReviewCampaignSource() schema.AccessReviewCampaignSourceResolver {
return &accessReviewCampaignSourceResolver{r}
}
// AccessReviewCampaignSourceFetchAttemptConnection returns schema.AccessReviewCampaignSourceFetchAttemptConnectionResolver implementation.
func (r *Resolver) AccessReviewCampaignSourceFetchAttemptConnection() schema.AccessReviewCampaignSourceFetchAttemptConnectionResolver {
return &accessReviewCampaignSourceFetchAttemptConnectionResolver{r}
}
// AccessReviewEntry returns schema.AccessReviewEntryResolver implementation.
func (r *Resolver) AccessReviewEntry() schema.AccessReviewEntryResolver {
return &accessReviewEntryResolver{r}
}
// AccessReviewEntryConnection returns schema.AccessReviewEntryConnectionResolver implementation.
func (r *Resolver) AccessReviewEntryConnection() schema.AccessReviewEntryConnectionResolver {
return &accessReviewEntryConnectionResolver{r}
}
// AccessReviewSource returns schema.AccessReviewSourceResolver implementation.
func (r *Resolver) AccessReviewSource() schema.AccessReviewSourceResolver {
return &accessReviewSourceResolver{r}
}
// AccessReviewSourceConnection returns schema.AccessReviewSourceConnectionResolver implementation.
func (r *Resolver) AccessReviewSourceConnection() schema.AccessReviewSourceConnectionResolver {
return &accessReviewSourceConnectionResolver{r}
}
type (
accessReviewCampaignResolver struct{ *Resolver }
accessReviewCampaignConnectionResolver struct{ *Resolver }
accessReviewCampaignSourceResolver struct{ *Resolver }
accessReviewCampaignSourceFetchAttemptConnectionResolver struct{ *Resolver }
accessReviewEntryResolver struct{ *Resolver }
accessReviewEntryConnectionResolver struct{ *Resolver }
accessReviewSourceResolver struct{ *Resolver }
accessReviewSourceConnectionResolver struct{ *Resolver }
)