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 } )