feat: [CODE-2818]: Add Archive Repo functionality (#3051)

BT-10437
Atefeh Mohseni Ejiyeh 2025-01-13 19:02:51 +00:00 committed by Harness
parent 64d66772d4
commit f78f767ae2
36 changed files with 429 additions and 211 deletions

View File

@ -17,14 +17,14 @@ package auth
import (
"context"
"fmt"
"slices"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/pkg/errors"
)
// CheckRepo checks if a repo specific permission is granted for the current auth session
@ -66,3 +66,55 @@ func IsRepoOwner(
return err == nil, nil
}
// CheckRepoState checks if requested permission is allowed given the state of the repository.
func CheckRepoState(
_ context.Context,
_ *auth.Session,
repo *types.Repository,
reqPermission enum.Permission,
additionalAllowedRepoStates ...enum.RepoState,
) error {
permissionsAllowedPerRepoState := map[enum.RepoState][]enum.Permission{
enum.RepoStateActive: {
enum.PermissionRepoView,
enum.PermissionRepoCreate,
enum.PermissionRepoEdit,
enum.PermissionRepoPush,
enum.PermissionRepoReview,
enum.PermissionRepoDelete,
enum.PermissionRepoReportCommitCheck,
enum.PermissionPipelineView,
enum.PermissionPipelineExecute,
enum.PermissionPipelineEdit,
enum.PermissionPipelineDelete,
enum.PermissionServiceAccountView,
},
enum.RepoStateArchived: {
enum.PermissionRepoView,
enum.PermissionPipelineView,
enum.PermissionServiceAccountView,
},
// allowed permissions for repos on transition states during import/migration are handled by their controller.
enum.RepoStateGitImport: {},
enum.RepoStateMigrateDataImport: {},
enum.RepoStateMigrateGitPush: {},
}
if len(additionalAllowedRepoStates) > 0 && slices.Contains(additionalAllowedRepoStates, repo.State) {
return nil
}
defaultAllowedPermissions := permissionsAllowedPerRepoState[repo.State]
if !slices.Contains(defaultAllowedPermissions, reqPermission) {
return errors.PreconditionFailed("Operation is not allowed for repository in state %s", repo.State)
}
return nil
}

View File

@ -68,8 +68,13 @@ func NewController(
}
}
func (c *Controller) getRepoCheckAccess(ctx context.Context,
session *auth.Session, repoRef string, reqPermission enum.Permission,
//nolint:unparam
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
@ -80,6 +85,10 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return nil, fmt.Errorf("failed to find repository: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/pipeline/checks"
"github.com/harness/gitness/types"
@ -34,14 +33,15 @@ func (c *Controller) Cancel(
pipelineIdentifier string,
executionNum int64,
) (*types.Execution, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(
ctx,
session,
repoRef,
pipelineIdentifier,
enum.PermissionPipelineExecute,
)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineExecute)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
return nil, err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -15,6 +15,11 @@
package execution
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/pipeline/canceler"
"github.com/harness/gitness/app/pipeline/commit"
@ -22,6 +27,8 @@ import (
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
type Controller struct {
@ -62,3 +69,38 @@ func NewController(
repoFinder: repoFinder,
}
}
// getRepoCheckPipelineAccess fetches a repo, checks if the permission is allowed based on the repo state,
// and checks if the current user has permission to access pipelines belong to it.
//
//nolint:unparam
func (c *Controller) getRepoCheckPipelineAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
pipelineIdentifier string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
err = apiauth.CheckPipeline(
ctx,
c.authorizer,
session,
repo.Path,
pipelineIdentifier,
reqPermission)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
return repo, nil
}

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/pipeline/triggerer"
"github.com/harness/gitness/types"
@ -34,15 +33,9 @@ func (c *Controller) Create(
pipelineIdentifier string,
branch string,
) (*types.Execution, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, pipelineIdentifier, enum.PermissionPipelineExecute)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path,
pipelineIdentifier, enum.PermissionPipelineExecute)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
return nil, err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
@ -30,14 +29,15 @@ func (c *Controller) Delete(
pipelineIdentifier string,
executionNum int64,
) error {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(
ctx,
session,
repoRef,
pipelineIdentifier,
enum.PermissionPipelineDelete,
)
if err != nil {
return fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineDelete)
if err != nil {
return fmt.Errorf("failed to authorize: %w", err)
return err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -31,14 +30,15 @@ func (c *Controller) Find(
pipelineIdentifier string,
executionNum int64,
) (*types.Execution, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(
ctx,
session,
repoRef,
pipelineIdentifier,
enum.PermissionPipelineView,
)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
return nil, err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
@ -32,14 +31,15 @@ func (c *Controller) List(
pipelineIdentifier string,
pagination types.Pagination,
) ([]*types.Execution, int64, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(
ctx,
session,
repoRef,
pipelineIdentifier,
enum.PermissionPipelineView,
)
if err != nil {
return nil, 0, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, 0, fmt.Errorf("failed to authorize: %w", err)
return nil, 0, err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -92,8 +92,12 @@ func NewController(
}
}
func (c *Controller) getRepoCheckAccess(ctx context.Context,
_ *auth.Session, repoID int64, _ enum.Permission) (*types.Repository, error) {
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
_ *auth.Session,
repoID int64,
_ enum.Permission,
) (*types.Repository, error) {
if repoID < 1 {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
}
@ -102,6 +106,7 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
if err != nil {
return nil, fmt.Errorf("failed to find repo with id %d: %w", repoID, err)
}
// repo state check is done in pre-receive.
// TODO: execute permission check. block anything but Harness service?

View File

@ -33,6 +33,9 @@ import (
"golang.org/x/exp/slices"
)
// allowedRepoStatesForPush lists repository states that git push is allowed for internal and external calls.
var allowedRepoStatesForPush = []enum.RepoState{enum.RepoStateActive, enum.RepoStateMigrateGitPush}
// PreReceive executes the pre-receive hook for a git repository.
func (c *Controller) PreReceive(
ctx context.Context,
@ -47,8 +50,8 @@ func (c *Controller) PreReceive(
return hook.Output{}, err
}
if !in.Internal && repo.State != enum.RepoStateActive && repo.State != enum.RepoStateMigrateGitPush {
output.Error = ptr.String("Push not allowed in the current repository state")
if !in.Internal && !slices.Contains(allowedRepoStatesForPush, repo.State) {
output.Error = ptr.String(fmt.Sprintf("Push not allowed when repository is in '%s' state", repo.State))
return output, nil
}

View File

@ -108,6 +108,8 @@ func (c *Controller) getRepoCheckAccess(
return nil, fmt.Errorf("failed to find repo: %w", err)
}
// repo state check happens per operation as it varies given the stage of the migration.
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("failed to verify authorization: %w", err)
}

View File

@ -15,10 +15,17 @@
package pipeline
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
events "github.com/harness/gitness/app/events/pipeline"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
type Controller struct {
@ -45,3 +52,33 @@ func NewController(
reporter: reporter,
}
}
// getRepoCheckPipelineAccess fetches a repo, checks if operation is allowed given the repo state
// and checks if the current user has permission to access pipelines of the repo.
//
//nolint:unparam
func (c *Controller) getRepoCheckPipelineAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
pipelineIdentifier string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path,
pipelineIdentifier, reqPermission)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
return repo, nil
}

View File

@ -20,7 +20,6 @@ import (
"strings"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
events "github.com/harness/gitness/app/events/pipeline"
@ -57,14 +56,9 @@ func (c *Controller) Create(
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, "", enum.PermissionPipelineEdit)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, "", enum.PermissionPipelineEdit)
if err != nil {
return nil, fmt.Errorf("failed to authorize pipeline: %w", err)
return nil, err
}
var pipeline *types.Pipeline

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
@ -29,14 +28,9 @@ func (c *Controller) Delete(
repoRef string,
identifier string,
) error {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, identifier, enum.PermissionPipelineDelete)
if err != nil {
return fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, identifier, enum.PermissionPipelineDelete)
if err != nil {
return fmt.Errorf("failed to authorize pipeline: %w", err)
return err
}
err = c.pipelineStore.DeleteByIdentifier(ctx, repo.ID, identifier)

View File

@ -16,9 +16,7 @@ package pipeline
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -30,14 +28,9 @@ func (c *Controller) Find(
repoRef string,
identifier string,
) (*types.Pipeline, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, identifier, enum.PermissionPipelineView)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, identifier, enum.PermissionPipelineView)
if err != nil {
return nil, fmt.Errorf("failed to authorize pipeline: %w", err)
return nil, err
}
return c.pipelineStore.FindByIdentifier(ctx, repo.ID, identifier)

View File

@ -19,7 +19,6 @@ import (
"fmt"
"strings"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
events "github.com/harness/gitness/app/events/pipeline"
"github.com/harness/gitness/types"
@ -43,14 +42,9 @@ func (c *Controller) Update(
identifier string,
in *UpdateInput,
) (*types.Pipeline, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, identifier, enum.PermissionPipelineEdit)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, identifier, enum.PermissionPipelineEdit)
if err != nil {
return nil, fmt.Errorf("failed to authorize pipeline: %w", err)
return nil, err
}
if err = c.sanitizeUpdateInput(in); err != nil {

View File

@ -185,21 +185,26 @@ func (c *Controller) getRepo(ctx context.Context, repoRef string) (*types.Reposi
return nil, fmt.Errorf("failed to find repository: %w", err)
}
if repo.State != enum.RepoStateActive {
return nil, usererror.BadRequest("Repository is not ready to use.")
}
return repo, nil
}
func (c *Controller) getRepoCheckAccess(ctx context.Context,
session *auth.Session, repoRef string, reqPermission enum.Permission,
//nolint:unparam
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
repo, err := c.getRepo(ctx, repoRef)
if err != nil {
return nil, err
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}

View File

@ -60,6 +60,11 @@ func (c *Controller) ReviewerDelete(
reqPermission = enum.PermissionRepoEdit // RepoEdit permission is required to remove a submitted review.
}
// Make sure the repository state allows delete reviewer operation.
if err = apiauth.CheckRepoState(ctx, session, repo, reqPermission); err != nil {
return err
}
// Make sure the caller has the right permission even if the PR is merged, so that we can return the correct error.
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return fmt.Errorf("access check failed: %w", err)

View File

@ -51,12 +51,13 @@ import (
"github.com/harness/gitness/types/enum"
)
var errPublicRepoCreationDisabled = usererror.BadRequestf("Public repository creation is disabled.")
var errPublicRepoCreationDisabled = usererror.BadRequest("Public repository creation is disabled.")
type RepositoryOutput struct {
types.Repository
IsPublic bool `json:"is_public" yaml:"is_public"`
Importing bool `json:"importing" yaml:"-"`
Archived bool `json:"archived" yaml:"-"`
}
// TODO [CODE-1363]: remove after identifier migration.
@ -187,26 +188,16 @@ func NewController(
}
}
// getRepo fetches an active repo (not one that is currently being imported).
func (c *Controller) getRepo(
ctx context.Context,
repoRef string,
) (*types.Repository, error) {
return GetRepo(
ctx,
c.repoFinder,
repoRef,
ActiveRepoStates,
)
}
// getRepoCheckAccess fetches an active repo (not one that is currently being imported)
// getRepoCheckAccess fetches a repo, checks if repo state allows requested permission
// and checks if the current user has permission to access it.
//
//nolint:unparam
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
return GetRepoCheckAccess(
ctx,
@ -215,7 +206,7 @@ func (c *Controller) getRepoCheckAccess(
session,
repoRef,
reqPermission,
ActiveRepoStates,
allowedRepoStates...,
)
}
@ -234,7 +225,8 @@ func (c *Controller) getRepoCheckAccessForGit(
session,
repoRef,
reqPermission,
nil, // Any state allowed - we'll block in the pre-receive hook.
// importing/migrating states are allowed - we'll block in the pre-receive hook if needed.
enum.RepoStateGitImport, enum.RepoStateMigrateDataImport, enum.RepoStateMigrateGitPush,
)
}

View File

@ -24,20 +24,24 @@ import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"golang.org/x/exp/slices"
)
var ActiveRepoStates = []enum.RepoState{enum.RepoStateActive}
var importingStates = []enum.RepoState{
enum.RepoStateGitImport,
enum.RepoStateMigrateDataImport,
enum.RepoStateMigrateGitPush,
}
// GetRepo fetches an repository.
func GetRepo(
ctx context.Context,
repoFinder refcache.RepoFinder,
repoRef string,
allowedStates []enum.RepoState,
) (*types.Repository, error) {
if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
@ -48,14 +52,10 @@ func GetRepo(
return nil, fmt.Errorf("failed to find repository: %w", err)
}
if len(allowedStates) > 0 && !slices.Contains(allowedStates, repo.State) {
return nil, usererror.BadRequest("Repository is not ready to use.")
}
return repo, nil
}
// GetRepoCheckAccess fetches an active repo (not one that is currently being imported)
// GetRepoCheckAccess fetches a repo with option to enforce repo state check
// and checks if the current user has permission to access it.
func GetRepoCheckAccess(
ctx context.Context,
@ -64,13 +64,17 @@ func GetRepoCheckAccess(
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
allowedStates []enum.RepoState,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
repo, err := GetRepo(ctx, repoFinder, repoRef, allowedStates)
repo, err := GetRepo(ctx, repoFinder, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
@ -119,7 +123,8 @@ func GetRepoOutput(
return &RepositoryOutput{
Repository: *repo,
IsPublic: isPublic,
Importing: repo.State != enum.RepoStateActive,
Importing: slices.Contains(importingStates, repo.State),
Archived: repo.State == enum.RepoStateArchived,
}, nil
}
@ -131,6 +136,38 @@ func GetRepoOutputWithAccess(
return &RepositoryOutput{
Repository: *repo,
IsPublic: isPublic,
Importing: repo.State != enum.RepoStateActive,
Importing: slices.Contains(importingStates, repo.State),
Archived: repo.State == enum.RepoStateArchived,
}
}
// GetRepoCheckServiceAccountAccess fetches a repo with option to enforce repo state check
// and checks if the current user has permission to access service accounts within repo.
func GetRepoCheckServiceAccountAccess(
ctx context.Context,
session *auth.Session,
authorizer authz.Authorizer,
repoRef string,
reqPermission enum.Permission,
repoFinder refcache.RepoFinder,
repoStore store.RepoStore,
spaceStore store.SpaceStore,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
repo, err := GetRepo(ctx, repoFinder, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
if err := apiauth.CheckServiceAccount(ctx, authorizer, session, spaceStore, repoStore,
enum.ParentResourceTypeRepo, repo.ID, "", reqPermission,
); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
return repo, nil
}

View File

@ -32,10 +32,15 @@ func (c *Controller) ListPipelines(
repoRef string,
filter *types.ListPipelinesFilter,
) ([]*types.Pipeline, int64, error) {
repo, err := c.getRepo(ctx, repoRef)
repo, err := GetRepo(ctx, c.repoFinder, repoRef)
if err != nil {
return nil, 0, fmt.Errorf("failed to find repo: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, enum.PermissionPipelineView); err != nil {
return nil, 0, err
}
if err := apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, "", enum.PermissionPipelineView); err != nil {
return nil, 0, fmt.Errorf("access check failed: %w", err)
}

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -30,22 +29,16 @@ func (c *Controller) ListServiceAccounts(
session *auth.Session,
repoRef string,
) ([]*types.ServiceAccount, error) {
repo, err := c.getRepo(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo: %w", err)
}
if err := apiauth.CheckServiceAccount(
repo, err := GetRepoCheckServiceAccountAccess(
ctx,
c.authorizer,
session,
c.spaceStore,
c.repoStore,
enum.ParentResourceTypeRepo,
repo.ID,
"",
c.authorizer,
repoRef,
enum.PermissionServiceAccountView,
); err != nil {
c.repoFinder,
c.repoStore,
c.spaceStore)
if err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}

View File

@ -18,8 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -53,17 +51,9 @@ func (c *Controller) Move(ctx context.Context,
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}
if repo.State != enum.RepoStateActive {
return nil, usererror.BadRequest("Can't move a repo that isn't ready to use.")
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit); err != nil {
return nil, err
return nil, fmt.Errorf("failed to find or acquire access to repo: %w", err)
}
if !in.hasChanges(repo) {

View File

@ -19,6 +19,8 @@ import (
"fmt"
"strings"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/audit"
@ -27,15 +29,31 @@ import (
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
"golang.org/x/exp/slices"
)
// UpdateInput is used for updating a repo.
type UpdateInput struct {
Description *string `json:"description"`
Description *string `json:"description"`
State *enum.RepoState `json:"state"`
}
var allowedRepoStateTransitions = map[enum.RepoState][]enum.RepoState{
enum.RepoStateActive: {enum.RepoStateArchived, enum.RepoStateMigrateDataImport},
enum.RepoStateArchived: {enum.RepoStateActive},
enum.RepoStateMigrateDataImport: {enum.RepoStateActive},
enum.RepoStateMigrateGitPush: {enum.RepoStateActive, enum.RepoStateMigrateDataImport},
}
func (in *UpdateInput) hasChanges(repo *types.Repository) bool {
return in.Description != nil && *in.Description != repo.Description
if in.Description != nil && *in.Description != repo.Description {
return true
}
if in.State != nil && *in.State != repo.State {
return true
}
return false
}
// Update updates a repository.
@ -44,11 +62,27 @@ func (c *Controller) Update(ctx context.Context,
repoRef string,
in *UpdateInput,
) (*RepositoryOutput, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
repo, err := GetRepo(ctx, c.repoFinder, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo: %w", err)
}
var additionalAllowedRepoStates []enum.RepoState
if in.State != nil {
additionalAllowedRepoStates = []enum.RepoState{
enum.RepoStateArchived, enum.RepoStateMigrateDataImport, enum.RepoStateMigrateGitPush}
}
err = apiauth.CheckRepoState(ctx, session, repo, enum.PermissionRepoEdit, additionalAllowedRepoStates...)
if err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
repoClone := repo.Clone()
if !in.hasChanges(repo) {
@ -59,16 +93,25 @@ func (c *Controller) Update(ctx context.Context,
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
if in.State != nil &&
!slices.Contains(allowedRepoStateTransitions[repo.State], *in.State) {
return nil, usererror.BadRequestf("Changing the state of a repository from %s to %s is not allowed.",
repo.State, *in.State)
}
repo, err = c.repoStore.UpdateOptLock(ctx, repo, func(repo *types.Repository) error {
// update values only if provided
if in.Description != nil {
repo.Description = *in.Description
}
if in.State != nil {
repo.State = *in.State
}
return nil
})
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to update the repo: %w", err)
}
err = c.auditService.Log(ctx,

View File

@ -16,7 +16,9 @@ package reposettings
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
@ -48,22 +50,27 @@ func NewController(
}
}
// getRepoCheckAccess fetches an active repo (not one that is currently being imported)
// getRepoCheckAccess fetches a repo, checks if operation is allowed given the repo state
// and checks if the current user has permission to access it.
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
// migrating repositories need to adjust the repo settings during the import, hence expanding the allowedstates.
return repo.GetRepoCheckAccess(
ctx,
c.repoFinder,
c.authorizer,
session,
repoRef,
reqPermission,
[]enum.RepoState{enum.RepoStateActive, enum.RepoStateMigrateGitPush},
)
repo, err := repo.GetRepo(ctx, c.repoFinder, repoRef)
if err != nil {
return nil, err
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
return repo, nil
}

View File

@ -28,7 +28,9 @@ func (c *Controller) GeneralFind(
session *auth.Session,
repoRef string,
) (*GeneralSettings, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
// migrating repos need to adjust repo settings (like file-size-limit) during the migration.
var additionalAllowedRepoStates = []enum.RepoState{enum.RepoStateMigrateGitPush}
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, additionalAllowedRepoStates...)
if err != nil {
return nil, err
}

View File

@ -33,7 +33,9 @@ func (c *Controller) GeneralUpdate(
repoRef string,
in *GeneralSettings,
) (*GeneralSettings, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
// migrating repos need to adjust repo settings (like file-size-limit) during the migration.
var additionalAllowedRepoStates = []enum.RepoState{enum.RepoStateMigrateGitPush}
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, additionalAllowedRepoStates...)
if err != nil {
return nil, err
}

View File

@ -15,9 +15,16 @@
package trigger
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
type Controller struct {
@ -40,3 +47,33 @@ func NewController(
repoFinder: repoFinder,
}
}
// getRepoCheckPipelineAccess fetches a repo, checks if the permission is allowed based on the repo state,
// and checks if the current user has permission to access pipelines of the repo.
//
//nolint:unparam
func (c *Controller) getRepoCheckPipelineAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
pipelineIdentifier string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path,
pipelineIdentifier, reqPermission)
if err != nil {
return nil, fmt.Errorf("failed to authorize: %w", err)
}
return repo, nil
}

View File

@ -19,7 +19,6 @@ import (
"fmt"
"time"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
@ -48,16 +47,9 @@ func (c *Controller) Create(
return nil, fmt.Errorf("invalid input: %w", err)
}
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, pipelineIdentifier, enum.PermissionPipelineEdit)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
// Trigger permissions are associated with pipeline permissions. If a user has permissions
// to edit the pipeline, they will have permissions to create a trigger as well.
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineEdit)
if err != nil {
return nil, fmt.Errorf("failed to authorize pipeline: %w", err)
return nil, err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types/enum"
)
@ -30,16 +29,9 @@ func (c *Controller) Delete(
pipelineIdentifier string,
triggerIdentifier string,
) error {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, pipelineIdentifier, enum.PermissionPipelineEdit)
if err != nil {
return fmt.Errorf("failed to find repo by ref: %w", err)
}
// Trigger permissions are associated with pipeline permissions. If a user has permissions
// to edit the pipeline, they will have permissions to remove a trigger as well.
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineEdit)
if err != nil {
return fmt.Errorf("failed to authorize pipeline: %w", err)
return err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -31,14 +30,9 @@ func (c *Controller) Find(
pipelineIdentifier string,
triggerIdentifier string,
) (*types.Trigger, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, fmt.Errorf("failed to authorize pipeline: %w", err)
return nil, err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -18,7 +18,6 @@ import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -31,16 +30,9 @@ func (c *Controller) List(
pipelineIdentifier string,
filter types.ListQueryFilter,
) ([]*types.Trigger, int64, error) {
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, 0, fmt.Errorf("failed to find repo by ref: %w", err)
}
// Trigger permissions are associated with pipeline permissions. If a user has permissions
// to view the pipeline, they will have permissions to list triggers as well.
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineView)
if err != nil {
return nil, 0, fmt.Errorf("failed to authorize pipeline: %w", err)
return nil, 0, err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -19,7 +19,6 @@ import (
"fmt"
"strings"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/check"
@ -49,16 +48,9 @@ func (c *Controller) Update(
return nil, fmt.Errorf("invalid input: %w", err)
}
repo, err := c.repoFinder.FindByRef(ctx, repoRef)
repo, err := c.getRepoCheckPipelineAccess(ctx, session, repoRef, pipelineIdentifier, enum.PermissionPipelineEdit)
if err != nil {
return nil, fmt.Errorf("failed to find repo by ref: %w", err)
}
// Trigger permissions are associated with pipeline permissions. If a user has permissions
// to edit the pipeline, they will have permissions to edit the trigger as well.
err = apiauth.CheckPipeline(ctx, c.authorizer, session, repo.Path, pipelineIdentifier, enum.PermissionPipelineEdit)
if err != nil {
return nil, fmt.Errorf("failed to authorize pipeline: %w", err)
return nil, err
}
pipeline, err := c.pipelineStore.FindByIdentifier(ctx, repo.ID, pipelineIdentifier)

View File

@ -17,7 +17,6 @@ package upload
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"strings"
@ -28,6 +27,7 @@ import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/refcache"
"github.com/harness/gitness/blob"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -61,10 +61,13 @@ func NewController(authorizer authz.Authorizer,
blobStore: blobStore,
}
}
//nolint:unparam
func (c *Controller) getRepoCheckAccess(ctx context.Context,
session *auth.Session,
repoRef string,
permission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
@ -75,6 +78,10 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return nil, fmt.Errorf("failed to find repo: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, permission, allowedRepoStates...); err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, permission); err != nil {
return nil, fmt.Errorf("failed to verify authorization: %w", err)
}

View File

@ -57,8 +57,14 @@ func NewController(
}
}
func (c *Controller) getRepoCheckAccess(ctx context.Context,
session *auth.Session, repoRef string, reqPermission enum.Permission) (*types.Repository, error) {
//nolint:unparam
func (c *Controller) getRepoCheckAccess(
ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
allowedRepoStates ...enum.RepoState,
) (*types.Repository, error) {
if repoRef == "" {
return nil, errors.InvalidArgument("A valid repository reference must be provided.")
}
@ -68,6 +74,10 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return nil, fmt.Errorf("failed to find repo: %w", err)
}
if err := apiauth.CheckRepoState(ctx, session, repo, reqPermission, allowedRepoStates...); err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("failed to verify authorization: %w", err)
}

View File

@ -82,6 +82,7 @@ const (
RepoStateGitImport
RepoStateMigrateGitPush
RepoStateMigrateDataImport
RepoStateArchived
)
// String returns the string representation of the RepoState.
@ -95,6 +96,8 @@ func (state RepoState) String() string {
return "migrate-git-push"
case RepoStateMigrateDataImport:
return "migrate-data-import"
case RepoStateArchived:
return "archived"
default:
return undefined
}