Treat invalid campaign sources as not found

Drop ErrCampaignSourceOrganizationMismatch. When a source ID is
missing or belongs to another organization, return
coredata.ErrResourceNotFound so clients get a generic not-found
response instead of leaking cross-organization details.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
This commit is contained in:
Cursor Agent
2026-07-20 15:47:09 +00:00
parent 1dcfa31dab
commit eebc957d36
3 changed files with 28 additions and 25 deletions

View File

@@ -22,6 +22,7 @@ package accessreview
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"time" "time"
@@ -61,15 +62,15 @@ func (s *Service) CreateCampaign(
for _, sourceID := range req.AccessReviewSourceIDs { for _, sourceID := range req.AccessReviewSourceIDs {
source := &coredata.AccessReviewSource{} source := &coredata.AccessReviewSource{}
if err := source.LoadByID(ctx, conn, scope, sourceID); err != nil { if err := source.LoadByID(ctx, conn, scope, sourceID); err != nil {
return fmt.Errorf("cannot load access source %s: %w", sourceID, err) if errors.Is(err, coredata.ErrResourceNotFound) {
return coredata.ErrResourceNotFound
}
return fmt.Errorf("cannot load access source: %w", err)
} }
if source.OrganizationID != campaign.OrganizationID { if source.OrganizationID != campaign.OrganizationID {
return fmt.Errorf( return coredata.ErrResourceNotFound
"cannot create campaign: access source %s does not belong to the same organization: %w",
sourceID,
ErrCampaignSourceOrganizationMismatch,
)
} }
if err := s.upsertCampaignSource(ctx, conn, scope, campaign.ID, source); err != nil { if err := s.upsertCampaignSource(ctx, conn, scope, campaign.ID, source); err != nil {
@@ -256,15 +257,15 @@ func (s *Service) AddCampaignSource(
source := &coredata.AccessReviewSource{} source := &coredata.AccessReviewSource{}
if err := source.LoadByID(ctx, conn, scope, req.AccessReviewSourceID); err != nil { if err := source.LoadByID(ctx, conn, scope, req.AccessReviewSourceID); err != nil {
return fmt.Errorf("cannot load access source %s: %w", req.AccessReviewSourceID, err) if errors.Is(err, coredata.ErrResourceNotFound) {
return coredata.ErrResourceNotFound
}
return fmt.Errorf("cannot load access source: %w", err)
} }
if source.OrganizationID != campaign.OrganizationID { if source.OrganizationID != campaign.OrganizationID {
return fmt.Errorf( return coredata.ErrResourceNotFound
"cannot add scope source: access source %q does not belong to the same organization: %w",
req.AccessReviewSourceID,
ErrCampaignSourceOrganizationMismatch,
)
} }
if err := s.upsertCampaignSource(ctx, conn, scope, campaign.ID, source); err != nil { if err := s.upsertCampaignSource(ctx, conn, scope, campaign.ID, source); err != nil {
@@ -349,15 +350,15 @@ func (s *Service) syncCampaignSources(
source := &coredata.AccessReviewSource{} source := &coredata.AccessReviewSource{}
if err := source.LoadByID(ctx, conn, scope, sourceID); err != nil { if err := source.LoadByID(ctx, conn, scope, sourceID); err != nil {
return fmt.Errorf("cannot load access source %s: %w", sourceID, err) if errors.Is(err, coredata.ErrResourceNotFound) {
return coredata.ErrResourceNotFound
}
return fmt.Errorf("cannot load access source: %w", err)
} }
if source.OrganizationID != campaign.OrganizationID { if source.OrganizationID != campaign.OrganizationID {
return fmt.Errorf( return coredata.ErrResourceNotFound
"cannot update campaign: access source %s does not belong to the same organization: %w",
sourceID,
ErrCampaignSourceOrganizationMismatch,
)
} }
if err := s.upsertCampaignSource(ctx, conn, scope, campaign.ID, source); err != nil { if err := s.upsertCampaignSource(ctx, conn, scope, campaign.ID, source); err != nil {

View File

@@ -23,7 +23,6 @@ package accessreview
import "errors" import "errors"
var ( var (
ErrCampaignNoSourcesSelected = errors.New("cannot start campaign: no scope sources configured") ErrCampaignNoSourcesSelected = errors.New("cannot start campaign: no scope sources configured")
ErrCampaignNotDraft = errors.New("campaign must be in draft status") ErrCampaignNotDraft = errors.New("campaign must be in draft status")
ErrCampaignSourceOrganizationMismatch = errors.New("access source does not belong to the same organization")
) )

View File

@@ -724,8 +724,8 @@ func (r *mutationResolver) CreateAccessReviewCampaign(ctx context.Context, input
}, },
) )
if err != nil { if err != nil {
if errors.Is(err, accessreview.ErrCampaignSourceOrganizationMismatch) { if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.Invalid(ctx, err) return nil, gqlutils.NotFound(ctx, err)
} }
r.logger.ErrorCtx(ctx, "cannot create access review campaign", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot create access review campaign", log.Error(err))
@@ -760,8 +760,7 @@ func (r *mutationResolver) UpdateAccessReviewCampaign(ctx context.Context, input
return nil, gqlutils.NotFound(ctx, err) return nil, gqlutils.NotFound(ctx, err)
} }
if errors.Is(err, accessreview.ErrCampaignNotDraft) || if errors.Is(err, accessreview.ErrCampaignNotDraft) {
errors.Is(err, accessreview.ErrCampaignSourceOrganizationMismatch) {
return nil, gqlutils.Invalid(ctx, err) return nil, gqlutils.Invalid(ctx, err)
} }
@@ -875,6 +874,10 @@ func (r *mutationResolver) AddAccessReviewCampaignSource(ctx context.Context, in
}, },
) )
if err != nil { if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
r.logger.ErrorCtx(ctx, "cannot add scope source to access review campaign", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot add scope source to access review campaign", log.Error(err))
return nil, gqlutils.Internal(ctx) return nil, gqlutils.Internal(ctx)