Support Public Access (#2021)

ui/diff-word-wrap
Atefeh Mohseni Ejiyeh 2024-05-23 04:56:26 +00:00 committed by Harness
parent 831cf18abc
commit c365ef246a
141 changed files with 1924 additions and 489 deletions

View File

@ -30,7 +30,6 @@ import (
)
var (
ErrNotAuthenticated = errors.New("not authenticated")
ErrNotAuthorized = errors.New("not authorized")
ErrParentResourceTypeUnknown = errors.New("Unknown parent resource type")
ErrPrincipalTypeUnknown = errors.New("Unknown principal type")
@ -42,10 +41,6 @@ var (
func Check(ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
scope *types.Scope, resource *types.Resource, permission enum.Permission,
) error {
if session == nil {
return ErrNotAuthenticated
}
authorized, err := authorizer.Check(
ctx,
session,
@ -104,7 +99,7 @@ func getScopeForParent(ctx context.Context, spaceStore store.SpaceStore, repoSto
spacePath, repoName, err := paths.DisectLeaf(repo.Path)
if err != nil {
return nil, errors.Wrapf(err, "Failed to disect path '%s'", repo.Path)
return nil, fmt.Errorf("failed to disect path '%s': %w", repo.Path, err)
}
return &types.Scope{SpacePath: spacePath, Repo: repoName}, nil

View File

@ -16,14 +16,13 @@ package auth
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/pkg/errors"
)
// CheckPipeline checks if a pipeline specific permission is granted for the current auth session
@ -34,7 +33,7 @@ func CheckPipeline(ctx context.Context, authorizer authz.Authorizer, session *au
repoPath string, pipelineIdentifier string, permission enum.Permission) error {
spacePath, repoName, err := paths.DisectLeaf(repoPath)
if err != nil {
return errors.Wrapf(err, "Failed to disect path '%s'", repoPath)
return fmt.Errorf("failed to disect path '%s': %w", repoPath, err)
}
scope := &types.Scope{SpacePath: spacePath, Repo: repoName}
resource := &types.Resource{

View File

@ -37,15 +37,10 @@ func CheckRepo(
session *auth.Session,
repo *types.Repository,
permission enum.Permission,
orPublic bool,
) error {
if orPublic && repo.IsPublic {
return nil
}
parentSpace, name, err := paths.DisectLeaf(repo.Path)
if err != nil {
return errors.Wrapf(err, "Failed to disect path '%s'", repo.Path)
return fmt.Errorf("failed to disect path '%s': %w", repo.Path, err)
}
scope := &types.Scope{SpacePath: parentSpace}
@ -64,7 +59,7 @@ func IsRepoOwner(
repo *types.Repository,
) (bool, error) {
// for now we use repoedit as permission to verify if someone is a SpaceOwner and hence a RepoOwner.
err := CheckRepo(ctx, authorizer, session, repo, enum.PermissionRepoEdit, false)
err := CheckRepo(ctx, authorizer, session, repo, enum.PermissionRepoEdit)
if err != nil && !errors.Is(err, ErrNotAuthorized) {
return false, fmt.Errorf("failed to check access user access: %w", err)
}

View File

@ -16,14 +16,13 @@ package auth
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/pkg/errors"
)
// CheckSpace checks if a space specific permission is granted for the current auth session
@ -35,15 +34,10 @@ func CheckSpace(
session *auth.Session,
space *types.Space,
permission enum.Permission,
orPublic bool,
) error {
if orPublic && space.IsPublic {
return nil
}
parentSpace, name, err := paths.DisectLeaf(space.Path)
if err != nil {
return errors.Wrapf(err, "Failed to disect path '%s'", space.Path)
return fmt.Errorf("failed to disect path '%s': %w", space.Path, err)
}
scope := &types.Scope{SpacePath: parentSpace}
@ -65,12 +59,7 @@ func CheckSpaceScope(
space *types.Space,
resourceType enum.ResourceType,
permission enum.Permission,
orPublic bool,
) error {
if orPublic && space.IsPublic {
return nil
}
scope := &types.Scope{SpacePath: space.Path}
resource := &types.Resource{
Type: resourceType,

View File

@ -68,7 +68,7 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return nil, fmt.Errorf("failed to find repository: %w", err)
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}

View File

@ -15,15 +15,18 @@
package principal
import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/store"
)
type controller struct {
principalStore store.PrincipalStore
authorizer authz.Authorizer
}
func newController(principalStore store.PrincipalStore) *controller {
func newController(principalStore store.PrincipalStore, authorizer authz.Authorizer) *controller {
return &controller{
principalStore: principalStore,
authorizer: authorizer,
}
}

View File

@ -16,15 +16,44 @@ package principal
import (
"context"
"net/http"
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"
)
func (c controller) Find(ctx context.Context, principalID int64) (*types.PrincipalInfo, error) {
func (c controller) Find(
ctx context.Context,
session *auth.Session,
principalID int64,
) (*types.PrincipalInfo, error) {
principal, err := c.principalStore.Find(ctx, principalID)
if err != nil {
return nil, err
}
if principal.Type != enum.PrincipalTypeUser {
return nil, usererror.Newf(
http.StatusNotImplemented,
"only user principals are supported currently.",
)
}
if err := apiauth.Check(
ctx,
c.authorizer,
session,
&types.Scope{},
&types.Resource{
Type: enum.ResourceTypeUser,
},
enum.PermissionUserView,
); err != nil {
return nil, err
}
return principal.ToPrincipalInfo(), nil
}

View File

@ -17,6 +17,7 @@ package principal
import (
"context"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
)
@ -24,6 +25,6 @@ import (
// principal related information.
type Controller interface {
// List lists the principals based on the provided filter.
List(ctx context.Context, opts *types.PrincipalFilter) ([]*types.PrincipalInfo, error)
Find(ctx context.Context, principalID int64) (*types.PrincipalInfo, error)
List(ctx context.Context, session *auth.Session, opts *types.PrincipalFilter) ([]*types.PrincipalInfo, error)
Find(ctx context.Context, session *auth.Session, principalID int64) (*types.PrincipalInfo, error)
}

View File

@ -16,12 +16,44 @@ package principal
import (
"context"
"net/http"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
func (c controller) List(ctx context.Context, opts *types.PrincipalFilter) (
[]*types.PrincipalInfo, error) {
func (c controller) List(
ctx context.Context,
session *auth.Session,
opts *types.PrincipalFilter,
) ([]*types.PrincipalInfo, error) {
// only user search is supported right now!
if len(opts.Types) != 1 || opts.Types[0] != enum.PrincipalTypeUser {
return nil, usererror.Newf(
http.StatusNotImplemented,
"Only listing of users is supported at this moment (use query '%s=%s').",
request.QueryParamType,
enum.PrincipalTypeUser,
)
}
if err := apiauth.Check(
ctx,
c.authorizer,
session,
&types.Scope{},
&types.Resource{
Type: enum.ResourceTypeUser,
},
enum.PermissionUserView,
); err != nil {
return nil, err
}
principals, err := c.principalStore.List(ctx, opts)
if err != nil {
return nil, err

View File

@ -15,6 +15,7 @@
package principal
import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/store"
"github.com/google/wire"
@ -25,6 +26,6 @@ var WireSet = wire.NewSet(
ProvideController,
)
func ProvideController(principalStore store.PrincipalStore) Controller {
return newController(principalStore)
func ProvideController(principalStore store.PrincipalStore, authorizer authz.Authorizer) Controller {
return newController(principalStore, authorizer)
}

View File

@ -156,7 +156,7 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return nil, usererror.BadRequest("Repository import is in progress.")
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}

View File

@ -60,7 +60,7 @@ func (c *Controller) Create(
sourceRepo := targetRepo
if in.SourceRepoRef != "" {
sourceRepo, err = c.getRepoCheckAccess(ctx, session, in.SourceRepoRef, enum.PermissionRepoView)
sourceRepo, err = c.getRepoCheckAccess(ctx, session, in.SourceRepoRef, enum.PermissionRepoPush)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to source repo: %w", err)
}

View File

@ -79,7 +79,7 @@ func (c *Controller) State(ctx context.Context,
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, sourceRepo,
enum.PermissionRepoView, false); err != nil {
enum.PermissionRepoView); err != nil {
return nil, fmt.Errorf("failed to acquire access to source repo: %w", err)
}
}

View File

@ -74,7 +74,7 @@ func (c *Controller) Update(ctx context.Context,
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, sourceRepo,
enum.PermissionRepoView, false); err != nil {
enum.PermissionRepoView); err != nil {
return nil, fmt.Errorf("failed to acquire access to source repo: %w", err)
}
}

View File

@ -87,7 +87,7 @@ func (c *Controller) ReviewerAdd(
if err = apiauth.CheckRepo(ctx, c.authorizer, &auth.Session{
Principal: *reviewerPrincipal,
Metadata: nil,
}, repo, enum.PermissionRepoView, false); err != nil {
}, repo, enum.PermissionRepoView); err != nil {
log.Ctx(ctx).Info().Msgf("Reviewer principal: %s access error: %s", reviewerInfo.UID, err)
return nil, usererror.BadRequest("The reviewer doesn't have enough permissions for the repository.")
}

View File

@ -31,7 +31,7 @@ func (c *Controller) Archive(
params api.ArchiveParams,
w io.Writer,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return err
}

View File

@ -39,7 +39,7 @@ func (c *Controller) Blame(ctx context.Context,
return nil, usererror.BadRequest("Line range must be valid.")
}
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -28,7 +28,7 @@ func (c *Controller) CodeOwnersValidate(
repoRef string,
ref string,
) (*types.CodeOwnersValidation, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -62,7 +62,7 @@ func (c *Controller) CommitFiles(ctx context.Context,
repoRef string,
in *CommitFilesOptions,
) (types.CommitFilesResponse, []types.RuleViolations, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush)
if err != nil {
return types.CommitFilesResponse{}, nil, err
}

View File

@ -101,7 +101,7 @@ func (c *Controller) GetContent(ctx context.Context,
repoPath string,
includeLatestCommit bool,
) (*GetContentOutput, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -39,7 +39,7 @@ func (c *Controller) PathsDetails(ctx context.Context,
gitRef string,
input PathsDetailsInput,
) (PathsDetailsOutput, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return PathsDetailsOutput{}, err
}

View File

@ -16,6 +16,7 @@ package repo
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
@ -31,6 +32,7 @@ import (
"github.com/harness/gitness/app/services/keywordsearch"
"github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
@ -43,13 +45,28 @@ import (
"github.com/harness/gitness/types/enum"
)
var (
errPublicRepoCreationDisabled = usererror.BadRequestf("Public repository creation is disabled.")
)
var errPublicRepoCreationDisabled = usererror.BadRequestf("Public repository creation is disabled.")
type RepositoryOutput struct {
types.Repository
IsPublic bool `json:"is_public" yaml:"is_public"`
}
// TODO [CODE-1363]: remove after identifier migration.
func (r RepositoryOutput) MarshalJSON() ([]byte, error) {
// alias allows us to embed the original object while avoiding an infinite loop of marshaling.
type alias RepositoryOutput
return json.Marshal(&struct {
alias
UID string `json:"uid"`
}{
alias: (alias)(r),
UID: r.Identifier,
})
}
type Controller struct {
defaultBranch string
publicResourceCreationEnabled bool
defaultBranch string
tx dbtx.Transactor
urlProvider url.Provider
@ -73,6 +90,7 @@ type Controller struct {
mtxManager lock.MutexManager
identifierCheck check.RepoIdentifier
repoCheck Check
publicAccess publicaccess.Service
}
func NewController(
@ -99,32 +117,33 @@ func NewController(
mtxManager lock.MutexManager,
identifierCheck check.RepoIdentifier,
repoCheck Check,
publicAccess publicaccess.Service,
) *Controller {
return &Controller{
defaultBranch: config.Git.DefaultBranch,
publicResourceCreationEnabled: config.PublicResourceCreationEnabled,
tx: tx,
urlProvider: urlProvider,
authorizer: authorizer,
repoStore: repoStore,
spaceStore: spaceStore,
pipelineStore: pipelineStore,
principalStore: principalStore,
ruleStore: ruleStore,
settings: settings,
principalInfoCache: principalInfoCache,
protectionManager: protectionManager,
git: git,
importer: importer,
codeOwners: codeOwners,
eventReporter: eventReporter,
indexer: indexer,
resourceLimiter: limiter,
locker: locker,
auditService: auditService,
mtxManager: mtxManager,
identifierCheck: identifierCheck,
repoCheck: repoCheck,
defaultBranch: config.Git.DefaultBranch,
tx: tx,
urlProvider: urlProvider,
authorizer: authorizer,
repoStore: repoStore,
spaceStore: spaceStore,
pipelineStore: pipelineStore,
principalStore: principalStore,
ruleStore: ruleStore,
settings: settings,
principalInfoCache: principalInfoCache,
protectionManager: protectionManager,
git: git,
importer: importer,
codeOwners: codeOwners,
eventReporter: eventReporter,
indexer: indexer,
resourceLimiter: limiter,
locker: locker,
auditService: auditService,
mtxManager: mtxManager,
identifierCheck: identifierCheck,
repoCheck: repoCheck,
publicAccess: publicAccess,
}
}
@ -147,7 +166,6 @@ func (c *Controller) getRepoCheckAccess(
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
orPublic bool,
) (*types.Repository, error) {
return GetRepoCheckAccess(
ctx,
@ -156,7 +174,6 @@ func (c *Controller) getRepoCheckAccess(
session,
repoRef,
reqPermission,
orPublic,
)
}

View File

@ -62,7 +62,7 @@ type CreateInput struct {
// Create creates a new repository.
//
//nolint:gocognit
func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*types.Repository, error) {
func (c *Controller) Create(ctx context.Context, session *auth.Session, in *CreateInput) (*RepositoryOutput, error) {
if err := c.sanitizeCreateInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
@ -72,11 +72,28 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
return nil, err
}
isPublicAccessSupported, err := c.publicAccess.IsPublicAccessSupported(ctx, parentSpace.Path)
if err != nil {
return nil, fmt.Errorf(
"failed to check if public access is supported for parent space %q: %w",
parentSpace.Path,
err,
)
}
if in.IsPublic && !isPublicAccessSupported {
return nil, errPublicRepoCreationDisabled
}
err = c.repoCheck.Create(ctx, session, in)
if err != nil {
return nil, err
}
gitResp, isEmpty, err := c.createGitRepository(ctx, session, in)
if err != nil {
return nil, fmt.Errorf("error creating repository on git: %w", err)
}
var repo *types.Repository
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
if err := c.resourceLimiter.RepoCount(ctx, parentSpace.ID, 1); err != nil {
@ -89,11 +106,6 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
return fmt.Errorf("failed to find the parent space: %w", err)
}
gitResp, isEmpty, err := c.createGitRepository(ctx, session, in)
if err != nil {
return fmt.Errorf("error creating repository on git: %w", err)
}
now := time.Now().UnixMilli()
repo = &types.Repository{
Version: 0,
@ -101,7 +113,6 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
Identifier: in.Identifier,
GitUID: gitResp.UID,
Description: in.Description,
IsPublic: in.IsPublic,
CreatedBy: session.Principal.ID,
Created: now,
Updated: now,
@ -109,34 +120,53 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
DefaultBranch: in.DefaultBranch,
IsEmpty: isEmpty,
}
err = c.repoStore.Create(ctx, repo)
if err != nil {
if dErr := c.DeleteGitRepository(ctx, session, repo); dErr != nil {
log.Ctx(ctx).Warn().Err(dErr).Msg("failed to delete repo for cleanup")
}
return fmt.Errorf("failed to create repository in storage: %w", err)
}
return nil
return c.repoStore.Create(ctx, repo)
}, sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
// best effort cleanup
if dErr := c.DeleteGitRepository(ctx, session, repo); dErr != nil {
log.Ctx(ctx).Warn().Err(dErr).Msg("failed to delete repo for cleanup")
}
return nil, err
}
err = c.publicAccess.Set(ctx, enum.PublicResourceTypeRepo, repo.Path, in.IsPublic)
if err != nil {
if dErr := c.publicAccess.Delete(ctx, enum.PublicResourceTypeRepo, repo.Path); dErr != nil {
return nil, fmt.Errorf("failed to set repo public access (and public access cleanup: %w): %w", dErr, err)
}
// only cleanup repo itself if cleanup of public access succeeded (to avoid leaking public access)
if dErr := c.PurgeNoAuth(ctx, session, repo); dErr != nil {
return nil, fmt.Errorf("failed to set repo public access (and repo purge: %w): %w", dErr, err)
}
return nil, fmt.Errorf("failed to set repo public access (succesfull cleanup): %w", err)
}
// backfil GitURL
repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path)
repoOutput := &RepositoryOutput{
Repository: *repo,
IsPublic: in.IsPublic,
}
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionCreated,
paths.Parent(repo.Path),
audit.WithNewObject(repo),
audit.WithNewObject(audit.RepositoryObject{
Repository: repoOutput.Repository,
IsPublic: repoOutput.IsPublic,
}),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for create repository operation: %s", err)
}
// backfil GitURL
repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path)
// index repository if files are created
if !repo.IsEmpty {
err = c.indexer.Index(ctx, repo)
@ -145,7 +175,7 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea
}
}
return repo, nil
return repoOutput, nil
}
func (c *Controller) getSpaceCheckAuthRepoCreation(
@ -166,7 +196,6 @@ func (c *Controller) getSpaceCheckAuthRepoCreation(
space,
enum.ResourceTypeRepo,
enum.PermissionRepoEdit,
false,
)
if err != nil {
return nil, fmt.Errorf("auth check failed: %w", err)
@ -181,10 +210,6 @@ func (c *Controller) sanitizeCreateInput(in *CreateInput) error {
in.Identifier = in.UID
}
if in.IsPublic && !c.publicResourceCreationEnabled {
return errPublicRepoCreationDisabled
}
if err := c.validateParentRef(in.ParentRef); err != nil {
return err
}

View File

@ -43,7 +43,7 @@ func (c *Controller) CreateBranch(ctx context.Context,
repoRef string,
in *CreateBranchInput,
) (*Branch, []types.RuleViolations, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush)
if err != nil {
return nil, nil, err
}

View File

@ -47,7 +47,7 @@ func (c *Controller) CreateCommitTag(ctx context.Context,
repoRef string,
in *CreateCommitTagInput,
) (*CommitTag, []types.RuleViolations, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush)
if err != nil {
return nil, nil, err
}

View File

@ -43,11 +43,12 @@ func (c *Controller) UpdateDefaultBranch(
session *auth.Session,
repoRef string,
in *UpdateDefaultBranchInput,
) (*types.Repository, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
) (*RepositoryOutput, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}
repoClone := repo.Clone()
// the max time we give an update default branch to succeed
const timeout = 2 * time.Minute
@ -95,13 +96,24 @@ func (c *Controller) UpdateDefaultBranch(
return nil, fmt.Errorf("failed to update the repo default branch on db:%w", err)
}
repoOutput, err := GetRepoOutput(ctx, c.publicAccess, repo)
if err != nil {
return nil, fmt.Errorf("failed to get repo output: %w", err)
}
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionUpdated,
paths.Parent(repo.Path),
audit.WithOldObject(repoClone),
audit.WithNewObject(repo),
audit.WithOldObject(audit.RepositoryObject{
Repository: repoClone,
IsPublic: repoOutput.IsPublic,
}),
audit.WithNewObject(audit.RepositoryObject{
Repository: *repo,
IsPublic: repoOutput.IsPublic,
}),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update default branch operation: %s", err)
@ -114,5 +126,5 @@ func (c *Controller) UpdateDefaultBranch(
NewName: repo.DefaultBranch,
})
return repo, nil
return repoOutput, nil
}

View File

@ -34,7 +34,7 @@ func (c *Controller) DeleteBranch(ctx context.Context,
branchName string,
bypassRules bool,
) ([]types.RuleViolations, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush)
if err != nil {
return nil, err
}

View File

@ -33,7 +33,7 @@ func (c *Controller) DeleteTag(ctx context.Context,
tagName string,
bypassRules bool,
) ([]types.RuleViolations, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoPush)
if err != nil {
return nil, err
}

View File

@ -36,7 +36,7 @@ func (c *Controller) RawDiff(
path string,
files ...gittypes.FileDiffRequest,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return err
}
@ -61,7 +61,7 @@ func (c *Controller) CommitDiff(
rev string,
w io.Writer,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return err
}
@ -104,7 +104,7 @@ func (c *Controller) DiffStats(
return types.DiffStats{}, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView); err != nil {
return types.DiffStats{}, err
}
@ -139,7 +139,7 @@ func (c *Controller) Diff(
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView); err != nil {
return nil, err
}

View File

@ -19,24 +19,23 @@ import (
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"
)
// Find finds a repo.
func (c *Controller) Find(ctx context.Context, session *auth.Session, repoRef string) (*types.Repository, error) {
func (c *Controller) Find(ctx context.Context, session *auth.Session, repoRef string) (*RepositoryOutput, error) {
// note: can't use c.getRepoCheckAccess because even repositories that are currently being imported can be fetched.
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, true); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView); err != nil {
return nil, err
}
// backfill clone url
repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path)
return repo, nil
return GetRepoOutput(ctx, c.publicAccess, repo)
}

View File

@ -29,7 +29,7 @@ func (c *Controller) GetBranch(ctx context.Context,
repoRef string,
branchName string,
) (*Branch, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -31,7 +31,7 @@ func (c *Controller) GetCommit(ctx context.Context,
repoRef string,
sha string,
) (*types.Commit, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -54,7 +54,7 @@ func (c *Controller) GetCommitDivergences(ctx context.Context,
repoRef string,
in *GetCommitDivergencesInput,
) ([]CommitDivergence, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -33,7 +33,7 @@ func (c *Controller) GitInfoRefs(
gitProtocol string,
w io.Writer,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return fmt.Errorf("failed to verify repo access: %w", err)
}

View File

@ -43,7 +43,7 @@ func (c *Controller) GitServicePack(
permission = enum.PermissionRepoPush
}
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, permission, !isWriteOperation)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, permission)
if err != nil {
return fmt.Errorf("failed to verify repo access: %w", err)
}

View File

@ -22,6 +22,7 @@ import (
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -58,16 +59,31 @@ func GetRepoCheckAccess(
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
orPublic bool,
) (*types.Repository, error) {
repo, err := GetRepo(ctx, repoStore, repoRef)
if err != nil {
return nil, fmt.Errorf("failed to find repo: %w", err)
}
if err = apiauth.CheckRepo(ctx, authorizer, session, repo, reqPermission, orPublic); err != nil {
if err = apiauth.CheckRepo(ctx, authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
return repo, nil
}
func GetRepoOutput(
ctx context.Context,
publicAccess publicaccess.Service,
repo *types.Repository,
) (*RepositoryOutput, error) {
isPublic, err := publicAccess.Get(ctx, enum.PublicResourceTypeRepo, repo.Path)
if err != nil {
return nil, fmt.Errorf("failed to check if repo is public: %w", err)
}
return &RepositoryOutput{
Repository: *repo,
IsPublic: isPublic,
}, nil
}

View File

@ -23,7 +23,6 @@ import (
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/app/services/importer"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/types"
"github.com/rs/zerolog/log"
)
@ -42,7 +41,7 @@ type ImportInput struct {
}
// Import creates a new empty repository and starts git import to it from a remote repository.
func (c *Controller) Import(ctx context.Context, session *auth.Session, in *ImportInput) (*types.Repository, error) {
func (c *Controller) Import(ctx context.Context, session *auth.Session, in *ImportInput) (*RepositoryOutput, error) {
if err := c.sanitizeImportInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
@ -52,24 +51,24 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
return nil, err
}
var repo *types.Repository
remoteRepository, provider, err := importer.LoadRepositoryFromProvider(ctx, in.Provider, in.ProviderRepo)
if err != nil {
return nil, err
}
repo, isPublic := remoteRepository.ToRepo(
parentSpace.ID,
parentSpace.Path,
in.Identifier,
in.Description,
&session.Principal,
)
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
if err := c.resourceLimiter.RepoCount(ctx, parentSpace.ID, 1); err != nil {
return fmt.Errorf("resource limit exceeded: %w", limiter.ErrMaxNumReposReached)
}
remoteRepository, provider, err := importer.LoadRepositoryFromProvider(ctx, in.Provider, in.ProviderRepo)
if err != nil {
return err
}
repo = remoteRepository.ToRepo(
parentSpace.ID,
in.Identifier,
in.Description,
&session.Principal,
c.publicResourceCreationEnabled,
)
// lock the space for update during repo creation to prevent racing conditions with space soft delete.
parentSpace, err = c.spaceStore.FindForUpdate(ctx, parentSpace.ID)
if err != nil {
@ -84,6 +83,7 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
err = c.importer.Run(ctx,
provider,
repo,
isPublic,
remoteRepository.CloneURL,
in.Pipelines,
)
@ -104,13 +104,19 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionCreated,
paths.Parent(repo.Path),
audit.WithNewObject(repo),
audit.WithNewObject(audit.RepositoryObject{
Repository: *repo,
IsPublic: false,
}),
)
if err != nil {
log.Warn().Msgf("failed to insert audit log for import repository operation: %s", err)
}
return repo, nil
return &RepositoryOutput{
Repository: *repo,
IsPublic: false,
}, nil
}
func (c *Controller) sanitizeImportInput(in *ImportInput) error {

View File

@ -38,7 +38,7 @@ func (c *Controller) ImportProgress(ctx context.Context,
return job.Progress{}, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView); err != nil {
return job.Progress{}, err
}

View File

@ -38,7 +38,7 @@ func (c *Controller) ListBranches(ctx context.Context,
includeCommit bool,
filter *types.BranchFilter,
) ([]Branch, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -42,7 +42,7 @@ func (c *Controller) ListCommitTags(ctx context.Context,
includeCommit bool,
filter *types.TagFilter,
) ([]CommitTag, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -32,7 +32,7 @@ func (c *Controller) ListCommits(ctx context.Context,
gitRef string,
filter *types.CommitFilter,
) (types.ListCommitResponse, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return types.ListCommitResponse{}, err
}

View File

@ -35,7 +35,7 @@ func (c *Controller) ListPaths(ctx context.Context,
gitRef string,
includeDirectories bool,
) (ListPathsOutput, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return ListPathsOutput{}, err
}

View File

@ -35,7 +35,7 @@ func (c *Controller) MergeCheck(
repoRef string,
diffPath string,
) (MergeCheck, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return MergeCheck{}, err
}

View File

@ -48,7 +48,7 @@ func (c *Controller) Move(ctx context.Context,
session *auth.Session,
repoRef string,
in *MoveInput,
) (*types.Repository, error) {
) (*RepositoryOutput, error) {
if err := c.sanitizeMoveInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
@ -62,14 +62,31 @@ func (c *Controller) Move(ctx context.Context,
return nil, usererror.BadRequest("can't move a repo that is being imported")
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit); err != nil {
return nil, err
}
if !in.hasChanges(repo) {
return repo, nil
return GetRepoOutput(ctx, c.publicAccess, repo)
}
oldIdentifier := repo.Identifier
isPublic, err := c.publicAccess.Get(ctx, enum.PublicResourceTypeRepo, repo.Path)
if err != nil {
return nil, fmt.Errorf("failed to get repo public access: %w", err)
}
// remove public access from old repo path to avoid leaking it
if err := c.publicAccess.Delete(
ctx,
enum.PublicResourceTypeRepo,
repo.Path,
); err != nil {
return nil, fmt.Errorf("failed to remove public access on the original path: %w", err)
}
// TODO add a repo level lock here to avoid racing condition or partial repo update w/o setting repo public access
repo, err = c.repoStore.UpdateOptLock(ctx, repo, func(r *types.Repository) error {
if in.Identifier != nil {
r.Identifier = *in.Identifier
@ -80,9 +97,42 @@ func (c *Controller) Move(ctx context.Context,
return nil, fmt.Errorf("failed to update repo: %w", err)
}
// set public access for the new repo path
if err := c.publicAccess.Set(ctx, enum.PublicResourceTypeRepo, repo.Path, isPublic); err != nil {
// ensure public access for new repo path is cleaned up first or we risk leaking it
if dErr := c.publicAccess.Delete(ctx, enum.PublicResourceTypeRepo, repo.Path); dErr != nil {
return nil, fmt.Errorf("failed to set repo public access (and public access cleanup: %w): %w", dErr, err)
}
// revert identifier changes first
var dErr error
repo, dErr = c.repoStore.UpdateOptLock(ctx, repo, func(r *types.Repository) error {
r.Identifier = oldIdentifier
return nil
})
if dErr != nil {
return nil, fmt.Errorf(
"failed to set public access for new path (and reverting of move: %w): %w",
dErr,
err,
)
}
// revert public access changes only after we successfully restored original path
if dErr = c.publicAccess.Set(ctx, enum.PublicResourceTypeRepo, repo.Path, isPublic); dErr != nil {
return nil, fmt.Errorf(
"failed to set public access for new path (and reverting of public access: %w): %w",
dErr,
err,
)
}
return nil, fmt.Errorf("failed to set repo public access for new path (cleanup successful): %w", err)
}
repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path)
return repo, nil
return GetRepoOutput(ctx, c.publicAccess, repo)
}
func (c *Controller) sanitizeMoveInput(in *MoveInput) error {

View File

@ -29,7 +29,7 @@ func (c *Controller) PipelineGenerate(
session *auth.Session,
repoRef string,
) ([]byte, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -43,7 +43,7 @@ func (c *Controller) Purge(
return fmt.Errorf("failed to find the repo (deleted at %d): %w", deletedAt, err)
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoDelete, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoDelete); err != nil {
return err
}

View File

@ -34,7 +34,7 @@ func (c *Controller) Raw(ctx context.Context,
gitRef string,
path string,
) (io.ReadCloser, int64, sha.SHA, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, 0, sha.Nil, err
}

View File

@ -40,13 +40,13 @@ func (c *Controller) Restore(
repoRef string,
deletedAt int64,
in *RestoreInput,
) (*types.Repository, error) {
) (*RepositoryOutput, error) {
repo, err := c.repoStore.FindByRefAndDeletedAt(ctx, repoRef, deletedAt)
if err != nil {
return nil, fmt.Errorf("failed to find repository: %w", err)
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoEdit); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
@ -75,7 +75,7 @@ func (c *Controller) RestoreNoAuth(
repo *types.Repository,
newIdentifier *string,
newParentID int64,
) (*types.Repository, error) {
) (*RepositoryOutput, error) {
var err error
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
if err := c.resourceLimiter.RepoCount(ctx, newParentID, 1); err != nil {
@ -93,5 +93,9 @@ func (c *Controller) RestoreNoAuth(
return nil, fmt.Errorf("failed to restore the repo: %w", err)
}
return repo, nil
// Repos restored as private since public access data has been deleted upon deletion.
return &RepositoryOutput{
Repository: *repo,
IsPublic: false,
}, nil
}

View File

@ -85,7 +85,7 @@ func (c *Controller) RuleCreate(ctx context.Context,
return nil, err
}
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}

View File

@ -32,7 +32,7 @@ func (c *Controller) RuleDelete(ctx context.Context,
repoRef string,
identifier string,
) error {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return err
}

View File

@ -29,7 +29,7 @@ func (c *Controller) RuleFind(ctx context.Context,
repoRef string,
identifier string,
) (*types.Rule, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -30,7 +30,7 @@ func (c *Controller) RuleList(ctx context.Context,
repoRef string,
filter *types.RuleFilter,
) ([]types.Rule, int64, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, 0, err
}

View File

@ -91,7 +91,7 @@ func (c *Controller) RuleUpdate(ctx context.Context,
return nil, err
}
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}

View File

@ -46,7 +46,7 @@ func (c *Controller) SoftDelete(
return nil, fmt.Errorf("failed to find the repo for soft delete: %w", err)
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoDelete, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoDelete); err != nil {
return nil, fmt.Errorf("access check failed: %w", err)
}
@ -54,6 +54,11 @@ func (c *Controller) SoftDelete(
return nil, usererror.BadRequest("repository has been already deleted")
}
isPublic, err := c.publicAccess.Get(ctx, enum.PublicResourceTypeRepo, repo.Path)
if err != nil {
return nil, fmt.Errorf("failed to check current public access status: %w", err)
}
log.Ctx(ctx).Info().
Int64("repo.id", repo.ID).
Str("repo.path", repo.Path).
@ -69,7 +74,10 @@ func (c *Controller) SoftDelete(
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionDeleted,
paths.Parent(repo.Path),
audit.WithOldObject(repo),
audit.WithOldObject(audit.RepositoryObject{
Repository: *repo,
IsPublic: isPublic,
}),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for delete repository operation: %s", err)
@ -84,12 +92,16 @@ func (c *Controller) SoftDeleteNoAuth(
repo *types.Repository,
deletedAt int64,
) error {
err := c.publicAccess.Delete(ctx, enum.PublicResourceTypeRepo, repo.Path)
if err != nil {
return fmt.Errorf("failed to delete public access for repo: %w", err)
}
if repo.Importing {
return c.PurgeNoAuth(ctx, session, repo)
}
err := c.repoStore.SoftDelete(ctx, repo, deletedAt)
if err != nil {
if err := c.repoStore.SoftDelete(ctx, repo, deletedAt); err != nil {
return fmt.Errorf("failed to soft delete repo from db: %w", err)
}

View File

@ -32,12 +32,10 @@ import (
// UpdateInput is used for updating a repo.
type UpdateInput struct {
Description *string `json:"description"`
IsPublic *bool `json:"is_public"`
}
func (in *UpdateInput) hasChanges(repo *types.Repository) bool {
return (in.Description != nil && *in.Description != repo.Description) ||
(in.IsPublic != nil && *in.IsPublic != repo.IsPublic)
return in.Description != nil && *in.Description != repo.Description
}
// Update updates a repository.
@ -45,8 +43,8 @@ func (c *Controller) Update(ctx context.Context,
session *auth.Session,
repoRef string,
in *UpdateInput,
) (*types.Repository, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
) (*RepositoryOutput, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}
@ -54,7 +52,7 @@ func (c *Controller) Update(ctx context.Context,
repoClone := repo.Clone()
if !in.hasChanges(repo) {
return repo, nil
return GetRepoOutput(ctx, c.publicAccess, repo)
}
if err = c.sanitizeUpdateInput(in); err != nil {
@ -66,9 +64,6 @@ func (c *Controller) Update(ctx context.Context,
if in.Description != nil {
repo.Description = *in.Description
}
if in.IsPublic != nil {
repo.IsPublic = *in.IsPublic
}
return nil
})
@ -91,16 +86,10 @@ func (c *Controller) Update(ctx context.Context,
// backfill repo url
repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path)
return repo, nil
return GetRepoOutput(ctx, c.publicAccess, repo)
}
func (c *Controller) sanitizeUpdateInput(in *UpdateInput) error {
if in.IsPublic != nil {
if *in.IsPublic && !c.publicResourceCreationEnabled {
return errPublicRepoCreationDisabled
}
}
if in.Description != nil {
*in.Description = strings.TrimSpace(*in.Description)
if err := check.Description(*in.Description); err != nil {

View File

@ -0,0 +1,101 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"context"
"fmt"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/audit"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type UpdatePublicAccessInput struct {
IsPublic bool `json:"is_public"`
}
func (c *Controller) UpdatePublicAccess(ctx context.Context,
session *auth.Session,
repoRef string,
in *UpdatePublicAccessInput,
) (*RepositoryOutput, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}
parentPath, _, err := paths.DisectLeaf(repo.Path)
if err != nil {
return nil, fmt.Errorf("failed to disect path %q: %w", repo.Path, err)
}
isPublicAccessSupported, err := c.publicAccess.IsPublicAccessSupported(ctx, parentPath)
if err != nil {
return nil, fmt.Errorf(
"failed to check if public access is supported for parent space %q: %w",
parentPath,
err,
)
}
if in.IsPublic && !isPublicAccessSupported {
return nil, errPublicRepoCreationDisabled
}
isPublic, err := c.publicAccess.Get(ctx, enum.PublicResourceTypeRepo, repo.Path)
if err != nil {
return nil, fmt.Errorf("failed to check current public access status: %w", err)
}
// no op
if isPublic == in.IsPublic {
return &RepositoryOutput{
Repository: *repo,
IsPublic: isPublic,
}, nil
}
if err = c.publicAccess.Set(ctx, enum.PublicResourceTypeRepo, repo.Path, in.IsPublic); err != nil {
return nil, fmt.Errorf("failed to update repo public access: %w", err)
}
// backfill GitURL
repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path)
err = c.auditService.Log(ctx,
session.Principal,
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionUpdated,
paths.Parent(repo.Path),
audit.WithOldObject(audit.RepositoryObject{
Repository: *repo,
IsPublic: isPublic,
}),
audit.WithNewObject(audit.RepositoryObject{
Repository: *repo,
IsPublic: in.IsPublic,
}),
)
if err != nil {
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update repository operation: %s", err)
}
return &RepositoryOutput{
Repository: *repo,
IsPublic: in.IsPublic,
}, nil
}

View File

@ -23,6 +23,7 @@ import (
"github.com/harness/gitness/app/services/keywordsearch"
"github.com/harness/gitness/app/services/locker"
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/settings"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
@ -65,12 +66,14 @@ func ProvideController(
mtxManager lock.MutexManager,
identifierCheck check.RepoIdentifier,
repoChecks Check,
publicAccess publicaccess.Service,
) *Controller {
return NewController(config, tx, urlProvider,
authorizer, repoStore,
spaceStore, pipelineStore,
authorizer,
repoStore, spaceStore, pipelineStore,
principalStore, ruleStore, settings, principalInfoCache, protectionManager, rpcClient, importer,
codeOwners, reporeporter, indexer, limiter, locker, auditService, mtxManager, identifierCheck, repoChecks)
codeOwners, reporeporter, indexer, limiter, locker, auditService, mtxManager, identifierCheck,
repoChecks, publicAccess)
}
func ProvideRepoCheck() Check {

View File

@ -55,7 +55,6 @@ func (c *Controller) getRepoCheckAccess(
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
orPublic bool,
) (*types.Repository, error) {
return repo.GetRepoCheckAccess(
ctx,
@ -64,6 +63,5 @@ func (c *Controller) getRepoCheckAccess(
session,
repoRef,
reqPermission,
orPublic,
)
}

View File

@ -28,7 +28,7 @@ func (c *Controller) GeneralFind(
session *auth.Session,
repoRef string,
) (*GeneralSettings, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -33,7 +33,7 @@ func (c *Controller) GeneralUpdate(
repoRef string,
in *GeneralSettings,
) (*GeneralSettings, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}

View File

@ -28,7 +28,7 @@ func (c *Controller) SecurityFind(
session *auth.Session,
repoRef string,
) (*SecuritySettings, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, err
}

View File

@ -33,7 +33,7 @@ func (c *Controller) SecurityUpdate(
repoRef string,
in *SecuritySettings,
) (*SecuritySettings, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit)
if err != nil {
return nil, err
}

View File

@ -15,12 +15,15 @@
package space
import (
"encoding/json"
"github.com/harness/gitness/app/api/controller/limiter"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/exporter"
"github.com/harness/gitness/app/services/importer"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
@ -36,9 +39,27 @@ var (
errPublicSpaceCreationDisabled = usererror.BadRequestf("Public space creation is disabled.")
)
//nolint:revive
type SpaceOutput struct {
types.Space
IsPublic bool `json:"is_public" yaml:"is_public"`
}
// TODO [CODE-1363]: remove after identifier migration.
func (s SpaceOutput) MarshalJSON() ([]byte, error) {
// alias allows us to embed the original object while avoiding an infinite loop of marshaling.
type alias SpaceOutput
return json.Marshal(&struct {
alias
UID string `json:"uid"`
}{
alias: (alias)(s),
UID: s.Identifier,
})
}
type Controller struct {
nestedSpacesEnabled bool
publicResourceCreationEnabled bool
nestedSpacesEnabled bool
tx dbtx.Transactor
urlProvider url.Provider
@ -58,6 +79,7 @@ type Controller struct {
importer *importer.Repository
exporter *exporter.Repository
resourceLimiter limiter.ResourceLimiter
publicAccess publicaccess.Service
auditService audit.Service
}
@ -67,29 +89,29 @@ func NewController(config *types.Config, tx dbtx.Transactor, urlProvider url.Pro
connectorStore store.ConnectorStore, templateStore store.TemplateStore, spaceStore store.SpaceStore,
repoStore store.RepoStore, principalStore store.PrincipalStore, repoCtrl *repo.Controller,
membershipStore store.MembershipStore, importer *importer.Repository, exporter *exporter.Repository,
limiter limiter.ResourceLimiter, auditService audit.Service,
limiter limiter.ResourceLimiter, publicAccess publicaccess.Service, auditService audit.Service,
) *Controller {
return &Controller{
nestedSpacesEnabled: config.NestedSpacesEnabled,
publicResourceCreationEnabled: config.PublicResourceCreationEnabled,
tx: tx,
urlProvider: urlProvider,
sseStreamer: sseStreamer,
identifierCheck: identifierCheck,
authorizer: authorizer,
spacePathStore: spacePathStore,
pipelineStore: pipelineStore,
secretStore: secretStore,
connectorStore: connectorStore,
templateStore: templateStore,
spaceStore: spaceStore,
repoStore: repoStore,
principalStore: principalStore,
repoCtrl: repoCtrl,
membershipStore: membershipStore,
importer: importer,
exporter: exporter,
resourceLimiter: limiter,
auditService: auditService,
nestedSpacesEnabled: config.NestedSpacesEnabled,
tx: tx,
urlProvider: urlProvider,
sseStreamer: sseStreamer,
identifierCheck: identifierCheck,
authorizer: authorizer,
spacePathStore: spacePathStore,
pipelineStore: pipelineStore,
secretStore: secretStore,
connectorStore: connectorStore,
templateStore: templateStore,
spaceStore: spaceStore,
repoStore: repoStore,
principalStore: principalStore,
repoCtrl: repoCtrl,
membershipStore: membershipStore,
importer: importer,
exporter: exporter,
resourceLimiter: limiter,
publicAccess: publicAccess,
auditService: auditService,
}
}

View File

@ -52,7 +52,7 @@ func (c *Controller) Create(
ctx context.Context,
session *auth.Session,
in *CreateInput,
) (*types.Space, error) {
) (*SpaceOutput, error) {
if err := c.sanitizeCreateInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize input: %w", err)
}
@ -62,6 +62,18 @@ func (c *Controller) Create(
return nil, err
}
isPublicAccessSupported, err := c.publicAccess.IsPublicAccessSupported(ctx, parentSpace.Path)
if err != nil {
return nil, fmt.Errorf(
"failed to check if public access is supported for parent space %q: %w",
parentSpace.Path,
err,
)
}
if in.IsPublic && !isPublicAccessSupported {
return nil, errPublicSpaceCreationDisabled
}
var space *types.Space
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
space, err = c.createSpaceInnerInTX(ctx, session, parentSpace.ID, in)
@ -71,7 +83,21 @@ func (c *Controller) Create(
return nil, err
}
return space, nil
err = c.publicAccess.Set(ctx, enum.PublicResourceTypeSpace, space.Path, in.IsPublic)
if err != nil {
if dErr := c.publicAccess.Delete(ctx, enum.PublicResourceTypeSpace, space.Path); dErr != nil {
return nil, fmt.Errorf("failed to set space public access (and public access cleanup: %w): %w", dErr, err)
}
// only cleanup space itself if cleanup of public access succeeded or we risk leaking public access
if dErr := c.PurgeNoAuth(ctx, session, space); dErr != nil {
return nil, fmt.Errorf("failed to set space public access (and space purge: %w): %w", dErr, err)
}
return nil, fmt.Errorf("failed to set space public access (succesfull cleanup): %w", err)
}
return GetSpaceOutput(ctx, c.publicAccess, space)
}
func (c *Controller) createSpaceInnerInTX(
@ -102,7 +128,6 @@ func (c *Controller) createSpaceInnerInTX(
ParentID: parentID,
Identifier: in.Identifier,
Description: in.Description,
IsPublic: in.IsPublic,
Path: spacePath,
CreatedBy: session.Principal.ID,
Created: now,
@ -158,7 +183,7 @@ func (c *Controller) getSpaceCheckAuthSpaceCreation(
parentRefAsID, err := strconv.ParseInt(parentRef, 10, 64)
if (parentRefAsID <= 0 && err == nil) || (len(strings.TrimSpace(parentRef)) == 0) {
// TODO: Restrict top level space creation - should be move to authorizer?
if session == nil {
if auth.IsAnonymousSession(session) {
return nil, fmt.Errorf("anonymous user not allowed to create top level spaces: %w", usererror.ErrUnauthorized)
}
@ -177,7 +202,6 @@ func (c *Controller) getSpaceCheckAuthSpaceCreation(
parentSpace,
enum.ResourceTypeSpace,
enum.PermissionSpaceEdit,
false,
); err != nil {
return nil, fmt.Errorf("authorization failed: %w", err)
}
@ -196,10 +220,6 @@ func (c *Controller) sanitizeCreateInput(in *CreateInput) error {
return errNestedSpacesNotSupported
}
if in.IsPublic && !c.publicResourceCreationEnabled {
return errPublicSpaceCreationDisabled
}
parentRefAsID, err := strconv.ParseInt(in.ParentRef, 10, 64)
if err == nil && parentRefAsID < 0 {
return errParentIDNegative

View File

@ -34,7 +34,7 @@ func (c *Controller) Events(
return nil, nil, nil, fmt.Errorf("failed to find space ref: %w", err)
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView); err != nil {
return nil, nil, nil, fmt.Errorf("failed to authorize stream: %w", err)
}

View File

@ -41,7 +41,7 @@ func (c *Controller) Export(ctx context.Context, session *auth.Session, spaceRef
return err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit, false); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit); err != nil {
return err
}

View File

@ -42,7 +42,7 @@ func (c *Controller) ExportProgress(ctx context.Context,
return ExportProgressOutput{}, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, false); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView); err != nil {
return ExportProgressOutput{}, err
}

View File

@ -19,22 +19,21 @@ import (
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"
)
/*
* Find finds a space.
*/
func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string) (*types.Space, error) {
func (c *Controller) Find(ctx context.Context, session *auth.Session, spaceRef string) (*SpaceOutput, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, true); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView); err != nil {
return nil, err
}
return space, nil
return GetSpaceOutput(ctx, c.publicAccess, space)
}

View File

@ -0,0 +1,40 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
func GetSpaceOutput(
ctx context.Context,
publicAccess publicaccess.Service,
space *types.Space,
) (*SpaceOutput, error) {
isPublic, err := publicAccess.Get(ctx, enum.PublicResourceTypeSpace, space.Path)
if err != nil {
return nil, fmt.Errorf("failed to get resource public access mode: %w", err)
}
return &SpaceOutput{
Space: *space,
IsPublic: isPublic,
}, nil
}

View File

@ -41,7 +41,9 @@ type ImportInput struct {
}
// Import creates new space and starts import of all repositories from the remote provider's space into it.
func (c *Controller) Import(ctx context.Context, session *auth.Session, in *ImportInput) (*types.Space, error) {
//
//nolint:gocognit
func (c *Controller) Import(ctx context.Context, session *auth.Session, in *ImportInput) (*SpaceOutput, error) {
parentSpace, err := c.getSpaceCheckAuthSpaceCreation(ctx, session, in.ParentRef)
if err != nil {
return nil, err
@ -67,6 +69,7 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
}
repoIDs := make([]int64, len(remoteRepositories))
repoIsPublicVals := make([]bool, len(remoteRepositories))
cloneURLs := make([]string, len(remoteRepositories))
repos := make([]*types.Repository, 0, len(remoteRepositories))
@ -83,12 +86,12 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
}
for i, remoteRepository := range remoteRepositories {
repo := remoteRepository.ToRepo(
repo, isPublic := remoteRepository.ToRepo(
space.ID,
space.Path,
remoteRepository.Identifier,
"",
&session.Principal,
c.publicResourceCreationEnabled,
)
err = c.repoStore.Create(ctx, repo)
@ -98,6 +101,7 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
repos = append(repos, repo)
repoIDs[i] = repo.ID
cloneURLs[i] = remoteRepository.CloneURL
repoIsPublicVals[i] = isPublic
}
jobGroupID := fmt.Sprintf("space-import-%d", space.ID)
@ -105,6 +109,7 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
jobGroupID,
provider,
repoIDs,
repoIsPublicVals,
cloneURLs,
in.Pipelines,
)
@ -124,14 +129,17 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionCreated,
paths.Parent(repo.Path),
audit.WithNewObject(repo),
audit.WithNewObject(audit.RepositoryObject{
Repository: *repo,
IsPublic: false, // in import we configure public access and create a new audit log.
}),
)
if err != nil {
log.Warn().Msgf("failed to insert audit log for import repository operation: %s", err)
}
}
return space, nil
return GetSpaceOutput(ctx, c.publicAccess, space)
}
func (c *Controller) sanitizeImportInput(in *ImportInput) error {

View File

@ -71,6 +71,8 @@ func (c *Controller) getSpaceCheckAuthRepoCreation(
// ImportRepositories imports repositories into an existing space. It ignores and continues on
// repo naming conflicts.
//
//nolint:gocognit
func (c *Controller) ImportRepositories(
ctx context.Context,
session *auth.Session,
@ -92,10 +94,25 @@ func (c *Controller) ImportRepositories(
return ImportRepositoriesOutput{}, usererror.BadRequestf("found no repositories at %s", in.ProviderSpace)
}
repoIDs := make([]int64, 0, len(remoteRepositories))
cloneURLs := make([]string, 0, len(remoteRepositories))
repos := make([]*types.Repository, 0, len(remoteRepositories))
duplicateRepos := make([]*types.Repository, 0, len(remoteRepositories))
repoIDs := make([]int64, 0, len(remoteRepositories))
repoIsPublicVals := make([]bool, 0, len(remoteRepositories))
cloneURLs := make([]string, 0, len(remoteRepositories))
for _, remoteRepository := range remoteRepositories {
repo, isPublic := remoteRepository.ToRepo(
space.ID,
space.Path,
remoteRepository.Identifier,
"",
&session.Principal,
)
repos = append(repos, repo)
repoIsPublicVals = append(repoIsPublicVals, isPublic)
cloneURLs = append(cloneURLs, remoteRepository.CloneURL)
}
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
// lock the space for update during repo creation to prevent racing conditions with space soft delete.
@ -109,26 +126,20 @@ func (c *Controller) ImportRepositories(
return fmt.Errorf("resource limit exceeded: %w", limiter.ErrMaxNumReposReached)
}
for _, remoteRepository := range remoteRepositories {
repo := remoteRepository.ToRepo(
space.ID,
remoteRepository.Identifier,
"",
&session.Principal,
c.publicResourceCreationEnabled,
)
for _, repo := range repos {
err = c.repoStore.Create(ctx, repo)
if errors.Is(err, store.ErrDuplicate) {
log.Ctx(ctx).Warn().Err(err).Msg("skipping duplicate repo")
duplicateRepos = append(duplicateRepos, repo)
l := len(repoIDs)
repoIsPublicVals = append(repoIsPublicVals[:l], repoIsPublicVals[l+1:]...)
cloneURLs = append(cloneURLs[:l], cloneURLs[l+1:]...)
continue
} else if err != nil {
return fmt.Errorf("failed to create repository in storage: %w", err)
}
repos = append(repos, repo)
repoIDs = append(repoIDs, repo.ID)
cloneURLs = append(cloneURLs, remoteRepository.CloneURL)
}
if len(repoIDs) == 0 {
return nil
@ -139,6 +150,7 @@ func (c *Controller) ImportRepositories(
jobGroupID,
provider,
repoIDs,
repoIsPublicVals,
cloneURLs,
in.Pipelines,
)
@ -158,7 +170,10 @@ func (c *Controller) ImportRepositories(
audit.NewResource(audit.ResourceTypeRepository, repo.Identifier),
audit.ActionCreated,
paths.Parent(repo.Path),
audit.WithNewObject(repo),
audit.WithNewObject(audit.RepositoryObject{
Repository: *repo,
IsPublic: false, // in import we configure public access and create a new audit log.
}),
)
if err != nil {
log.Warn().Msgf("failed to insert audit log for import repository operation: %s", err)

View File

@ -19,7 +19,9 @@ import (
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
repoCtrl "github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/store/database/dbtx"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
@ -30,7 +32,7 @@ func (c *Controller) ListRepositories(
session *auth.Session,
spaceRef string,
filter *types.RepoFilter,
) ([]*types.Repository, int64, error) {
) ([]*repoCtrl.RepositoryOutput, int64, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, 0, err
@ -43,7 +45,6 @@ func (c *Controller) ListRepositories(
space,
enum.ResourceTypeRepo,
enum.PermissionRepoView,
true,
); err != nil {
return nil, 0, err
}
@ -56,21 +57,39 @@ func (c *Controller) ListRepositoriesNoAuth(
ctx context.Context,
spaceID int64,
filter *types.RepoFilter,
) ([]*types.Repository, int64, error) {
count, err := c.repoStore.Count(ctx, spaceID, filter)
) ([]*repoCtrl.RepositoryOutput, int64, error) {
var repos []*types.Repository
var count int64
err := c.tx.WithTx(ctx, func(ctx context.Context) (err error) {
count, err = c.repoStore.Count(ctx, spaceID, filter)
if err != nil {
return fmt.Errorf("failed to count child repos: %w", err)
}
repos, err = c.repoStore.List(ctx, spaceID, filter)
if err != nil {
return fmt.Errorf("failed to list child repos: %w", err)
}
return nil
}, dbtx.TxDefaultReadOnly)
if err != nil {
return nil, 0, fmt.Errorf("failed to count child repos: %w", err)
return nil, 0, err
}
repos, err := c.repoStore.List(ctx, spaceID, filter)
if err != nil {
return nil, 0, fmt.Errorf("failed to list child repos: %w", err)
}
// backfill URLs
var reposOut []*repoCtrl.RepositoryOutput
for _, repo := range repos {
// backfill URLs
repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path)
repoOut, err := repoCtrl.GetRepoOutput(ctx, c.publicAccess, repo)
if err != nil {
return nil, 0, fmt.Errorf("failed to get repo %q output: %w", repo.Path, err)
}
reposOut = append(reposOut, repoOut)
}
return repos, count, nil
return reposOut, count, nil
}

View File

@ -30,7 +30,7 @@ func (c *Controller) ListSpaces(ctx context.Context,
session *auth.Session,
spaceRef string,
filter *types.SpaceFilter,
) ([]*types.Space, int64, error) {
) ([]*SpaceOutput, int64, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, 0, err
@ -43,10 +43,10 @@ func (c *Controller) ListSpaces(ctx context.Context,
space,
enum.ResourceTypeSpace,
enum.PermissionSpaceView,
true,
); err != nil {
return nil, 0, err
}
return c.ListSpacesNoAuth(ctx, space.ID, filter)
}
@ -55,7 +55,7 @@ func (c *Controller) ListSpacesNoAuth(
ctx context.Context,
spaceID int64,
filter *types.SpaceFilter,
) ([]*types.Space, int64, error) {
) ([]*SpaceOutput, int64, error) {
var spaces []*types.Space
var count int64
@ -76,5 +76,16 @@ func (c *Controller) ListSpacesNoAuth(
return nil, 0, err
}
return spaces, count, nil
// backfill public access mode
var spacesOut []*SpaceOutput
for _, space := range spaces {
spaceOut, err := GetSpaceOutput(ctx, c.publicAccess, space)
if err != nil {
return nil, 0, fmt.Errorf("failed to get space %q output: %w", space.Path, err)
}
spacesOut = append(spacesOut, spaceOut)
}
return spacesOut, count, nil
}

View File

@ -66,7 +66,7 @@ func (c *Controller) MembershipAdd(ctx context.Context,
return nil, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit, false); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit); err != nil {
return nil, err
}

View File

@ -35,7 +35,7 @@ func (c *Controller) MembershipDelete(ctx context.Context,
return err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit, false); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit); err != nil {
return err
}

View File

@ -36,7 +36,7 @@ func (c *Controller) MembershipList(ctx context.Context,
return nil, 0, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, false); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView); err != nil {
return nil, 0, err
}

View File

@ -58,7 +58,7 @@ func (c *Controller) MembershipUpdate(ctx context.Context,
return nil, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit, false); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit); err != nil {
return nil, err
}

View File

@ -49,13 +49,13 @@ func (c *Controller) Move(
session *auth.Session,
spaceRef string,
in *MoveInput,
) (*types.Space, error) {
) (*SpaceOutput, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit, false); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit); err != nil {
return nil, err
}
@ -65,7 +65,7 @@ func (c *Controller) Move(
// exit early if there are no changes
if !in.hasChanges(space) {
return space, nil
return GetSpaceOutput(ctx, c.publicAccess, space)
}
if err = c.moveInner(
@ -77,7 +77,7 @@ func (c *Controller) Move(
return nil, err
}
return space, nil
return GetSpaceOutput(ctx, c.publicAccess, space)
}
func (c *Controller) sanitizeMoveInput(in *MoveInput, isRoot bool) error {

View File

@ -43,12 +43,12 @@ func (c *Controller) Purge(
// authz will check the permission within the first existing parent since space was deleted.
// purge top level space is limited to admin only.
err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceDelete, false)
err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceDelete)
if err != nil {
return fmt.Errorf("failed to authorize on space purge: %w", err)
}
return c.PurgeNoAuth(ctx, session, space.ID, deletedAt)
return c.PurgeNoAuth(ctx, session, space)
}
// PurgeNoAuth purges the space - no authorization is verified.
@ -56,8 +56,7 @@ func (c *Controller) Purge(
func (c *Controller) PurgeNoAuth(
ctx context.Context,
session *auth.Session,
spaceID int64,
deletedAt int64,
space *types.Space,
) error {
// the max time we give a purge space to succeed
const timeout = 15 * time.Minute
@ -71,11 +70,11 @@ func (c *Controller) PurgeNoAuth(
var toBeDeletedRepos []*types.Repository
var err error
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
toBeDeletedRepos, err = c.purgeSpaceInnerInTx(ctx, spaceID, deletedAt)
toBeDeletedRepos, err = c.purgeSpaceInnerInTx(ctx, space.ID, *space.Deleted)
return err
})
if err != nil {
return fmt.Errorf("failed to purge space %d in a tnx: %w", spaceID, err)
return fmt.Errorf("failed to purge space %d in a tnx: %w", space.ID, err)
}
// permanently purge all repositories in the space and its subspaces after successful space purge tnx.

View File

@ -45,7 +45,7 @@ func (c *Controller) Restore(
spaceRef string,
deletedAt int64,
in *RestoreInput,
) (*types.Space, error) {
) (*SpaceOutput, error) {
if err := c.sanitizeRestoreInput(in); err != nil {
return nil, fmt.Errorf("failed to sanitize restore input: %w", err)
}
@ -56,7 +56,7 @@ func (c *Controller) Restore(
}
// check view permission on the original ref.
err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView, false)
err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceView)
if err != nil {
return nil, fmt.Errorf("failed to authorize on space restore: %w", err)
}
@ -74,7 +74,6 @@ func (c *Controller) Restore(
parentSpace,
enum.ResourceTypeSpace,
enum.PermissionSpaceEdit,
false,
); err != nil {
return nil, fmt.Errorf("authorization failed on space restore: %w", err)
}
@ -98,7 +97,11 @@ func (c *Controller) Restore(
return nil, fmt.Errorf("failed to restore space in a tnx: %w", err)
}
return space, nil
// restored spaces will be private since public access data has deleted upon deletion.
return &SpaceOutput{
Space: *space,
IsPublic: false,
}, nil
}
func (c *Controller) restoreSpaceInnerInTx(

View File

@ -47,7 +47,6 @@ func (c *Controller) SoftDelete(
session,
space,
enum.PermissionSpaceDelete,
false,
); err != nil {
return nil, fmt.Errorf("failed to check access: %w", err)
}
@ -62,9 +61,12 @@ func (c *Controller) SoftDeleteNoAuth(
session *auth.Session,
space *types.Space,
) (*SoftDeleteResponse, error) {
var softDelRes *SoftDeleteResponse
var err error
err := c.publicAccess.Delete(ctx, enum.PublicResourceTypeSpace, space.Path)
if err != nil {
return nil, fmt.Errorf("failed to delete public access for space: %w", err)
}
var softDelRes *SoftDeleteResponse
err = c.tx.WithTx(ctx, func(ctx context.Context) error {
softDelRes, err = c.softDeleteInnerInTx(ctx, session, space)
return err

View File

@ -29,28 +29,30 @@ import (
// UpdateInput is used for updating a space.
type UpdateInput struct {
Description *string `json:"description"`
IsPublic *bool `json:"is_public"`
}
func (in *UpdateInput) hasChanges(space *types.Space) bool {
return (in.Description != nil && *in.Description != space.Description) ||
(in.IsPublic != nil && *in.IsPublic != space.IsPublic)
return in.Description != nil && *in.Description != space.Description
}
// Update updates a space.
func (c *Controller) Update(ctx context.Context, session *auth.Session,
spaceRef string, in *UpdateInput) (*types.Space, error) {
func (c *Controller) Update(
ctx context.Context,
session *auth.Session,
spaceRef string,
in *UpdateInput,
) (*SpaceOutput, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit, false); err != nil {
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit); err != nil {
return nil, err
}
if !in.hasChanges(space) {
return space, nil
return GetSpaceOutput(ctx, c.publicAccess, space)
}
if err = c.sanitizeUpdateInput(in); err != nil {
@ -62,9 +64,6 @@ func (c *Controller) Update(ctx context.Context, session *auth.Session,
if in.Description != nil {
space.Description = *in.Description
}
if in.IsPublic != nil {
space.IsPublic = *in.IsPublic
}
return nil
})
@ -72,16 +71,10 @@ func (c *Controller) Update(ctx context.Context, session *auth.Session,
return nil, err
}
return space, nil
return GetSpaceOutput(ctx, c.publicAccess, space)
}
func (c *Controller) sanitizeUpdateInput(in *UpdateInput) error {
if in.IsPublic != nil {
if *in.IsPublic && !c.publicResourceCreationEnabled {
return errPublicSpaceCreationDisabled
}
}
if in.Description != nil {
*in.Description = strings.TrimSpace(*in.Description)
if err := check.Description(*in.Description); err != nil {

View File

@ -0,0 +1,82 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"context"
"fmt"
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/types/enum"
)
type UpdatePublicAccessInput struct {
IsPublic bool `json:"is_public"`
}
func (c *Controller) UpdatePublicAccess(ctx context.Context,
session *auth.Session,
spaceRef string,
in *UpdatePublicAccessInput,
) (*SpaceOutput, error) {
space, err := c.spaceStore.FindByRef(ctx, spaceRef)
if err != nil {
return nil, err
}
if err = apiauth.CheckSpace(ctx, c.authorizer, session, space, enum.PermissionSpaceEdit); err != nil {
return nil, err
}
parentPath, _, err := paths.DisectLeaf(space.Path)
if err != nil {
return nil, fmt.Errorf("failed to disect path %q: %w", space.Path, err)
}
isPublicAccessSupported, err := c.publicAccess.IsPublicAccessSupported(ctx, parentPath)
if err != nil {
return nil, fmt.Errorf(
"failed to check if public access is supported for parent space %q: %w",
parentPath,
err,
)
}
if in.IsPublic && !isPublicAccessSupported {
return nil, errPublicSpaceCreationDisabled
}
isPublic, err := c.publicAccess.Get(ctx, enum.PublicResourceTypeSpace, space.Path)
if err != nil {
return nil, fmt.Errorf("failed to check current public access status: %w", err)
}
// no op
if isPublic == in.IsPublic {
return &SpaceOutput{
Space: *space,
IsPublic: isPublic,
}, nil
}
if err = c.publicAccess.Set(ctx, enum.PublicResourceTypeSpace, space.Path, in.IsPublic); err != nil {
return nil, fmt.Errorf("failed to update space public access: %w", err)
}
return &SpaceOutput{
Space: *space,
IsPublic: in.IsPublic,
}, nil
}

View File

@ -20,6 +20,7 @@ import (
"github.com/harness/gitness/app/auth/authz"
"github.com/harness/gitness/app/services/exporter"
"github.com/harness/gitness/app/services/importer"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/sse"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
@ -42,11 +43,12 @@ func ProvideController(config *types.Config, tx dbtx.Transactor, urlProvider url
connectorStore store.ConnectorStore, templateStore store.TemplateStore,
spaceStore store.SpaceStore, repoStore store.RepoStore, principalStore store.PrincipalStore,
repoCtrl *repo.Controller, membershipStore store.MembershipStore, importer *importer.Repository,
exporter *exporter.Repository, limiter limiter.ResourceLimiter, auditService audit.Service,
exporter *exporter.Repository, limiter limiter.ResourceLimiter, publicAccess publicaccess.Service,
auditService audit.Service,
) *Controller {
return NewController(config, tx, urlProvider, sseStreamer, identifierCheck, authorizer,
spacePathStore, pipelineStore, secretStore,
connectorStore, templateStore,
spaceStore, repoStore, principalStore,
repoCtrl, membershipStore, importer, exporter, limiter, auditService)
repoCtrl, membershipStore, importer, exporter, limiter, publicAccess, auditService)
}

View File

@ -65,7 +65,6 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
session *auth.Session,
repoRef string,
reqPermission enum.Permission,
orPublic bool,
) (*types.Repository, error) {
if repoRef == "" {
return nil, usererror.BadRequest("A valid repository reference must be provided.")
@ -76,7 +75,7 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return nil, fmt.Errorf("failed to find repo: %w", err)
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission, orPublic); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("failed to verify authorization: %w", err)
}

View File

@ -31,7 +31,7 @@ func (c *Controller) Download(
repoRef string,
filePath string,
) (string, io.ReadCloser, error) {
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, true)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return "", nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}

View File

@ -42,7 +42,7 @@ func (c *Controller) Upload(ctx context.Context,
file io.Reader,
) (*Result, error) {
// Permission check to see if the user in request has access to the repo.
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView, false)
repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView)
if err != nil {
return nil, fmt.Errorf("failed to acquire access to repo: %w", err)
}

View File

@ -74,7 +74,7 @@ func (c *Controller) getRepoCheckAccess(ctx context.Context,
return nil, fmt.Errorf("failed to find repo: %w", err)
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission, false); err != nil {
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission); err != nil {
return nil, fmt.Errorf("failed to verify authorization: %w", err)
}

View File

@ -25,14 +25,14 @@ import (
func HandleFind(principalCtrl principal.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
principalID, err := request.GetPrincipalIDFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
principalInfo, err := principalCtrl.Find(ctx, principalID)
principalInfo, err := principalCtrl.Find(ctx, session, principalID)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -26,8 +26,9 @@ func HandleList(principalCtrl principal.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
principalFilter := request.ParsePrincipalFilter(r)
principalInfos, err := principalCtrl.List(ctx, principalFilter)
principalInfos, err := principalCtrl.List(ctx, session, principalFilter)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return

View File

@ -23,6 +23,7 @@ import (
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/url"
)
@ -51,7 +52,7 @@ func HandleGitInfoRefs(repoCtrl *repo.Controller, urlProvider url.Provider) http
w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
err = repoCtrl.GitInfoRefs(ctx, session, repoRef, service, gitProtocol, w)
if errors.Is(err, apiauth.ErrNotAuthenticated) {
if errors.Is(err, apiauth.ErrNotAuthorized) && auth.IsAnonymousSession(session) {
renderBasicAuth(w, urlProvider)
return
}

View File

@ -24,6 +24,7 @@ import (
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/types/enum"
@ -71,7 +72,7 @@ func HandleGitServicePack(
w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
err = repoCtrl.GitServicePack(ctx, session, repoRef, service, gitProtocol, dataReader, w)
if errors.Is(err, apiauth.ErrNotAuthenticated) {
if errors.Is(err, apiauth.ErrNotAuthorized) && auth.IsAnonymousSession(session) {
renderBasicAuth(w, urlProvider)
return
}

View File

@ -0,0 +1,52 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package repo
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/repo"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
func HandleUpdatePublicAccess(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(repo.UpdatePublicAccessInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid Request Body: %s.", err)
return
}
res, err := repoCtrl.UpdatePublicAccess(ctx, session, repoRef, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, res)
}
}

View File

@ -0,0 +1,52 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package space
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/app/api/controller/space"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
)
// HandleUpdatePublicAccess updates public access mode of an existing space.
func HandleUpdatePublicAccess(spaceCtrl *space.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
spaceRef, err := request.GetSpaceRefFromPath(r)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
in := new(space.UpdatePublicAccessInput)
err = json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequestf(ctx, w, "Invalid request body: %s.", err)
return
}
space, err := spaceCtrl.UpdatePublicAccess(ctx, session, spaceRef, in)
if err != nil {
render.TranslatedUserError(ctx, w, err)
return
}
render.JSON(w, http.StatusOK, space)
}
}

View File

@ -20,6 +20,7 @@ import (
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/auth/authn"
"github.com/rs/zerolog"
@ -29,50 +30,24 @@ import (
// Attempt returns an http.HandlerFunc middleware that authenticates
// the http.Request if authentication payload is available.
func Attempt(authenticator authn.Authenticator) func(http.Handler) http.Handler {
return performAuthentication(authenticator, false)
}
// Required returns an http.HandlerFunc middleware that authenticates
// the http.Request and fails the request if no auth data was available.
func Required(authenticator authn.Authenticator) func(http.Handler) http.Handler {
return performAuthentication(authenticator, true)
}
// performAuthentication returns an http.HandlerFunc middleware that authenticates
// the http.Request if authentication payload is available.
// Depending on whether it is required or not, the request will be failed.
func performAuthentication(
authenticator authn.Authenticator,
required bool,
) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := hlog.FromRequest(r)
session, err := authenticator.Authenticate(r)
if err != nil {
if !errors.Is(err, authn.ErrNoAuthData) {
// log error to help with investigating any auth related errors
log.Warn().Err(err).Msg("authentication failed")
}
if err != nil && !errors.Is(err, authn.ErrNoAuthData) {
log.Debug().Err(err).Msg("authentication failed")
if required {
render.Unauthorized(ctx, w)
return
}
// if there was no (valid) auth data in the request, then continue without session
next.ServeHTTP(w, r)
render.Unauthorized(ctx, w)
return
}
if session == nil {
// when err == nil session should never be nil!
log.Error().Msg("auth session is nil eventhough the authenticator didn't return any error!")
render.InternalError(ctx, w)
return
if errors.Is(err, authn.ErrNoAuthData) {
log.Info().Msg("No authentication data found, continue as anonymous")
session = &auth.Session{
Principal: auth.AnonymousPrincipal,
}
}
// Update the logging context and inject principal in context

View File

@ -200,6 +200,11 @@ type restoreRequest struct {
repo.RestoreInput
}
type updateRepoPublicAccessRequest struct {
repoRequest
repo.UpdatePublicAccessInput
}
type securitySettingsRequest struct {
repoRequest
reposettings.SecuritySettings
@ -605,7 +610,7 @@ func repoOperations(reflector *openapi3.Reflector) {
createRepository.WithMapOfAnything(map[string]interface{}{"operationId": "createRepository"})
createRepository.WithParameters(queryParameterSpacePath)
_ = reflector.SetRequest(&createRepository, new(createRepositoryRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&createRepository, new(types.Repository), http.StatusCreated)
_ = reflector.SetJSONResponse(&createRepository, new(repo.RepositoryOutput), http.StatusCreated)
_ = reflector.SetJSONResponse(&createRepository, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&createRepository, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&createRepository, new(usererror.Error), http.StatusUnauthorized)
@ -617,7 +622,7 @@ func repoOperations(reflector *openapi3.Reflector) {
importRepository.WithMapOfAnything(map[string]interface{}{"operationId": "importRepository"})
importRepository.WithParameters(queryParameterSpacePath)
_ = reflector.SetRequest(&importRepository, &struct{ repo.ImportInput }{}, http.MethodPost)
_ = reflector.SetJSONResponse(&importRepository, new(types.Repository), http.StatusCreated)
_ = reflector.SetJSONResponse(&importRepository, new(repo.RepositoryOutput), http.StatusCreated)
_ = reflector.SetJSONResponse(&importRepository, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&importRepository, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&importRepository, new(usererror.Error), http.StatusUnauthorized)
@ -628,7 +633,7 @@ func repoOperations(reflector *openapi3.Reflector) {
opFind.WithTags("repository")
opFind.WithMapOfAnything(map[string]interface{}{"operationId": "findRepository"})
_ = reflector.SetRequest(&opFind, new(repoRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opFind, new(types.Repository), http.StatusOK)
_ = reflector.SetJSONResponse(&opFind, new(repo.RepositoryOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opFind, new(usererror.Error), http.StatusForbidden)
@ -639,7 +644,7 @@ func repoOperations(reflector *openapi3.Reflector) {
opUpdate.WithTags("repository")
opUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updateRepository"})
_ = reflector.SetRequest(&opUpdate, new(updateRepoRequest), http.MethodPatch)
_ = reflector.SetJSONResponse(&opUpdate, new(types.Repository), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdate, new(repo.RepositoryOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusUnauthorized)
@ -675,7 +680,7 @@ func repoOperations(reflector *openapi3.Reflector) {
opRestore.WithMapOfAnything(map[string]interface{}{"operationId": "restoreRepository"})
opRestore.WithParameters(queryParameterDeletedAt)
_ = reflector.SetRequest(&opRestore, new(restoreRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&opRestore, new(types.Repository), http.StatusOK)
_ = reflector.SetJSONResponse(&opRestore, new(repo.RepositoryOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opRestore, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opRestore, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRestore, new(usererror.Error), http.StatusUnauthorized)
@ -687,13 +692,28 @@ func repoOperations(reflector *openapi3.Reflector) {
opMove.WithTags("repository")
opMove.WithMapOfAnything(map[string]interface{}{"operationId": "moveRepository"})
_ = reflector.SetRequest(&opMove, new(moveRepoRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&opMove, new(types.Repository), http.StatusOK)
_ = reflector.SetJSONResponse(&opMove, new(repo.RepositoryOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opMove, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opMove, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opMove, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opMove, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodPost, "/repos/{repo_ref}/move", opMove)
opUpdatePublicAccess := openapi3.Operation{}
opUpdatePublicAccess.WithTags("repository")
opUpdatePublicAccess.WithMapOfAnything(
map[string]interface{}{"operationId": "updatePublicAccess"})
_ = reflector.SetRequest(
&opUpdatePublicAccess, new(updateRepoPublicAccessRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(repo.RepositoryOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(
http.MethodPost, "/repos/{repo_ref}/public-access", opUpdatePublicAccess)
opServiceAccounts := openapi3.Operation{}
opServiceAccounts.WithTags("repository")
opServiceAccounts.WithMapOfAnything(map[string]interface{}{"operationId": "listRepositoryServiceAccounts"})

View File

@ -40,6 +40,10 @@ type updateSpaceRequest struct {
space.UpdateInput
}
type updateSpacePublicAccessRequest struct {
spaceRequest
space.UpdatePublicAccessInput
}
type moveSpaceRequest struct {
spaceRequest
space.MoveInput
@ -173,7 +177,7 @@ func spaceOperations(reflector *openapi3.Reflector) {
opCreate.WithTags("space")
opCreate.WithMapOfAnything(map[string]interface{}{"operationId": "createSpace"})
_ = reflector.SetRequest(&opCreate, new(createSpaceRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&opCreate, new(types.Space), http.StatusCreated)
_ = reflector.SetJSONResponse(&opCreate, new(space.SpaceOutput), http.StatusCreated)
_ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opCreate, new(usererror.Error), http.StatusUnauthorized)
@ -184,7 +188,7 @@ func spaceOperations(reflector *openapi3.Reflector) {
opImport.WithTags("space")
opImport.WithMapOfAnything(map[string]interface{}{"operationId": "importSpace"})
_ = reflector.SetRequest(&opImport, &struct{ space.ImportInput }{}, http.MethodPost)
_ = reflector.SetJSONResponse(&opImport, new(types.Space), http.StatusCreated)
_ = reflector.SetJSONResponse(&opImport, new(space.SpaceOutput), http.StatusCreated)
_ = reflector.SetJSONResponse(&opImport, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opImport, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opImport, new(usererror.Error), http.StatusUnauthorized)
@ -228,7 +232,7 @@ func spaceOperations(reflector *openapi3.Reflector) {
opGet.WithTags("space")
opGet.WithMapOfAnything(map[string]interface{}{"operationId": "getSpace"})
_ = reflector.SetRequest(&opGet, new(spaceRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opGet, new(types.Space), http.StatusOK)
_ = reflector.SetJSONResponse(&opGet, new(space.SpaceOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opGet, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opGet, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opGet, new(usererror.Error), http.StatusForbidden)
@ -239,7 +243,7 @@ func spaceOperations(reflector *openapi3.Reflector) {
opUpdate.WithTags("space")
opUpdate.WithMapOfAnything(map[string]interface{}{"operationId": "updateSpace"})
_ = reflector.SetRequest(&opUpdate, new(updateSpaceRequest), http.MethodPatch)
_ = reflector.SetJSONResponse(&opUpdate, new(types.Space), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdate, new(space.SpaceOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusUnauthorized)
@ -247,6 +251,21 @@ func spaceOperations(reflector *openapi3.Reflector) {
_ = reflector.SetJSONResponse(&opUpdate, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(http.MethodPatch, "/spaces/{space_ref}", opUpdate)
opUpdatePublicAccess := openapi3.Operation{}
opUpdatePublicAccess.WithTags("space")
opUpdatePublicAccess.WithMapOfAnything(
map[string]interface{}{"operationId": "updatePublicAccess"})
_ = reflector.SetRequest(
&opUpdatePublicAccess, new(updateSpacePublicAccessRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(space.SpaceOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusForbidden)
_ = reflector.SetJSONResponse(&opUpdatePublicAccess, new(usererror.Error), http.StatusNotFound)
_ = reflector.Spec.AddOperation(
http.MethodPost, "/spaces/{space_ref}/public-access", opUpdatePublicAccess)
opDelete := openapi3.Operation{}
opDelete.WithTags("space")
opDelete.WithMapOfAnything(map[string]interface{}{"operationId": "deleteSpace"})
@ -275,7 +294,7 @@ func spaceOperations(reflector *openapi3.Reflector) {
opRestore.WithMapOfAnything(map[string]interface{}{"operationId": "restoreSpace"})
opRestore.WithParameters(queryParameterDeletedAt)
_ = reflector.SetRequest(&opRestore, new(restoreSpaceRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&opRestore, new(types.Space), http.StatusOK)
_ = reflector.SetJSONResponse(&opRestore, new(space.SpaceOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opRestore, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opRestore, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opRestore, new(usererror.Error), http.StatusUnauthorized)
@ -287,7 +306,7 @@ func spaceOperations(reflector *openapi3.Reflector) {
opMove.WithTags("space")
opMove.WithMapOfAnything(map[string]interface{}{"operationId": "moveSpace"})
_ = reflector.SetRequest(&opMove, new(moveSpaceRequest), http.MethodPost)
_ = reflector.SetJSONResponse(&opMove, new(types.Space), http.StatusOK)
_ = reflector.SetJSONResponse(&opMove, new(space.SpaceOutput), http.StatusOK)
_ = reflector.SetJSONResponse(&opMove, new(usererror.Error), http.StatusBadRequest)
_ = reflector.SetJSONResponse(&opMove, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opMove, new(usererror.Error), http.StatusUnauthorized)
@ -301,7 +320,7 @@ func spaceOperations(reflector *openapi3.Reflector) {
opSpaces.WithParameters(queryParameterQuerySpace, queryParameterSortSpace, queryParameterOrder,
queryParameterPage, queryParameterLimit)
_ = reflector.SetRequest(&opSpaces, new(spaceRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opSpaces, []types.Space{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opSpaces, []space.SpaceOutput{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opSpaces, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opSpaces, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opSpaces, new(usererror.Error), http.StatusForbidden)

View File

@ -21,6 +21,7 @@ import (
apiauth "github.com/harness/gitness/app/api/auth"
"github.com/harness/gitness/app/api/controller/limiter"
"github.com/harness/gitness/app/services/codeowners"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/services/webhook"
"github.com/harness/gitness/blob"
"github.com/harness/gitness/errors"
@ -53,8 +54,6 @@ func Translate(ctx context.Context, err error) *Error {
return rError
// api auth errors
case errors.Is(err, apiauth.ErrNotAuthenticated):
return ErrUnauthorized
case errors.Is(err, apiauth.ErrNotAuthorized):
return ErrForbidden
@ -113,6 +112,10 @@ func Translate(ctx context.Context, err error) *Error {
case errors.As(err, &lockError):
return errorFromLockError(lockError)
// public access errors
case errors.Is(err, publicaccess.ErrPublicAccessNotAllowed):
return BadRequestf("Public access on resources is not allowed.")
// unknown error
default:
log.Ctx(ctx).Warn().Err(err).Msgf("Unable to translate error - returning Internal Error.")

32
app/auth/anonymous.go Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package auth
import (
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
// AnonymousPrincipal is an in-memory principal for users with no auth data.
// Authorizer is in charge of handling anonymous access.
var AnonymousPrincipal = types.Principal{
ID: -1,
UID: types.AnonymousPrincipalUID,
Type: enum.PrincipalTypeUser,
}
func IsAnonymousSession(session *Session) bool {
return session != nil && session.Principal.UID == types.AnonymousPrincipalUID
}

View File

@ -20,6 +20,7 @@ import (
"github.com/harness/gitness/app/auth"
"github.com/harness/gitness/app/paths"
"github.com/harness/gitness/app/services/publicaccess"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -32,15 +33,18 @@ var _ Authorizer = (*MembershipAuthorizer)(nil)
type MembershipAuthorizer struct {
permissionCache PermissionCache
spaceStore store.SpaceStore
publicAccess publicaccess.Service
}
func NewMembershipAuthorizer(
permissionCache PermissionCache,
spaceStore store.SpaceStore,
publicAccess publicaccess.Service,
) *MembershipAuthorizer {
return &MembershipAuthorizer{
permissionCache: permissionCache,
spaceStore: spaceStore,
publicAccess: publicAccess,
}
}
@ -51,7 +55,15 @@ func (a *MembershipAuthorizer) Check(
resource *types.Resource,
permission enum.Permission,
) (bool, error) {
// public access - not expected to come here as of now (have to refactor that part)
publicAccessAllowed, err := a.CheckPublicAccess(ctx, scope, resource, permission)
if err != nil {
return false, fmt.Errorf("failed to check public access: %w", err)
}
if publicAccessAllowed {
return true, nil
}
if session == nil {
log.Ctx(ctx).Warn().Msgf(
"public access request for %s in scope %#v got to authorizer",
@ -102,9 +114,14 @@ func (a *MembershipAuthorizer) Check(
spacePath = scope.SpacePath
case enum.ResourceTypeUser:
// a user is allowed to view / edit themselves
// a user is allowed to edit themselves
if resource.Identifier == session.Principal.UID &&
(permission == enum.PermissionUserView || permission == enum.PermissionUserEdit) {
permission == enum.PermissionUserEdit {
return true, nil
}
// user can see all other users in the system.
if permission == enum.PermissionUserView {
return true, nil
}

Some files were not shown because too many files have changed in this diff Show More