Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/cli/internal/action/referrer_discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type ReferrerItem struct {
Digest string `json:"digest"`
Kind string `json:"kind"`
Downloadable bool `json:"downloadable"`
Public bool `json:"public"`
CreatedAt *time.Time `json:"createdAt"`
References []*ReferrerItem `json:"references"`
}
Expand Down Expand Up @@ -58,6 +59,7 @@ func pbReferrerItemToAction(in *pb.ReferrerItem) *ReferrerItem {
out := &ReferrerItem{
Digest: in.GetDigest(),
Downloadable: in.GetDownloadable(),
Public: in.GetPublic(),
Kind: in.GetKind(),
CreatedAt: toTimePtr(in.GetCreatedAt().AsTime()),
References: make([]*ReferrerItem, 0, len(in.GetReferences())),
Expand Down
65 changes: 38 additions & 27 deletions app/controlplane/api/controlplane/v1/referrer.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/controlplane/api/controlplane/v1/referrer.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/controlplane/api/controlplane/v1/referrer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ message ReferrerItem {
string kind = 2;
// Whether the referrer is downloadable or not from CAS
bool downloadable = 3;
// Whether the referrer is public since it belongs to a public workflow
bool public = 6;
repeated ReferrerItem references = 4;
google.protobuf.Timestamp created_at = 5;
}
17 changes: 16 additions & 1 deletion app/controlplane/api/gen/frontend/controlplane/v1/referrer.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions app/controlplane/cmd/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 49 additions & 16 deletions app/controlplane/internal/biz/referrer.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ type Referrer struct {
Kind string
// Wether the item is downloadable from CAS or not
Downloadable bool
References []*Referrer
// If this referrer is part of a public workflow
InPublicWorkflow bool
References []*Referrer
}

// Actual referrer stored in the DB which includes a nested list of storedReferences
Expand All @@ -48,53 +50,75 @@ type StoredReferrer struct {
ID uuid.UUID
CreatedAt *time.Time
// Fully expanded list of 1-level off references
References []*StoredReferrer
OrgIDs []uuid.UUID
References []*StoredReferrer
OrgIDs, WorkflowIDs []uuid.UUID
}

type ReferrerRepo interface {
Save(ctx context.Context, input []*Referrer, orgID uuid.UUID) error
Save(ctx context.Context, input []*Referrer, workflowID uuid.UUID) error
// GetFromRoot returns the referrer identified by the provided content digest, including its first-level references
// For example if sha:deadbeef represents an attestation, the result will contain the attestation + materials associated to it
// OrgIDs represent an allowList of organizations where the referrers should be looked for
GetFromRoot(ctx context.Context, digest, kind string, orgIDS []uuid.UUID) (*StoredReferrer, error)
GetFromRoot(ctx context.Context, digest string, orgIDS []uuid.UUID, filters ...GetFromRootFilter) (*StoredReferrer, error)
}

type GetFromRootFilters struct {
// RootKind is the kind of the root referrer, i.e ATTESTATION
RootKind *string
// Wether to filter by visibility or not
Public *bool
}

type GetFromRootFilter func(*GetFromRootFilters)

func WithKind(kind string) func(*GetFromRootFilters) {
return func(o *GetFromRootFilters) {
o.RootKind = &kind
}
}

func WithPublicVisibility(public bool) func(*GetFromRootFilters) {
return func(o *GetFromRootFilters) {
o.Public = &public
}
}

type ReferrerUseCase struct {
repo ReferrerRepo
orgRepo OrganizationRepo
membershipRepo MembershipRepo
workflowRepo WorkflowRepo
logger *log.Helper
}

func NewReferrerUseCase(repo ReferrerRepo, orgRepo OrganizationRepo, mRepo MembershipRepo, l log.Logger) *ReferrerUseCase {
func NewReferrerUseCase(repo ReferrerRepo, wfRepo WorkflowRepo, mRepo MembershipRepo, l log.Logger) *ReferrerUseCase {
if l == nil {
l = log.NewStdLogger(io.Discard)
}

return &ReferrerUseCase{repo, orgRepo, mRepo, servicelogger.ScopedHelper(l, "biz/Referrer")}
return &ReferrerUseCase{repo, mRepo, wfRepo, servicelogger.ScopedHelper(l, "biz/Referrer")}
}

// ExtractAndPersist extracts the referrers (subject + materials) from the given attestation
// and store it as part of the referrers index table
func (s *ReferrerUseCase) ExtractAndPersist(ctx context.Context, att *dsse.Envelope, orgID string) error {
orgUUID, err := uuid.Parse(orgID)
func (s *ReferrerUseCase) ExtractAndPersist(ctx context.Context, att *dsse.Envelope, workflowID string) error {
workflowUUID, err := uuid.Parse(workflowID)
if err != nil {
return NewErrInvalidUUID(err)
}

if org, err := s.orgRepo.FindByID(ctx, orgUUID); err != nil {
return fmt.Errorf("finding organization: %w", err)
} else if org == nil {
return NewErrNotFound("organization")
// Check that the workflow belongs to the organization
if wf, err := s.workflowRepo.FindByID(ctx, workflowUUID); err != nil {
return fmt.Errorf("finding workflow: %w", err)
} else if wf == nil {
return NewErrNotFound("workflow")
}

m, err := extractReferrers(att)
if err != nil {
return fmt.Errorf("extracting referrers: %w", err)
}

if err := s.repo.Save(ctx, m, orgUUID); err != nil {
if err := s.repo.Save(ctx, m, workflowUUID); err != nil {
return fmt.Errorf("saving referrers: %w", err)
}

Expand All @@ -110,6 +134,10 @@ func (s *ReferrerUseCase) GetFromRoot(ctx context.Context, digest string, rootKi
return nil, NewErrInvalidUUID(err)
}

if _, err = cr_v1.NewHash(digest); err != nil {
return nil, NewErrValidation(fmt.Errorf("invalid digest format: %w", err))
}

// We pass the list of organizationsIDs from where to look for the referrer
// For now we just pass the list of organizations the user is member of
// in the future we will expand this to publicly available orgs and so on.
Expand All @@ -123,7 +151,12 @@ func (s *ReferrerUseCase) GetFromRoot(ctx context.Context, digest string, rootKi
orgIDs = append(orgIDs, m.OrganizationID)
}

ref, err := s.repo.GetFromRoot(ctx, digest, rootKind, orgIDs)
filters := make([]GetFromRootFilter, 0)
if rootKind != "" {
filters = append(filters, WithKind(rootKind))
}

ref, err := s.repo.GetFromRoot(ctx, digest, orgIDs, filters...)
if err != nil {
if errors.As(err, &ErrAmbiguousReferrer{}) {
return nil, NewErrValidation(fmt.Errorf("please provide the referrer kind: %w", err))
Expand Down
Loading