Org-defaulting for picker providers only ran in the GraphQL resolver, so a picker-provider source created or updated through the MCP API connected fine but resolved no users until the org was picked. Move the defaulting into the accessreview service as AutoSelectDefaultOrganization and call it from both surfaces, moving the providerOrgConfigs picker dispatch alongside it (the three console picker resolvers now dispatch through service accessors, behavior unchanged). Also harden the moved path: resolve the provider from cheap connector metadata before building the authenticated HTTP client, so the ~50 non-picker providers no longer pay a decrypt/refresh/DB-write on every create/update; bound the outbound ListOrgs call with a 10s timeout so a hung provider cannot stall the mutation; and re-check inside the ConfigureAccessReviewSource tx (OnlyIfUnset) so an org the user picks while ListOrgs is in flight is not overwritten by the first listed org. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
1069 lines
37 KiB
Go
1069 lines
37 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/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.
|
|
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
|
|
}
|
|
|
|
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 {
|
|
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),
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
|
return nil, gqlutils.NotFound(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)
|
|
}
|
|
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 }
|
|
)
|