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
5 changes: 4 additions & 1 deletion app/cli/cmd/referrer_discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ func newReferrerDiscoverCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "discover",
Short: "(Preview) inspect pieces of evidence or artifacts stored through Chainloop",
RunE: func(cmd *cobra.Command, args []string) error {
Long: `(Preview) inspect pieces of evidence or artifacts stored through Chainloop.

The discovery covers only the current organization. Select another organization with --org.`,
RunE: func(_ *cobra.Command, _ []string) error {
pagination := &action.PaginationOpts{
Limit: paginationOpts.Limit,
NextCursor: paginationOpts.NextCursor,
Expand Down
6 changes: 6 additions & 0 deletions app/cli/documentation/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,12 @@ Options inherited from parent commands

(Preview) inspect pieces of evidence or artifacts stored through Chainloop

Synopsis

(Preview) inspect pieces of evidence or artifacts stored through Chainloop.

The discovery covers only the current organization. Select another organization with --org.

```
chainloop discover [flags]
```
Expand Down
6 changes: 3 additions & 3 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.

4 changes: 2 additions & 2 deletions app/controlplane/api/controlplane/v1/referrer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ import "protoc-gen-openapiv2/options/annotations.proto";
option go_package = "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1;v1";

service ReferrerService {
// DiscoverPrivate returns the referrer item for a given digest in the organizations of the logged-in user
// DiscoverPrivate returns the referrer item for a given digest in the current organization
rpc DiscoverPrivate(ReferrerServiceDiscoverPrivateRequest) returns (ReferrerServiceDiscoverPrivateResponse) {
option (google.api.http) = {get: "/discover/{digest}"};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Discover private referrer"
description: "Returns the referrer item for a given digest in the organizations of the logged-in user"
description: "Returns the referrer item for a given digest in the current organization"
produces: ["application/json"]
};
}
Expand Down
4 changes: 2 additions & 2 deletions app/controlplane/api/controlplane/v1/referrer_grpc.pb.go

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

2 changes: 1 addition & 1 deletion app/controlplane/api/controlplane/v1/referrer_http.pb.go

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

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

4 changes: 1 addition & 3 deletions app/controlplane/api/gen/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ externalDocs:
paths:
/discover/{digest}:
get:
description: >-
Returns the referrer item for a given digest in the organizations of the
logged-in user
description: Returns the referrer item for a given digest in the current organization
operationId: ReferrerService_DiscoverPrivate
parameters:
- description: Digest is the unique identifier of the referrer to discover
Expand Down
31 changes: 12 additions & 19 deletions app/controlplane/internal/service/referrer.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func NewReferrerService(uc *biz.ReferrerUseCase, opts ...NewOpt) *ReferrerServic
}

func (s *ReferrerService) DiscoverPrivate(ctx context.Context, req *pb.ReferrerServiceDiscoverPrivateRequest) (*pb.ReferrerServiceDiscoverPrivateResponse, error) {
currentUser, currentToken, err := requireCurrentUserOrAPIToken(ctx)
currentUser, _, err := requireCurrentUserOrAPIToken(ctx)
if err != nil {
return nil, err
}
Expand All @@ -64,27 +64,20 @@ func (s *ReferrerService) DiscoverPrivate(ctx context.Context, req *pb.ReferrerS
extraFilters = append(extraFilters, biz.WithProjectScope(req.GetProjectName(), req.GetProjectVersion()))
}

// if we are logged in as user we find the referrer from the user
// otherwise for the current organization associated with the API token
orgUUID, err := uuid.Parse(currentOrg.ID)
if err != nil {
return nil, fmt.Errorf("invalid org UUID: %w", err)
}

// Discovery is always limited to the current organization, with the project visibility of
// the caller in it (see GetFromRootUser for users)
var referrer *biz.StoredReferrer
var nextCursor string
if currentUser != nil {
referrer, nextCursor, err = s.referrerUC.GetFromRootUser(ctx, req.GetDigest(), req.GetKind(), currentUser.ID, paginationOpts, extraFilters...)
} else if currentToken != nil {
var orgUUID uuid.UUID
orgUUID, err = uuid.Parse(currentOrg.ID)
if err != nil {
return nil, fmt.Errorf("invalid org UUID: %w", err)
}

// Apply RBAC visibility for api tokens (only for the current organization)
orgsProjectsMap := make(map[uuid.UUID][]uuid.UUID)
visibleProjects := s.visibleProjects(ctx)
if visibleProjects != nil {
orgsProjectsMap[orgUUID] = visibleProjects
}

referrer, nextCursor, err = s.referrerUC.GetFromRoot(ctx, req.GetDigest(), req.GetKind(), []uuid.UUID{orgUUID}, orgsProjectsMap, paginationOpts, extraFilters...)
referrer, nextCursor, err = s.referrerUC.GetFromRootUser(ctx, req.GetDigest(), req.GetKind(), currentUser.ID, orgUUID, paginationOpts, extraFilters...)
Comment thread
migmartri marked this conversation as resolved.
Resolved
} else {
referrer, nextCursor, err = s.referrerUC.GetFromRoot(ctx, req.GetDigest(), req.GetKind(), []uuid.UUID{orgUUID},
s.rbacScopesForOrg(ctx, orgUUID).ProjectIDsByOrg(), paginationOpts, extraFilters...)
}
if err != nil {
return nil, handleUseCaseErr(ctx, err, s.log)
Expand Down
25 changes: 17 additions & 8 deletions app/controlplane/pkg/biz/referrer.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,10 @@ func (s *ReferrerUseCase) ExtractAndPersist(ctx context.Context, att *dsse.Envel
}

// GetFromRootUser 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.
// It only returns referrers that belong to organizations the user is member of.
func (s *ReferrerUseCase) GetFromRootUser(ctx context.Context, digest, rootKind, userID string, p *pagination.CursorOptions, extraFilters ...GetFromRootFilter) (*StoredReferrer, string, error) {
// The discovery is limited to the given organization, with the project visibility that the user has in that
// organization. The caller makes sure that the user can work in the organization, as for any other operation.
// A user without a membership in it (an instance admin) sees the whole organization.
func (s *ReferrerUseCase) GetFromRootUser(ctx context.Context, digest, rootKind, userID string, orgID uuid.UUID, p *pagination.CursorOptions, extraFilters ...GetFromRootFilter) (*StoredReferrer, string, error) {
ctx, span := otelx.Start(ctx, referrerTracer, "ReferrerUseCase.GetFromRootUser")
defer span.End()

Expand All @@ -172,17 +173,25 @@ func (s *ReferrerUseCase) GetFromRootUser(ctx context.Context, digest, rootKind,
return nil, "", NewErrInvalidUUID(err)
}

userOrgs, scopes, err := s.membershipUseCase.GetOrgsAndRBACInfoForUser(ctx, userUUID)
_, scopes, err := s.membershipUseCase.GetOrgsAndRBACInfoForUser(ctx, userUUID)
if err != nil {
return nil, "", err
}

// We pass the list of organizationsIDs from where to look for the referrer:
// the organizations the user is a member of. Referrers are always project-scoped, so only the
// project dimension of the RBAC scopes is relevant here.
return s.GetFromRoot(ctx, digest, rootKind, userOrgs, scopes.ProjectIDsByOrg(), p, extraFilters...)
// Only the project visibility of the user in this organization applies. A referrer lists its
// projects of all organizations, so a visible project in another organization must not count.
// Referrers are always project-scoped, so only the project dimension of the RBAC scopes is relevant.
var projectIDs map[OrgID][]ProjectID
if scope, ok := scopes[orgID]; ok {
projectIDs = RBACScopes{orgID: scope}.ProjectIDsByOrg()
}

return s.GetFromRoot(ctx, digest, rootKind, []uuid.UUID{orgID}, projectIDs, p, extraFilters...)
}

// 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.
// It only returns referrers that belong to the given organizations, with the given project visibility.
func (s *ReferrerUseCase) GetFromRoot(ctx context.Context, digest, rootKind string, orgIDs []uuid.UUID, projectIDs map[OrgID][]ProjectID, p *pagination.CursorOptions, extraFilters ...GetFromRootFilter) (*StoredReferrer, string, error) {
ctx, span := otelx.Start(ctx, referrerTracer, "ReferrerUseCase.GetFromRoot")
defer span.End()
Expand Down
Loading
Loading