Files
probo/pkg/server/api/console/v1/access_review_campaign_resolvers.go
Émile Ré 7fb19a63d9 Rename resolver files from dot to underscore separation
Change gqlgen filename_template from {name}.resolvers.go to
{name}_resolvers.go across all three APIs for consistent Go naming.

Signed-off-by: Émile Ré <emile@getprobo.com>
2026-04-15 09:19:40 +04:00

1000 lines
36 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.87
import (
"context"
"errors"
"fmt"
"go.probo.inc/probo/pkg/accessreview"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"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/schema"
"go.probo.inc/probo/pkg/server/api/console/v1/types"
"go.probo.inc/probo/pkg/server/gqlutils"
)
// Campaign is the resolver for the campaign field.
func (r *accessEntryResolver) Campaign(ctx context.Context, obj *types.AccessEntry) (*types.AccessReviewCampaign, error) {
if err := r.authorize(ctx, obj.Campaign.ID, probo.ActionAccessReviewCampaignGet); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.Campaign.ID)
campaign, err := r.accessReview.Campaigns(scope).Get(ctx, obj.Campaign.ID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot get access review campaign: %w", err))
}
return types.NewAccessReviewCampaign(campaign), nil
}
// AccessSource is the resolver for the accessSource field.
func (r *accessEntryResolver) AccessSource(ctx context.Context, obj *types.AccessEntry) (*types.AccessSource, error) {
if err := r.authorize(ctx, obj.AccessSource.ID, probo.ActionAccessSourceGet); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.AccessSource.ID)
source, err := r.accessReview.Sources(scope).Get(ctx, obj.AccessSource.ID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot get access source: %w", err))
}
return types.NewAccessSource(source), nil
}
// DecisionHistory is the resolver for the decisionHistory field.
func (r *accessEntryResolver) DecisionHistory(ctx context.Context, obj *types.AccessEntry) ([]*types.AccessEntryDecisionHistoryEntry, error) {
if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryGet); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.ID)
histories, err := r.accessReview.Entries(scope).DecisionHistory(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get decision history: %w", err))
}
result := make([]*types.AccessEntryDecisionHistoryEntry, len(histories))
for i, h := range histories {
result[i] = types.NewAccessEntryDecisionHistoryEntry(h)
}
return result, nil
}
// Permission is the resolver for the permission field.
func (r *accessEntryResolver) Permission(ctx context.Context, obj *types.AccessEntry, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field.
func (r *accessEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessEntryConnection) (int, error) {
scope := coredata.NewScopeFromObjectID(obj.ParentID)
switch obj.Resolver.(type) {
case *accessReviewCampaignResolver:
if obj.SourceID != nil {
count, err := r.accessReview.Entries(scope).CountForCampaignIDAndSourceID(ctx, obj.ParentID, *obj.SourceID, obj.Filter)
if err != nil {
panic(fmt.Errorf("cannot count access entries: %w", err))
}
return count, nil
}
count, err := r.accessReview.Entries(scope).CountForCampaignID(ctx, obj.ParentID, obj.Filter)
if err != nil {
panic(fmt.Errorf("cannot count access entries: %w", err))
}
return count, nil
}
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
}
// Organization is the resolver for the organization field.
func (r *accessReviewResolver) Organization(ctx context.Context, obj *types.AccessReview) (*types.Organization, error) {
return obj.Organization, nil
}
// IdentitySource is the resolver for the identitySource field.
func (r *accessReviewResolver) IdentitySource(ctx context.Context, obj *types.AccessReview) (*types.AccessSource, error) {
return obj.IdentitySource, nil
}
// AccessSources is the resolver for the accessSources field.
func (r *accessReviewResolver) AccessSources(ctx context.Context, obj *types.AccessReview, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessSourceOrder) (*types.AccessSourceConnection, error) {
if err := r.authorize(ctx, obj.Organization.ID, probo.ActionAccessSourceList); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.Organization.ID)
pageOrderBy := page.OrderBy[coredata.AccessSourceOrderField]{
Field: coredata.AccessSourceOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.AccessSourceOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
p, err := r.accessReview.Sources(scope).ListForOrganizationID(ctx, obj.Organization.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list access sources: %w", err))
}
return types.NewAccessSourceConnection(p, r, obj.Organization.ID), nil
}
// Campaigns is the resolver for the campaigns field.
func (r *accessReviewResolver) Campaigns(ctx context.Context, obj *types.AccessReview, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessReviewCampaignOrder) (*types.AccessReviewCampaignConnection, error) {
if err := r.authorize(ctx, obj.Organization.ID, probo.ActionAccessReviewCampaignList); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.Organization.ID)
pageOrderBy := page.OrderBy[coredata.AccessReviewCampaignOrderField]{
Field: coredata.AccessReviewCampaignOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.AccessReviewCampaignOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
p, err := r.accessReview.Campaigns(scope).ListForOrganizationID(ctx, obj.Organization.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list access review campaigns: %w", err))
}
return types.NewAccessReviewCampaignConnection(p, r, obj.Organization.ID), nil
}
// Permission is the resolver for the permission field.
func (r *accessReviewResolver) Permission(ctx context.Context, obj *types.AccessReview, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// Organization is the resolver for the organization field.
func (r *accessReviewCampaignResolver) Organization(ctx context.Context, obj *types.AccessReviewCampaign) (*types.Organization, error) {
return obj.Organization, nil
}
// ScopeSources is the resolver for the scopeSources field.
func (r *accessReviewCampaignResolver) ScopeSources(ctx context.Context, obj *types.AccessReviewCampaign) ([]*types.AccessReviewCampaignScopeSource, error) {
if err := r.authorize(ctx, obj.ID, probo.ActionAccessSourceList); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.ID)
sources, err := r.accessReview.Sources(scope).ListScopeSourcesForCampaignID(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot list scope sources: %w", err))
}
fetches, err := r.accessReview.Campaigns(scope).ListSourceFetches(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot list source fetch states: %w", err))
}
fetchBySourceID := make(map[gid.GID]*coredata.AccessReviewCampaignSourceFetch, len(fetches))
for _, fetch := range fetches {
fetchBySourceID[fetch.AccessSourceID] = fetch
}
result := make([]*types.AccessReviewCampaignScopeSource, len(sources))
for i, s := range sources {
result[i] = types.NewAccessReviewCampaignScopeSource(obj.ID, s, fetchBySourceID[s.ID])
}
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.AccessEntryOrder, accessSourceID *gid.GID, filter *coredata.AccessEntryFilter) (*types.AccessEntryConnection, error) {
if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryList); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.ID)
pageOrderBy := page.OrderBy[coredata.AccessEntryOrderField]{
Field: coredata.AccessEntryOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.AccessEntryOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
var (
p *page.Page[*coredata.AccessEntry, coredata.AccessEntryOrderField]
err error
)
if accessSourceID != nil {
p, err = r.accessReview.Entries(scope).ListForCampaignIDAndSourceID(ctx, obj.ID, *accessSourceID, cursor, filter)
} else {
p, err = r.accessReview.Entries(scope).ListForCampaignID(ctx, obj.ID, cursor, filter)
}
if err != nil {
panic(fmt.Errorf("cannot list access entries: %w", err))
}
return types.NewAccessEntryConnection(p, r, obj.ID, accessSourceID, filter), nil
}
// PendingEntryCount is the resolver for the pendingEntryCount field.
func (r *accessReviewCampaignResolver) PendingEntryCount(ctx context.Context, obj *types.AccessReviewCampaign) (int, error) {
if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryList); err != nil {
return 0, err
}
scope := coredata.NewScopeFromObjectID(obj.ID)
count, err := r.accessReview.Entries(scope).CountPendingForCampaignID(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot count pending access entries: %w", err))
}
return count, nil
}
// Statistics is the resolver for the statistics field.
func (r *accessReviewCampaignResolver) Statistics(ctx context.Context, obj *types.AccessReviewCampaign) (*types.AccessReviewCampaignStatistics, error) {
if err := r.authorize(ctx, obj.ID, probo.ActionAccessEntryList); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.ID)
stats, err := r.accessReview.Entries(scope).Statistics(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get campaign statistics: %w", err))
}
return types.NewAccessReviewCampaignStatistics(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 := coredata.NewScopeFromObjectID(obj.ParentID)
switch obj.Resolver.(type) {
case *organizationResolver:
count, err := r.accessReview.Campaigns(scope).CountForOrganizationID(ctx, obj.ParentID)
if err != nil {
panic(fmt.Errorf("cannot count access review campaigns: %w", err))
}
return count, nil
}
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
}
// Entries is the resolver for the entries field.
func (r *accessReviewCampaignScopeSourceResolver) Entries(ctx context.Context, obj *types.AccessReviewCampaignScopeSource, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AccessEntryOrder, filter *coredata.AccessEntryFilter) (*types.AccessEntryConnection, error) {
if err := r.authorize(ctx, obj.CampaignID, probo.ActionAccessEntryList); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.CampaignID)
pageOrderBy := page.OrderBy[coredata.AccessEntryOrderField]{
Field: coredata.AccessEntryOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.AccessEntryOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
p, err := r.accessReview.Entries(scope).ListForCampaignIDAndSourceID(ctx, obj.CampaignID, obj.ID, cursor, filter)
if err != nil {
panic(fmt.Errorf("cannot list access entries: %w", err))
}
sourceID := obj.ID
return types.NewAccessEntryConnection(p, r, obj.CampaignID, &sourceID, filter), nil
}
// Statistics is the resolver for the statistics field.
func (r *accessReviewCampaignScopeSourceResolver) Statistics(ctx context.Context, obj *types.AccessReviewCampaignScopeSource) (*types.AccessReviewCampaignStatistics, error) {
if err := r.authorize(ctx, obj.CampaignID, probo.ActionAccessEntryList); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(obj.CampaignID)
stats, err := r.accessReview.Entries(scope).StatisticsForSource(ctx, obj.CampaignID, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get source statistics: %w", err))
}
return types.NewAccessReviewCampaignStatistics(stats), nil
}
// Organization is the resolver for the organization field.
func (r *accessSourceResolver) Organization(ctx context.Context, obj *types.AccessSource) (*types.Organization, error) {
return obj.Organization, nil
}
// Connector is the resolver for the connector field.
func (r *accessSourceResolver) Connector(ctx context.Context, obj *types.AccessSource) (*types.Connector, error) {
if obj.ConnectorID == nil {
return nil, nil
}
prb := r.ProboService(ctx, obj.ID.TenantID())
connector, err := prb.Connectors.Get(ctx, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, nil
}
panic(fmt.Errorf("cannot get connector: %w", err))
}
return types.NewConnector(connector), nil
}
// ProviderOrganizations is the resolver for the providerOrganizations field.
func (r *accessSourceResolver) ProviderOrganizations(ctx context.Context, obj *types.AccessSource) ([]*types.ProviderOrganization, error) {
if err := r.authorize(ctx, obj.ID, probo.ActionAccessSourceGet); err != nil {
return nil, err
}
if obj.ConnectorID == nil {
return []*types.ProviderOrganization{}, nil
}
scope := coredata.NewScopeFromObjectID(obj.ID)
httpClient, dbConnector, err := r.accessReview.Sources(scope).ConnectorHTTPClient(ctx, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return []*types.ProviderOrganization{}, nil
}
return nil, fmt.Errorf("cannot get connector HTTP client: %w", err)
}
switch dbConnector.Provider {
case coredata.ConnectorProviderGitHub:
orgs, err := fetchGitHubOrganizations(ctx, httpClient)
if err != nil {
return nil, fmt.Errorf("cannot fetch github organizations: %w", err)
}
return orgs, nil
case coredata.ConnectorProviderSentry:
orgs, err := fetchSentryOrganizations(ctx, httpClient)
if err != nil {
return nil, fmt.Errorf("cannot fetch sentry organizations: %w", err)
}
return orgs, nil
default:
return []*types.ProviderOrganization{}, nil
}
}
// NeedsConfiguration is the resolver for the needsConfiguration field.
func (r *accessSourceResolver) NeedsConfiguration(ctx context.Context, obj *types.AccessSource) (bool, error) {
if obj.ConnectorID == nil {
return false, nil
}
prb := r.ProboService(ctx, obj.ID.TenantID())
dbConnector, err := prb.Connectors.Get(ctx, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return false, nil
}
panic(fmt.Errorf("cannot get connector: %w", err))
}
switch dbConnector.Provider {
case coredata.ConnectorProviderGitHub:
settings, _ := dbConnector.GitHubSettings()
return settings.Organization == "", nil
case coredata.ConnectorProviderSentry:
settings, _ := dbConnector.SentrySettings()
return settings.OrganizationSlug == "", nil
default:
return false, nil
}
}
// ConnectionStatus is the resolver for the connectionStatus field.
func (r *accessSourceResolver) ConnectionStatus(ctx context.Context, obj *types.AccessSource) (types.AccessSourceConnectionStatus, error) {
if obj.ConnectorID == nil {
return types.AccessSourceConnectionStatusNotApplicable, nil
}
scope := coredata.NewScopeFromObjectID(obj.ID)
httpClient, dbConnector, err := r.accessReview.Sources(scope).ConnectorHTTPClient(ctx, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return types.AccessSourceConnectionStatusNotApplicable, nil
}
return types.AccessSourceConnectionStatusDisconnected, nil
}
if dbConnector.Protocol != coredata.ConnectorProtocolOAuth2 {
return types.AccessSourceConnectionStatusConnected, nil
}
// Creating an HTTP client may succeed even with an expired token
// (e.g. no refresh token available). Make a lightweight probe
// request to verify the token is actually valid.
probeURL := r.connectorRegistry.GetProbeURL(string(dbConnector.Provider))
if err := probeConnection(ctx, httpClient, probeURL); err != nil {
return types.AccessSourceConnectionStatusDisconnected, nil
}
return types.AccessSourceConnectionStatusConnected, nil
}
// SelectedOrganization is the resolver for the selectedOrganization field.
func (r *accessSourceResolver) SelectedOrganization(ctx context.Context, obj *types.AccessSource) (*string, error) {
if obj.ConnectorID == nil {
return nil, nil
}
prb := r.ProboService(ctx, obj.ID.TenantID())
dbConnector, err := prb.Connectors.Get(ctx, *obj.ConnectorID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, nil
}
panic(fmt.Errorf("cannot get connector: %w", err))
}
switch dbConnector.Provider {
case coredata.ConnectorProviderGitHub:
settings, _ := dbConnector.GitHubSettings()
if settings.Organization != "" {
return &settings.Organization, nil
}
case coredata.ConnectorProviderSentry:
settings, _ := dbConnector.SentrySettings()
if settings.OrganizationSlug != "" {
return &settings.OrganizationSlug, nil
}
}
return nil, nil
}
// Permission is the resolver for the permission field.
func (r *accessSourceResolver) Permission(ctx context.Context, obj *types.AccessSource, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field.
func (r *accessSourceConnectionResolver) TotalCount(ctx context.Context, obj *types.AccessSourceConnection) (int, error) {
scope := coredata.NewScopeFromObjectID(obj.ParentID)
switch obj.Resolver.(type) {
case *organizationResolver:
count, err := r.accessReview.Sources(scope).CountForOrganizationID(ctx, obj.ParentID)
if err != nil {
panic(fmt.Errorf("cannot count access sources: %w", err))
}
return count, nil
}
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
}
// CreateAccessSource is the resolver for the createAccessSource field.
func (r *mutationResolver) CreateAccessSource(ctx context.Context, input types.CreateAccessSourceInput) (*types.CreateAccessSourcePayload, error) {
if err := r.authorize(ctx, input.OrganizationID, probo.ActionAccessSourceCreate); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.OrganizationID)
source, err := r.accessReview.Sources(scope).Create(ctx, accessreview.CreateAccessSourceRequest{
OrganizationID: input.OrganizationID,
ConnectorID: input.ConnectorID,
Name: input.Name,
Category: coredata.AccessSourceCategorySaaS,
CsvData: input.CSVData,
})
if err != nil {
panic(fmt.Errorf("cannot create access source: %w", err))
}
return &types.CreateAccessSourcePayload{
AccessSourceEdge: types.NewAccessSourceEdge(source, coredata.AccessSourceOrderFieldCreatedAt),
}, nil
}
// UpdateAccessSource is the resolver for the updateAccessSource field.
func (r *mutationResolver) UpdateAccessSource(ctx context.Context, input types.UpdateAccessSourceInput) (*types.UpdateAccessSourcePayload, error) {
if err := r.authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceUpdate); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessSourceID)
req := accessreview.UpdateAccessSourceRequest{
AccessSourceID: input.AccessSourceID,
}
if input.Name.IsSet() {
req.Name = input.Name.Value()
}
if input.ConnectorID.IsSet() {
req.ConnectorID = gqlutils.UnwrapOmittable(input.ConnectorID)
}
if input.CSVData.IsSet() {
req.CsvData = gqlutils.UnwrapOmittable(input.CSVData)
}
source, err := r.accessReview.Sources(scope).Update(ctx, req)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot update access source: %w", err))
}
return &types.UpdateAccessSourcePayload{
AccessSource: types.NewAccessSource(source),
}, nil
}
// DeleteAccessSource is the resolver for the deleteAccessSource field.
func (r *mutationResolver) DeleteAccessSource(ctx context.Context, input types.DeleteAccessSourceInput) (*types.DeleteAccessSourcePayload, error) {
if err := r.authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceDelete); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessSourceID)
if err := r.accessReview.Sources(scope).Delete(ctx, input.AccessSourceID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot delete access source: %w", err))
}
return &types.DeleteAccessSourcePayload{
DeletedAccessSourceID: input.AccessSourceID,
}, nil
}
// ConfigureAccessSource is the resolver for the configureAccessSource field.
func (r *mutationResolver) ConfigureAccessSource(ctx context.Context, input types.ConfigureAccessSourceInput) (*types.ConfigureAccessSourcePayload, error) {
if err := r.authorize(ctx, input.AccessSourceID, probo.ActionAccessSourceUpdate); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessSourceID)
source, err := r.accessReview.Sources(scope).ConfigureAccessSource(
ctx,
accessreview.ConfigureAccessSourceRequest{
AccessSourceID: input.AccessSourceID,
OrganizationSlug: input.OrganizationSlug,
},
)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot configure access source: %w", err))
}
return &types.ConfigureAccessSourcePayload{
AccessSource: types.NewAccessSource(source),
}, nil
}
// CreateAccessReviewCampaign is the resolver for the createAccessReviewCampaign field.
func (r *mutationResolver) CreateAccessReviewCampaign(ctx context.Context, input types.CreateAccessReviewCampaignInput) (*types.CreateAccessReviewCampaignPayload, error) {
if err := r.authorize(ctx, input.OrganizationID, probo.ActionAccessReviewCampaignCreate); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.OrganizationID)
var description string
if input.Description != nil {
description = *input.Description
}
campaign, err := r.accessReview.Campaigns(scope).Create(ctx, accessreview.CreateAccessReviewCampaignRequest{
OrganizationID: input.OrganizationID,
Name: input.Name,
Description: description,
FrameworkControls: input.FrameworkControls,
AccessSourceIDs: input.AccessSourceIds,
})
if err != nil {
panic(fmt.Errorf("cannot create access review campaign: %w", err))
}
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) {
if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignUpdate); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID)
req := accessreview.UpdateAccessReviewCampaignRequest{
CampaignID: input.AccessReviewCampaignID,
}
if input.Name.IsSet() {
req.Name = input.Name.Value()
}
if input.Description.IsSet() {
req.Description = input.Description.Value()
}
if input.FrameworkControls.IsSet() {
controls := input.FrameworkControls.Value()
req.FrameworkControls = &controls
}
campaign, err := r.accessReview.Campaigns(scope).Update(ctx, req)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot update access review campaign: %w", err))
}
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) {
if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignDelete); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID)
if err := r.accessReview.Campaigns(scope).Delete(ctx, input.AccessReviewCampaignID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot delete access review campaign: %w", err))
}
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) {
if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignStart); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID)
campaign, err := r.accessReview.Campaigns(scope).Start(ctx, input.AccessReviewCampaignID)
if err != nil {
panic(fmt.Errorf("cannot start access review campaign: %w", err))
}
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) {
if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignClose); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID)
campaign, err := r.accessReview.Campaigns(scope).Close(ctx, input.AccessReviewCampaignID)
if err != nil {
panic(fmt.Errorf("cannot close access review campaign: %w", err))
}
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) {
if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignCancel); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID)
campaign, err := r.accessReview.Campaigns(scope).Cancel(ctx, input.AccessReviewCampaignID)
if err != nil {
panic(fmt.Errorf("cannot cancel access review campaign: %w", err))
}
return &types.CancelAccessReviewCampaignPayload{
AccessReviewCampaign: types.NewAccessReviewCampaign(campaign),
}, nil
}
// AddAccessReviewCampaignScopeSource is the resolver for the addAccessReviewCampaignScopeSource field.
func (r *mutationResolver) AddAccessReviewCampaignScopeSource(ctx context.Context, input types.AddAccessReviewCampaignScopeSourceInput) (*types.AddAccessReviewCampaignScopeSourcePayload, error) {
if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignAddScopeSource); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID)
campaign, err := r.accessReview.Campaigns(scope).AddScopeSource(ctx, accessreview.AddCampaignScopeSourceRequest{
CampaignID: input.AccessReviewCampaignID,
AccessSourceID: input.AccessSourceID,
})
if err != nil {
panic(fmt.Errorf("cannot add scope source to access review campaign: %w", err))
}
return &types.AddAccessReviewCampaignScopeSourcePayload{
AccessReviewCampaign: types.NewAccessReviewCampaign(campaign),
}, nil
}
// RemoveAccessReviewCampaignScopeSource is the resolver for the removeAccessReviewCampaignScopeSource field.
func (r *mutationResolver) RemoveAccessReviewCampaignScopeSource(ctx context.Context, input types.RemoveAccessReviewCampaignScopeSourceInput) (*types.RemoveAccessReviewCampaignScopeSourcePayload, error) {
if err := r.authorize(ctx, input.AccessReviewCampaignID, probo.ActionAccessReviewCampaignRemoveScopeSource); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessReviewCampaignID)
campaign, err := r.accessReview.Campaigns(scope).RemoveScopeSource(ctx, accessreview.RemoveCampaignScopeSourceRequest{
CampaignID: input.AccessReviewCampaignID,
AccessSourceID: input.AccessSourceID,
})
if err != nil {
panic(fmt.Errorf("cannot remove scope source from access review campaign: %w", err))
}
return &types.RemoveAccessReviewCampaignScopeSourcePayload{
AccessReviewCampaign: types.NewAccessReviewCampaign(campaign),
}, nil
}
// RecordAccessEntryDecision is the resolver for the recordAccessEntryDecision field.
func (r *mutationResolver) RecordAccessEntryDecision(ctx context.Context, input types.RecordAccessEntryDecisionInput) (*types.RecordAccessEntryDecisionPayload, error) {
if err := r.authorize(ctx, input.AccessEntryID, probo.ActionAccessEntryDecide); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessEntryID)
// Resolve the profile ID from the session's identity.
// The profile may not exist for every identity, in which
// case decided_by will be left nil.
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return nil, fmt.Errorf("no identity in context")
}
req := accessreview.RecordAccessEntryDecisionRequest{
EntryID: input.AccessEntryID,
Decision: input.Decision,
DecisionNote: input.DecisionNote,
}
organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, input.AccessEntryID)
if err == nil {
profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, organizationID)
if err == nil {
req.DecidedByID = &profile.ID
}
}
entry, err := r.accessReview.Entries(scope).RecordDecision(ctx, req)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot record access entry decision: %w", err))
}
return &types.RecordAccessEntryDecisionPayload{
AccessEntry: types.NewAccessEntry(entry),
}, nil
}
// RecordAccessEntryDecisions is the resolver for the recordAccessEntryDecisions field.
func (r *mutationResolver) RecordAccessEntryDecisions(ctx context.Context, input types.RecordAccessEntryDecisionsInput) (*types.RecordAccessEntryDecisionsPayload, error) {
if len(input.Decisions) == 0 {
return &types.RecordAccessEntryDecisionsPayload{
AccessEntries: []*types.AccessEntry{},
}, 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 {
if err := r.authorize(ctx, d.AccessEntryID, probo.ActionAccessEntryDecide); err != nil {
return nil, err
}
}
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return nil, fmt.Errorf("no identity in context")
}
tenantID := input.Decisions[0].AccessEntryID.TenantID()
scope := coredata.NewScope(tenantID)
// Cache profile lookups per organization so we resolve the correct
// decidedByID for each entry even when a batch spans multiple orgs.
profileCache := make(map[gid.GID]*gid.GID)
decisions := make([]accessreview.RecordAccessEntryDecisionRequest, len(input.Decisions))
for i, d := range input.Decisions {
var decidedByID *gid.GID
organizationID, err := r.accessReview.ResolveEntryOrganizationID(ctx, d.AccessEntryID)
if err == nil {
if cached, ok := profileCache[organizationID]; ok {
decidedByID = cached
} else {
profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, organizationID)
if err == nil {
decidedByID = &profile.ID
}
profileCache[organizationID] = decidedByID
}
}
decisions[i] = accessreview.RecordAccessEntryDecisionRequest{
EntryID: d.AccessEntryID,
Decision: d.Decision,
DecisionNote: d.DecisionNote,
DecidedByID: decidedByID,
}
}
entries, err := r.accessReview.Entries(scope).RecordDecisions(ctx, decisions)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot record access entry decisions: %w", err))
}
accessEntries := make([]*types.AccessEntry, len(entries))
for i, e := range entries {
accessEntries[i] = types.NewAccessEntry(e)
}
return &types.RecordAccessEntryDecisionsPayload{
AccessEntries: accessEntries,
}, nil
}
// FlagAccessEntry is the resolver for the flagAccessEntry field.
func (r *mutationResolver) FlagAccessEntry(ctx context.Context, input types.FlagAccessEntryInput) (*types.FlagAccessEntryPayload, error) {
if err := r.authorize(ctx, input.AccessEntryID, probo.ActionAccessEntryFlag); err != nil {
return nil, err
}
scope := coredata.NewScopeFromObjectID(input.AccessEntryID)
entry, err := r.accessReview.Entries(scope).FlagEntry(ctx, accessreview.FlagAccessEntryRequest{
EntryID: input.AccessEntryID,
Flags: input.Flags,
FlagReasons: input.FlagReasons,
})
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
panic(fmt.Errorf("cannot flag access entry: %w", err))
}
return &types.FlagAccessEntryPayload{
AccessEntry: types.NewAccessEntry(entry),
}, nil
}
// AccessEntry returns schema.AccessEntryResolver implementation.
func (r *Resolver) AccessEntry() schema.AccessEntryResolver { return &accessEntryResolver{r} }
// AccessEntryConnection returns schema.AccessEntryConnectionResolver implementation.
func (r *Resolver) AccessEntryConnection() schema.AccessEntryConnectionResolver {
return &accessEntryConnectionResolver{r}
}
// AccessReview returns schema.AccessReviewResolver implementation.
func (r *Resolver) AccessReview() schema.AccessReviewResolver { return &accessReviewResolver{r} }
// 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}
}
// AccessReviewCampaignScopeSource returns schema.AccessReviewCampaignScopeSourceResolver implementation.
func (r *Resolver) AccessReviewCampaignScopeSource() schema.AccessReviewCampaignScopeSourceResolver {
return &accessReviewCampaignScopeSourceResolver{r}
}
// AccessSource returns schema.AccessSourceResolver implementation.
func (r *Resolver) AccessSource() schema.AccessSourceResolver { return &accessSourceResolver{r} }
// AccessSourceConnection returns schema.AccessSourceConnectionResolver implementation.
func (r *Resolver) AccessSourceConnection() schema.AccessSourceConnectionResolver {
return &accessSourceConnectionResolver{r}
}
type accessEntryResolver struct{ *Resolver }
type accessEntryConnectionResolver struct{ *Resolver }
type accessReviewResolver struct{ *Resolver }
type accessReviewCampaignResolver struct{ *Resolver }
type accessReviewCampaignConnectionResolver struct{ *Resolver }
type accessReviewCampaignScopeSourceResolver struct{ *Resolver }
type accessSourceResolver struct{ *Resolver }
type accessSourceConnectionResolver struct{ *Resolver }