diff --git a/app/api/auth/auth.go b/app/api/auth/auth.go index 3d20fd2f88..8569e312a2 100644 --- a/app/api/auth/auth.go +++ b/app/api/auth/auth.go @@ -42,10 +42,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 +100,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 diff --git a/app/api/auth/pipeline.go b/app/api/auth/pipeline.go index 73a2ab561c..6ca1754fc5 100644 --- a/app/api/auth/pipeline.go +++ b/app/api/auth/pipeline.go @@ -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{ diff --git a/app/api/auth/repo.go b/app/api/auth/repo.go index dd89a10536..bca4cdbfb3 100644 --- a/app/api/auth/repo.go +++ b/app/api/auth/repo.go @@ -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) } diff --git a/app/api/auth/space.go b/app/api/auth/space.go index 71a496bb78..398676d728 100644 --- a/app/api/auth/space.go +++ b/app/api/auth/space.go @@ -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, diff --git a/app/api/controller/check/controller.go b/app/api/controller/check/controller.go index 93224d3e78..f13887b791 100644 --- a/app/api/controller/check/controller.go +++ b/app/api/controller/check/controller.go @@ -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) } diff --git a/app/api/controller/principal/controller.go b/app/api/controller/principal/controller.go index 0b351bed3d..2ad655f8cf 100644 --- a/app/api/controller/principal/controller.go +++ b/app/api/controller/principal/controller.go @@ -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, } } diff --git a/app/api/controller/principal/find.go b/app/api/controller/principal/find.go index 301bdde288..8c9443efee 100644 --- a/app/api/controller/principal/find.go +++ b/app/api/controller/principal/find.go @@ -17,10 +17,26 @@ package principal import ( "context" + 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" ) -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) { + if err := apiauth.Check( + ctx, + c.authorizer, + session, + &types.Scope{}, + &types.Resource{ + Type: enum.ResourceTypeUser, + }, + enum.PermissionUserView, + ); err != nil { + return nil, err + } + principal, err := c.principalStore.Find(ctx, principalID) if err != nil { return nil, err diff --git a/app/api/controller/principal/interface.go b/app/api/controller/principal/interface.go index 3a9404f1cb..67aae796b0 100644 --- a/app/api/controller/principal/interface.go +++ b/app/api/controller/principal/interface.go @@ -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) } diff --git a/app/api/controller/principal/search.go b/app/api/controller/principal/search.go index 48ed8dd976..6c20a04caa 100644 --- a/app/api/controller/principal/search.go +++ b/app/api/controller/principal/search.go @@ -17,11 +17,27 @@ package principal import ( "context" + 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" ) -func (c controller) List(ctx context.Context, opts *types.PrincipalFilter) ( +func (c controller) List(ctx context.Context, session *auth.Session, opts *types.PrincipalFilter) ( []*types.PrincipalInfo, error) { + 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 diff --git a/app/api/controller/principal/wire.go b/app/api/controller/principal/wire.go index 15d8f4dbd6..1c91706995 100644 --- a/app/api/controller/principal/wire.go +++ b/app/api/controller/principal/wire.go @@ -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) } diff --git a/app/api/controller/pullreq/controller.go b/app/api/controller/pullreq/controller.go index a23211aa22..1dc6c310e0 100644 --- a/app/api/controller/pullreq/controller.go +++ b/app/api/controller/pullreq/controller.go @@ -153,7 +153,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) } diff --git a/app/api/controller/pullreq/pr_create.go b/app/api/controller/pullreq/pr_create.go index af8f271a01..fc4fdb344c 100644 --- a/app/api/controller/pullreq/pr_create.go +++ b/app/api/controller/pullreq/pr_create.go @@ -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) } diff --git a/app/api/controller/pullreq/pr_state.go b/app/api/controller/pullreq/pr_state.go index 7705c77412..46b4ae1a78 100644 --- a/app/api/controller/pullreq/pr_state.go +++ b/app/api/controller/pullreq/pr_state.go @@ -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) } } diff --git a/app/api/controller/pullreq/pr_update.go b/app/api/controller/pullreq/pr_update.go index 06a8f44f5e..1f16fb2cdf 100644 --- a/app/api/controller/pullreq/pr_update.go +++ b/app/api/controller/pullreq/pr_update.go @@ -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) } } diff --git a/app/api/controller/pullreq/reviewer_add.go b/app/api/controller/pullreq/reviewer_add.go index cb3c0c0a41..bcd93c74d2 100644 --- a/app/api/controller/pullreq/reviewer_add.go +++ b/app/api/controller/pullreq/reviewer_add.go @@ -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.") } diff --git a/app/api/controller/pullreq/reviewer_delete.go b/app/api/controller/pullreq/reviewer_delete.go index f31173fe1e..105a8eb1af 100644 --- a/app/api/controller/pullreq/reviewer_delete.go +++ b/app/api/controller/pullreq/reviewer_delete.go @@ -25,7 +25,7 @@ import ( // ReviewerDelete deletes reviewer from the reviewerlist for the given PR. func (c *Controller) ReviewerDelete(ctx context.Context, session *auth.Session, repoRef string, prNum, reviewerID int64) error { - repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoEdit) + repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView) if err != nil { return fmt.Errorf("failed to acquire access to repo: %w", err) } diff --git a/app/api/controller/repo/archive.go b/app/api/controller/repo/archive.go index 558b8740b2..256ae8927f 100644 --- a/app/api/controller/repo/archive.go +++ b/app/api/controller/repo/archive.go @@ -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 } diff --git a/app/api/controller/repo/blame.go b/app/api/controller/repo/blame.go index 1400dc354b..585f3ab21a 100644 --- a/app/api/controller/repo/blame.go +++ b/app/api/controller/repo/blame.go @@ -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 } diff --git a/app/api/controller/repo/codeowner_validate.go b/app/api/controller/repo/codeowner_validate.go index 5fc52c1a02..8028f44136 100644 --- a/app/api/controller/repo/codeowner_validate.go +++ b/app/api/controller/repo/codeowner_validate.go @@ -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 } diff --git a/app/api/controller/repo/commit.go b/app/api/controller/repo/commit.go index 9e3718c63f..102c1bf3f4 100644 --- a/app/api/controller/repo/commit.go +++ b/app/api/controller/repo/commit.go @@ -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 } diff --git a/app/api/controller/repo/content_get.go b/app/api/controller/repo/content_get.go index 2acbfc9051..576a93d4c7 100644 --- a/app/api/controller/repo/content_get.go +++ b/app/api/controller/repo/content_get.go @@ -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 } diff --git a/app/api/controller/repo/content_paths_details.go b/app/api/controller/repo/content_paths_details.go index 302613fcef..d75d4e6783 100644 --- a/app/api/controller/repo/content_paths_details.go +++ b/app/api/controller/repo/content_paths_details.go @@ -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 } diff --git a/app/api/controller/repo/controller.go b/app/api/controller/repo/controller.go index ff67370aba..0aa90dcccf 100644 --- a/app/api/controller/repo/controller.go +++ b/app/api/controller/repo/controller.go @@ -16,13 +16,13 @@ package repo import ( "context" + "encoding/json" "fmt" "strconv" "strings" apiauth "github.com/harness/gitness/app/api/auth" "github.com/harness/gitness/app/api/controller/limiter" - "github.com/harness/gitness/app/api/usererror" "github.com/harness/gitness/app/auth" "github.com/harness/gitness/app/auth/authz" repoevents "github.com/harness/gitness/app/events/repo" @@ -31,6 +31,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,9 +44,23 @@ import ( "github.com/harness/gitness/types/enum" ) -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 @@ -73,6 +88,7 @@ type Controller struct { mtxManager lock.MutexManager identifierCheck check.RepoIdentifier repoCheck Check + publicAccess publicaccess.Service } func NewController( @@ -99,6 +115,7 @@ func NewController( mtxManager lock.MutexManager, identifierCheck check.RepoIdentifier, repoCheck Check, + publicAccess publicaccess.Service, ) *Controller { return &Controller{ defaultBranch: config.Git.DefaultBranch, @@ -125,6 +142,7 @@ func NewController( mtxManager: mtxManager, identifierCheck: identifierCheck, repoCheck: repoCheck, + publicAccess: publicAccess, } } @@ -147,7 +165,6 @@ func (c *Controller) getRepoCheckAccess( session *auth.Session, repoRef string, reqPermission enum.Permission, - orPublic bool, ) (*types.Repository, error) { return GetRepoCheckAccess( ctx, @@ -156,7 +173,6 @@ func (c *Controller) getRepoCheckAccess( session, repoRef, reqPermission, - orPublic, ) } diff --git a/app/api/controller/repo/create.go b/app/api/controller/repo/create.go index b62099abba..89c7fba74f 100644 --- a/app/api/controller/repo/create.go +++ b/app/api/controller/repo/create.go @@ -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) } @@ -77,6 +77,11 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea 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 +94,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 +101,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 +108,44 @@ 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.PurgeNoAuth(ctx, session, repo); dErr != nil { + log.Ctx(ctx).Warn().Err(dErr).Msg("failed to purge repo for cleanup") + } + return nil, fmt.Errorf("failed to set repo public access: %w", err) + } + + // backfil GitURL + repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path) + + repoData := &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(repoData), ) 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 +154,7 @@ func (c *Controller) Create(ctx context.Context, session *auth.Session, in *Crea } } - return repo, nil + return repoData, nil } func (c *Controller) getSpaceCheckAuthRepoCreation( @@ -166,7 +175,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 +189,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 } diff --git a/app/api/controller/repo/create_branch.go b/app/api/controller/repo/create_branch.go index 19e42d1832..d1581de45c 100644 --- a/app/api/controller/repo/create_branch.go +++ b/app/api/controller/repo/create_branch.go @@ -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 } diff --git a/app/api/controller/repo/create_commit_tag.go b/app/api/controller/repo/create_commit_tag.go index 672b057a74..c0584e59c8 100644 --- a/app/api/controller/repo/create_commit_tag.go +++ b/app/api/controller/repo/create_commit_tag.go @@ -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 } diff --git a/app/api/controller/repo/default_branch.go b/app/api/controller/repo/default_branch.go index d2d42b2d4b..ed0cbd7b3b 100644 --- a/app/api/controller/repo/default_branch.go +++ b/app/api/controller/repo/default_branch.go @@ -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 @@ -114,5 +115,5 @@ func (c *Controller) UpdateDefaultBranch( NewName: repo.DefaultBranch, }) - return repo, nil + return GetRepoOutput(ctx, c.publicAccess, repo) } diff --git a/app/api/controller/repo/delete_branch.go b/app/api/controller/repo/delete_branch.go index 0d7eaafb08..3f064f9540 100644 --- a/app/api/controller/repo/delete_branch.go +++ b/app/api/controller/repo/delete_branch.go @@ -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 } diff --git a/app/api/controller/repo/delete_tag.go b/app/api/controller/repo/delete_tag.go index 25dbc86a5c..9912e64085 100644 --- a/app/api/controller/repo/delete_tag.go +++ b/app/api/controller/repo/delete_tag.go @@ -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 } diff --git a/app/api/controller/repo/diff.go b/app/api/controller/repo/diff.go index 6f5344e02f..4bfb72e439 100644 --- a/app/api/controller/repo/diff.go +++ b/app/api/controller/repo/diff.go @@ -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 } diff --git a/app/api/controller/repo/find.go b/app/api/controller/repo/find.go index 21d890342b..6a08a93461 100644 --- a/app/api/controller/repo/find.go +++ b/app/api/controller/repo/find.go @@ -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) } diff --git a/app/api/controller/repo/get_branch.go b/app/api/controller/repo/get_branch.go index 7b792455e9..a1a06521fb 100644 --- a/app/api/controller/repo/get_branch.go +++ b/app/api/controller/repo/get_branch.go @@ -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 } diff --git a/app/api/controller/repo/get_commit.go b/app/api/controller/repo/get_commit.go index 9814f9b6fe..ede7f7c4a8 100644 --- a/app/api/controller/repo/get_commit.go +++ b/app/api/controller/repo/get_commit.go @@ -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 } diff --git a/app/api/controller/repo/get_commit_divergences.go b/app/api/controller/repo/get_commit_divergences.go index ff8d87e5f1..dad31b230e 100644 --- a/app/api/controller/repo/get_commit_divergences.go +++ b/app/api/controller/repo/get_commit_divergences.go @@ -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 } diff --git a/app/api/controller/repo/git_info_refs.go b/app/api/controller/repo/git_info_refs.go index 8d7ad2fbdc..3d9fbe09fd 100644 --- a/app/api/controller/repo/git_info_refs.go +++ b/app/api/controller/repo/git_info_refs.go @@ -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) } diff --git a/app/api/controller/repo/git_service_pack.go b/app/api/controller/repo/git_service_pack.go index 8d2988e65c..f4a436c480 100644 --- a/app/api/controller/repo/git_service_pack.go +++ b/app/api/controller/repo/git_service_pack.go @@ -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) } diff --git a/app/api/controller/repo/helper.go b/app/api/controller/repo/helper.go index 5d941069a6..97cce0e484 100644 --- a/app/api/controller/repo/helper.go +++ b/app/api/controller/repo/helper.go @@ -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 +} diff --git a/app/api/controller/repo/import.go b/app/api/controller/repo/import.go index 3e2f2ade30..f5a6541409 100644 --- a/app/api/controller/repo/import.go +++ b/app/api/controller/repo/import.go @@ -23,7 +23,7 @@ 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/harness/gitness/types/enum" "github.com/rs/zerolog/log" ) @@ -42,7 +42,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 +52,37 @@ 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, + in.Identifier, + in.Description, + &session.Principal, + c.publicResourceCreationEnabled, + ) + + cleanUpPublicAccess := func() { + err := c.publicAccess.Set(ctx, enum.PublicResourceTypeRepo, repo.Path, false) + if err != nil { + log.Ctx(ctx).Warn().Err(err).Msg("failed to cleanup repo public access") + } + } + + err = c.publicAccess.Set(ctx, enum.PublicResourceTypeRepo, repo.Path, isPublic) + if err != nil { + cleanUpPublicAccess() + return nil, fmt.Errorf("failed to set repo public access: %w", err) + } + 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 { @@ -85,6 +98,7 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo provider, repo, remoteRepository.CloneURL, + isPublic, in.Pipelines, ) if err != nil { @@ -94,23 +108,29 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo return nil }) if err != nil { + cleanUpPublicAccess() return nil, err } repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path) + repoData := &RepositoryOutput{ + Repository: *repo, + IsPublic: isPublic && c.publicResourceCreationEnabled, + } + err = c.auditService.Log(ctx, session.Principal, audit.NewResource(audit.ResourceTypeRepository, repo.Identifier), audit.ActionCreated, paths.Parent(repo.Path), - audit.WithNewObject(repo), + audit.WithNewObject(repoData), ) if err != nil { log.Warn().Msgf("failed to insert audit log for import repository operation: %s", err) } - return repo, nil + return repoData, nil } func (c *Controller) sanitizeImportInput(in *ImportInput) error { diff --git a/app/api/controller/repo/import_progress.go b/app/api/controller/repo/import_progress.go index ec999f6179..4156e16fec 100644 --- a/app/api/controller/repo/import_progress.go +++ b/app/api/controller/repo/import_progress.go @@ -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 } diff --git a/app/api/controller/repo/list_branches.go b/app/api/controller/repo/list_branches.go index 7c369e9ba8..a464da158f 100644 --- a/app/api/controller/repo/list_branches.go +++ b/app/api/controller/repo/list_branches.go @@ -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 } diff --git a/app/api/controller/repo/list_commit_tags.go b/app/api/controller/repo/list_commit_tags.go index c460c42069..a4e0320e14 100644 --- a/app/api/controller/repo/list_commit_tags.go +++ b/app/api/controller/repo/list_commit_tags.go @@ -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 } diff --git a/app/api/controller/repo/list_commits.go b/app/api/controller/repo/list_commits.go index 3e9ec83b81..44a5f82bdf 100644 --- a/app/api/controller/repo/list_commits.go +++ b/app/api/controller/repo/list_commits.go @@ -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 } diff --git a/app/api/controller/repo/list_paths.go b/app/api/controller/repo/list_paths.go index 0a388a7497..35cd827c41 100644 --- a/app/api/controller/repo/list_paths.go +++ b/app/api/controller/repo/list_paths.go @@ -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 } diff --git a/app/api/controller/repo/merge_check.go b/app/api/controller/repo/merge_check.go index 7f5021b4e5..2cd18d4962 100644 --- a/app/api/controller/repo/merge_check.go +++ b/app/api/controller/repo/merge_check.go @@ -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 } diff --git a/app/api/controller/repo/move.go b/app/api/controller/repo/move.go index 099da66d8e..12958ae052 100644 --- a/app/api/controller/repo/move.go +++ b/app/api/controller/repo/move.go @@ -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,12 +62,12 @@ 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) } repo, err = c.repoStore.UpdateOptLock(ctx, repo, func(r *types.Repository) error { @@ -82,7 +82,7 @@ func (c *Controller) Move(ctx context.Context, repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path) - return repo, nil + return GetRepoOutput(ctx, c.publicAccess, repo) } func (c *Controller) sanitizeMoveInput(in *MoveInput) error { diff --git a/app/api/controller/repo/pipeline_generate.go b/app/api/controller/repo/pipeline_generate.go index 97fe4c3b97..65142dad3c 100644 --- a/app/api/controller/repo/pipeline_generate.go +++ b/app/api/controller/repo/pipeline_generate.go @@ -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 } diff --git a/app/api/controller/repo/purge.go b/app/api/controller/repo/purge.go index 09a07e6db0..34e4f1b685 100644 --- a/app/api/controller/repo/purge.go +++ b/app/api/controller/repo/purge.go @@ -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 } diff --git a/app/api/controller/repo/raw.go b/app/api/controller/repo/raw.go index ab3ec4797d..8cec18d0e6 100644 --- a/app/api/controller/repo/raw.go +++ b/app/api/controller/repo/raw.go @@ -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 } diff --git a/app/api/controller/repo/restore.go b/app/api/controller/repo/restore.go index cc80ac5867..38317b6564 100644 --- a/app/api/controller/repo/restore.go +++ b/app/api/controller/repo/restore.go @@ -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 is deleted upon deletion. + return &RepositoryOutput{ + Repository: *repo, + IsPublic: false, + }, nil } diff --git a/app/api/controller/repo/rule_create.go b/app/api/controller/repo/rule_create.go index 4a85820af2..0330eda908 100644 --- a/app/api/controller/repo/rule_create.go +++ b/app/api/controller/repo/rule_create.go @@ -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 } diff --git a/app/api/controller/repo/rule_delete.go b/app/api/controller/repo/rule_delete.go index cb561b56d7..2d839c5775 100644 --- a/app/api/controller/repo/rule_delete.go +++ b/app/api/controller/repo/rule_delete.go @@ -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 } diff --git a/app/api/controller/repo/rule_find.go b/app/api/controller/repo/rule_find.go index bedef7ec20..53d3949641 100644 --- a/app/api/controller/repo/rule_find.go +++ b/app/api/controller/repo/rule_find.go @@ -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 } diff --git a/app/api/controller/repo/rule_list.go b/app/api/controller/repo/rule_list.go index faef00d075..36a17652b3 100644 --- a/app/api/controller/repo/rule_list.go +++ b/app/api/controller/repo/rule_list.go @@ -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 } diff --git a/app/api/controller/repo/rule_update.go b/app/api/controller/repo/rule_update.go index dc4011781d..18632a262b 100644 --- a/app/api/controller/repo/rule_update.go +++ b/app/api/controller/repo/rule_update.go @@ -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 } diff --git a/app/api/controller/repo/soft_delete.go b/app/api/controller/repo/soft_delete.go index 6fd09c87ad..51673a2716 100644 --- a/app/api/controller/repo/soft_delete.go +++ b/app/api/controller/repo/soft_delete.go @@ -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) } @@ -88,8 +88,13 @@ func (c *Controller) SoftDeleteNoAuth( return c.PurgeNoAuth(ctx, session, repo) } - err := c.repoStore.SoftDelete(ctx, repo, deletedAt) + // unset repo public access regardless if it's public/private for simplicity. + err := c.publicAccess.Set(ctx, enum.PublicResourceTypeRepo, repo.Path, false) if err != nil { + return fmt.Errorf("failed to disable repo public access: %w", err) + } + + if err := c.repoStore.SoftDelete(ctx, repo, deletedAt); err != nil { return fmt.Errorf("failed to soft delete repo from db: %w", err) } diff --git a/app/api/controller/repo/update.go b/app/api/controller/repo/update.go index 579f69f3d5..c18b066916 100644 --- a/app/api/controller/repo/update.go +++ b/app/api/controller/repo/update.go @@ -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 { diff --git a/app/api/controller/repo/update_public_access.go b/app/api/controller/repo/update_public_access.go new file mode 100644 index 0000000000..553984ab19 --- /dev/null +++ b/app/api/controller/repo/update_public_access.go @@ -0,0 +1,79 @@ +// 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 + } + + repoClone := repo.Clone() + + // get current public access vale for audit + 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 GetRepoOutput(ctx, c.publicAccess, repo) + } + + 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) + } + + err = c.auditService.Log(ctx, + session.Principal, + audit.NewResource(audit.ResourceTypeRepository, repo.Identifier), + audit.ActionUpdated, + paths.Parent(repo.Path), + audit.WithOldObject(&RepositoryOutput{ + Repository: repoClone, + IsPublic: isPublic, + }), + audit.WithNewObject(&RepositoryOutput{ + 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 GetRepoOutput(ctx, c.publicAccess, repo) +} diff --git a/app/api/controller/repo/wire.go b/app/api/controller/repo/wire.go index e39496c583..6e893015ea 100644 --- a/app/api/controller/repo/wire.go +++ b/app/api/controller/repo/wire.go @@ -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,13 @@ 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 { diff --git a/app/api/controller/reposettings/controller.go b/app/api/controller/reposettings/controller.go index 2c9e2c37bd..f59a875735 100644 --- a/app/api/controller/reposettings/controller.go +++ b/app/api/controller/reposettings/controller.go @@ -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, ) } diff --git a/app/api/controller/reposettings/general_find.go b/app/api/controller/reposettings/general_find.go index 863f1b9ea5..d83bbaf817 100644 --- a/app/api/controller/reposettings/general_find.go +++ b/app/api/controller/reposettings/general_find.go @@ -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 } diff --git a/app/api/controller/reposettings/general_update.go b/app/api/controller/reposettings/general_update.go index e1e21e0e18..cd9d704a52 100644 --- a/app/api/controller/reposettings/general_update.go +++ b/app/api/controller/reposettings/general_update.go @@ -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 } diff --git a/app/api/controller/reposettings/security_find.go b/app/api/controller/reposettings/security_find.go index f6c422f6cc..fc57a9c473 100644 --- a/app/api/controller/reposettings/security_find.go +++ b/app/api/controller/reposettings/security_find.go @@ -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 } diff --git a/app/api/controller/reposettings/security_update.go b/app/api/controller/reposettings/security_update.go index d7d2d5fdc0..3f66da8fc7 100644 --- a/app/api/controller/reposettings/security_update.go +++ b/app/api/controller/reposettings/security_update.go @@ -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 } diff --git a/app/api/controller/space/controller.go b/app/api/controller/space/controller.go index 06bb4af45b..56286fdbd1 100644 --- a/app/api/controller/space/controller.go +++ b/app/api/controller/space/controller.go @@ -21,6 +21,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" @@ -36,6 +37,11 @@ var ( errPublicSpaceCreationDisabled = usererror.BadRequestf("Public space creation is disabled.") ) +type SpaceOutput struct { + types.Space + IsPublic bool `json:"is_public" yaml:"is_public"` +} + type Controller struct { nestedSpacesEnabled bool publicResourceCreationEnabled bool @@ -58,6 +64,7 @@ type Controller struct { importer *importer.Repository exporter *exporter.Repository resourceLimiter limiter.ResourceLimiter + publicAccess publicaccess.Service auditService audit.Service } @@ -67,7 +74,7 @@ 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, @@ -90,6 +97,7 @@ func NewController(config *types.Config, tx dbtx.Transactor, urlProvider url.Pro importer: importer, exporter: exporter, resourceLimiter: limiter, + publicAccess: publicAccess, auditService: auditService, } } diff --git a/app/api/controller/space/create.go b/app/api/controller/space/create.go index 1b9e4fea0c..ca25b1c407 100644 --- a/app/api/controller/space/create.go +++ b/app/api/controller/space/create.go @@ -29,6 +29,7 @@ import ( "github.com/harness/gitness/types" "github.com/harness/gitness/types/check" "github.com/harness/gitness/types/enum" + "github.com/rs/zerolog/log" ) var ( @@ -52,7 +53,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) } @@ -71,7 +72,15 @@ 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.PurgeNoAuth(ctx, session, space); dErr != nil { + log.Ctx(ctx).Warn().Err(dErr).Msg("failed to set a space public") + } + return nil, fmt.Errorf("failed to set space public access: %w", err) + } + + return GetSpaceOutput(ctx, c.publicAccess, space) } func (c *Controller) createSpaceInnerInTX( @@ -102,7 +111,6 @@ func (c *Controller) createSpaceInnerInTX( ParentID: parentID, Identifier: in.Identifier, Description: in.Description, - IsPublic: in.IsPublic, Path: spacePath, CreatedBy: session.Principal.ID, Created: now, @@ -177,7 +185,6 @@ func (c *Controller) getSpaceCheckAuthSpaceCreation( parentSpace, enum.ResourceTypeSpace, enum.PermissionSpaceEdit, - false, ); err != nil { return nil, fmt.Errorf("authorization failed: %w", err) } diff --git a/app/api/controller/space/events.go b/app/api/controller/space/events.go index 3f4083f857..9020f39793 100644 --- a/app/api/controller/space/events.go +++ b/app/api/controller/space/events.go @@ -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) } diff --git a/app/api/controller/space/export.go b/app/api/controller/space/export.go index f8cf5d28a9..0a89c7cdcc 100644 --- a/app/api/controller/space/export.go +++ b/app/api/controller/space/export.go @@ -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 } diff --git a/app/api/controller/space/export_progress.go b/app/api/controller/space/export_progress.go index 92df8b0c9a..783cc43571 100644 --- a/app/api/controller/space/export_progress.go +++ b/app/api/controller/space/export_progress.go @@ -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 } diff --git a/app/api/controller/space/find.go b/app/api/controller/space/find.go index ed301d1dab..dd8569c6bf 100644 --- a/app/api/controller/space/find.go +++ b/app/api/controller/space/find.go @@ -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) } diff --git a/app/api/controller/space/helper.go b/app/api/controller/space/helper.go new file mode 100644 index 0000000000..9d1eb1292f --- /dev/null +++ b/app/api/controller/space/helper.go @@ -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 +} diff --git a/app/api/controller/space/import.go b/app/api/controller/space/import.go index c98ad1a5ec..ce54fa5569 100644 --- a/app/api/controller/space/import.go +++ b/app/api/controller/space/import.go @@ -41,7 +41,7 @@ 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) { +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 +67,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,7 +84,7 @@ 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, remoteRepository.Identifier, "", @@ -98,6 +99,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 +107,7 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo jobGroupID, provider, repoIDs, + repoIsPublicVals, cloneURLs, in.Pipelines, ) @@ -131,7 +134,7 @@ func (c *Controller) Import(ctx context.Context, session *auth.Session, in *Impo } } - return space, nil + return GetSpaceOutput(ctx, c.publicAccess, space) } func (c *Controller) sanitizeImportInput(in *ImportInput) error { diff --git a/app/api/controller/space/import_repositories.go b/app/api/controller/space/import_repositories.go index 0386dc648f..462bb014eb 100644 --- a/app/api/controller/space/import_repositories.go +++ b/app/api/controller/space/import_repositories.go @@ -93,6 +93,7 @@ func (c *Controller) ImportRepositories( } repoIDs := make([]int64, 0, len(remoteRepositories)) + repoIsPublicVals := make([]bool, 0, len(remoteRepositories)) cloneURLs := make([]string, 0, len(remoteRepositories)) repos := make([]*types.Repository, 0, len(remoteRepositories)) duplicateRepos := make([]*types.Repository, 0, len(remoteRepositories)) @@ -110,7 +111,7 @@ func (c *Controller) ImportRepositories( } for _, remoteRepository := range remoteRepositories { - repo := remoteRepository.ToRepo( + repo, isPublic := remoteRepository.ToRepo( space.ID, remoteRepository.Identifier, "", @@ -126,9 +127,11 @@ func (c *Controller) ImportRepositories( } 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) + repoIsPublicVals = append(repoIsPublicVals, isPublic) } if len(repoIDs) == 0 { return nil @@ -139,6 +142,7 @@ func (c *Controller) ImportRepositories( jobGroupID, provider, repoIDs, + repoIsPublicVals, cloneURLs, in.Pipelines, ) diff --git a/app/api/controller/space/list_repositories.go b/app/api/controller/space/list_repositories.go index 2f928f2b0f..2514acbf64 100644 --- a/app/api/controller/space/list_repositories.go +++ b/app/api/controller/space/list_repositories.go @@ -19,7 +19,10 @@ import ( "fmt" apiauth "github.com/harness/gitness/app/api/auth" + "github.com/harness/gitness/app/api/controller/repo" + 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 +33,7 @@ func (c *Controller) ListRepositories( session *auth.Session, spaceRef string, filter *types.RepoFilter, -) ([]*types.Repository, int64, error) { +) ([]*repo.RepositoryOutput, int64, error) { space, err := c.spaceStore.FindByRef(ctx, spaceRef) if err != nil { return nil, 0, err @@ -43,7 +46,6 @@ func (c *Controller) ListRepositories( space, enum.ResourceTypeRepo, enum.PermissionRepoView, - true, ); err != nil { return nil, 0, err } @@ -56,21 +58,43 @@ func (c *Controller) ListRepositoriesNoAuth( ctx context.Context, spaceID int64, filter *types.RepoFilter, -) ([]*types.Repository, int64, error) { - count, err := c.repoStore.Count(ctx, spaceID, filter) - if err != nil { - return nil, 0, fmt.Errorf("failed to count child repos: %w", err) - } +) ([]*repo.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) + } - repos, err := c.repoStore.List(ctx, spaceID, filter) + return nil + }, dbtx.TxDefaultReadOnly) if err != nil { - return nil, 0, fmt.Errorf("failed to list child repos: %w", err) + return nil, 0, err } - // backfill URLs + var reposOut []*repo.RepositoryOutput for _, repo := range repos { + // backfill URLs repo.GitURL = c.urlProvider.GenerateGITCloneURL(repo.Path) + + // backfill public access mode + isPublic, err := c.publicAccess.Get(ctx, enum.PublicResourceTypeRepo, repo.Path) + if err != nil { + return nil, 0, fmt.Errorf("failed to get resource public access mode: %w", err) + } + + reposOut = append(reposOut, &repoCtrl.RepositoryOutput{ + Repository: *repo, + IsPublic: isPublic, + }) } - return repos, count, nil + return reposOut, count, nil } diff --git a/app/api/controller/space/list_spaces.go b/app/api/controller/space/list_spaces.go index c27d93c228..d72e5fa146 100644 --- a/app/api/controller/space/list_spaces.go +++ b/app/api/controller/space/list_spaces.go @@ -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,19 @@ func (c *Controller) ListSpacesNoAuth( return nil, 0, err } - return spaces, count, nil + // backfill public access mode + var spacesOut []*SpaceOutput + for _, space := range spaces { + isPublic, err := c.publicAccess.Get(ctx, enum.PublicResourceTypeSpace, space.Path) + if err != nil { + return nil, 0, fmt.Errorf("failed to get space public access mode: %w", err) + } + + spacesOut = append(spacesOut, &SpaceOutput{ + Space: *space, + IsPublic: isPublic, + }) + } + + return spacesOut, count, nil } diff --git a/app/api/controller/space/membership_add.go b/app/api/controller/space/membership_add.go index 1b0c63c7b9..07de38c6ab 100644 --- a/app/api/controller/space/membership_add.go +++ b/app/api/controller/space/membership_add.go @@ -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 } diff --git a/app/api/controller/space/membership_delete.go b/app/api/controller/space/membership_delete.go index 698b895db8..b0544cec56 100644 --- a/app/api/controller/space/membership_delete.go +++ b/app/api/controller/space/membership_delete.go @@ -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 } diff --git a/app/api/controller/space/membership_list.go b/app/api/controller/space/membership_list.go index 1f4e9f936e..f35a62a633 100644 --- a/app/api/controller/space/membership_list.go +++ b/app/api/controller/space/membership_list.go @@ -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 } diff --git a/app/api/controller/space/membership_update.go b/app/api/controller/space/membership_update.go index f14223a0c1..78ff493292 100644 --- a/app/api/controller/space/membership_update.go +++ b/app/api/controller/space/membership_update.go @@ -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 } diff --git a/app/api/controller/space/move.go b/app/api/controller/space/move.go index 7ab78a875f..b113770806 100644 --- a/app/api/controller/space/move.go +++ b/app/api/controller/space/move.go @@ -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 { diff --git a/app/api/controller/space/purge.go b/app/api/controller/space/purge.go index bfa4b4c9aa..8e3d609378 100644 --- a/app/api/controller/space/purge.go +++ b/app/api/controller/space/purge.go @@ -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. diff --git a/app/api/controller/space/restore.go b/app/api/controller/space/restore.go index 7a86992080..152b16acd3 100644 --- a/app/api/controller/space/restore.go +++ b/app/api/controller/space/restore.go @@ -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,7 @@ func (c *Controller) Restore( return nil, fmt.Errorf("failed to restore space in a tnx: %w", err) } - return space, nil + return GetSpaceOutput(ctx, c.publicAccess, space) } func (c *Controller) restoreSpaceInnerInTx( diff --git a/app/api/controller/space/soft_delete.go b/app/api/controller/space/soft_delete.go index 4b33b03f64..891a15a1b1 100644 --- a/app/api/controller/space/soft_delete.go +++ b/app/api/controller/space/soft_delete.go @@ -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) } diff --git a/app/api/controller/space/update.go b/app/api/controller/space/update.go index 1a057d0746..77813543d9 100644 --- a/app/api/controller/space/update.go +++ b/app/api/controller/space/update.go @@ -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 { diff --git a/app/api/controller/space/update_public_access.go b/app/api/controller/space/update_public_access.go new file mode 100644 index 0000000000..ef8ff2abad --- /dev/null +++ b/app/api/controller/space/update_public_access.go @@ -0,0 +1,49 @@ +// 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/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 + } + + 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 GetSpaceOutput(ctx, c.publicAccess, space) +} diff --git a/app/api/controller/space/wire.go b/app/api/controller/space/wire.go index 179ddb9d34..ee59db6927 100644 --- a/app/api/controller/space/wire.go +++ b/app/api/controller/space/wire.go @@ -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,11 @@ 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) } diff --git a/app/api/controller/upload/controller.go b/app/api/controller/upload/controller.go index 10532e901a..7f3a7396a1 100644 --- a/app/api/controller/upload/controller.go +++ b/app/api/controller/upload/controller.go @@ -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) } diff --git a/app/api/controller/upload/download.go b/app/api/controller/upload/download.go index 6c62449177..7730b4b302 100644 --- a/app/api/controller/upload/download.go +++ b/app/api/controller/upload/download.go @@ -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) } diff --git a/app/api/controller/upload/upload.go b/app/api/controller/upload/upload.go index 9db2c00d9f..2fa1d2122a 100644 --- a/app/api/controller/upload/upload.go +++ b/app/api/controller/upload/upload.go @@ -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) } diff --git a/app/api/controller/user/find.go b/app/api/controller/user/find.go index d2097a74fb..ff7dafb756 100644 --- a/app/api/controller/user/find.go +++ b/app/api/controller/user/find.go @@ -28,6 +28,11 @@ import ( */ func (c *Controller) Find(ctx context.Context, session *auth.Session, userUID string) (*types.User, error) { + if session.Principal.UID == auth.AnonymousPrincipal.UID { + return &types.User{ + UID: auth.AnonymousPrincipal.UID, + }, nil + } user, err := c.FindNoAuth(ctx, userUID) if err != nil { return nil, err diff --git a/app/api/controller/webhook/controller.go b/app/api/controller/webhook/controller.go index 2763ea6037..1b6628535d 100644 --- a/app/api/controller/webhook/controller.go +++ b/app/api/controller/webhook/controller.go @@ -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) } diff --git a/app/api/handler/principal/find.go b/app/api/handler/principal/find.go index ca74463a97..2f4705353d 100644 --- a/app/api/handler/principal/find.go +++ b/app/api/handler/principal/find.go @@ -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 diff --git a/app/api/handler/principal/search.go b/app/api/handler/principal/search.go index d1a5df6925..c3b5fe9f17 100644 --- a/app/api/handler/principal/search.go +++ b/app/api/handler/principal/search.go @@ -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 diff --git a/app/api/handler/repo/update_public_access.go b/app/api/handler/repo/update_public_access.go new file mode 100644 index 0000000000..e1b2d63844 --- /dev/null +++ b/app/api/handler/repo/update_public_access.go @@ -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) + } +} diff --git a/app/api/handler/space/update_public_access.go b/app/api/handler/space/update_public_access.go new file mode 100644 index 0000000000..d7ccbfd140 --- /dev/null +++ b/app/api/handler/space/update_public_access.go @@ -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) + } +} diff --git a/app/api/middleware/authn/authn.go b/app/api/middleware/authn/authn.go index ef5305f949..4d81faabfa 100644 --- a/app/api/middleware/authn/authn.go +++ b/app/api/middleware/authn/authn.go @@ -15,7 +15,6 @@ package authn import ( - "errors" "net/http" "github.com/harness/gitness/app/api/render" @@ -32,12 +31,6 @@ 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. @@ -52,11 +45,6 @@ func performAuthentication( 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 required { render.Unauthorized(ctx, w) return diff --git a/app/api/openapi/repo.go b/app/api/openapi/repo.go index c623ac65bc..87a2bd046c 100644 --- a/app/api/openapi/repo.go +++ b/app/api/openapi/repo.go @@ -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.MethodPatch, "/repos/{repo_ref}/public-access", opUpdatePublicAccess) + opServiceAccounts := openapi3.Operation{} opServiceAccounts.WithTags("repository") opServiceAccounts.WithMapOfAnything(map[string]interface{}{"operationId": "listRepositoryServiceAccounts"}) diff --git a/app/api/openapi/space.go b/app/api/openapi/space.go index bd52e72d84..992fe4da75 100644 --- a/app/api/openapi/space.go +++ b/app/api/openapi/space.go @@ -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.MethodPatch, "/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) diff --git a/app/auth/anonymous.go b/app/auth/anonymous.go new file mode 100644 index 0000000000..14587741fe --- /dev/null +++ b/app/auth/anonymous.go @@ -0,0 +1,28 @@ +// 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 anonymouse access. +var AnonymousPrincipal = types.Principal{ + ID: -1, + UID: types.AnonymousPrincipalUID, + Type: enum.PrincipalTypeUser, +} diff --git a/app/auth/authn/authenticator.go b/app/auth/authn/authenticator.go index 3f948b5b4c..c3af00ac78 100644 --- a/app/auth/authn/authenticator.go +++ b/app/auth/authn/authenticator.go @@ -15,25 +15,18 @@ package authn import ( - "errors" "net/http" "github.com/harness/gitness/app/auth" ) -var ( - // ErrNoAuthData that is returned if the authorizer doesn't find any data in the request that can be used for auth. - ErrNoAuthData = errors.New("the request doesn't contain any auth data that can be used by the Authorizer") -) - // Authenticator is an abstraction of an entity that's responsible for authenticating principals // that are making calls via HTTP. type Authenticator interface { /* * Tries to authenticate the acting principal if credentials are available. * Returns: - * (session, nil) - request contains auth data and principal was verified - * (nil, ErrNoAuthData) - request doesn't contain any auth data + * (session, nil) - request contains auth data and principal was verified or is anonymous * (nil, err) - request contains auth data but verification failed */ Authenticate(r *http.Request) (*auth.Session, error) diff --git a/app/auth/authn/jwt.go b/app/auth/authn/jwt.go index 902abb01e2..1cf58318c6 100644 --- a/app/auth/authn/jwt.go +++ b/app/auth/authn/jwt.go @@ -56,7 +56,9 @@ func (a *JWTAuthenticator) Authenticate(r *http.Request) (*auth.Session, error) str := extractToken(r, a.cookieName) if len(str) == 0 { - return nil, ErrNoAuthData + return &auth.Session{ + Principal: auth.AnonymousPrincipal, + }, nil } var principal *types.Principal diff --git a/app/auth/authz/membership.go b/app/auth/authz/membership.go index 2bfb6e9180..44a18f9651 100644 --- a/app/auth/authz/membership.go +++ b/app/auth/authz/membership.go @@ -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 } diff --git a/app/auth/authz/public_access.go b/app/auth/authz/public_access.go new file mode 100644 index 0000000000..75795a5f9d --- /dev/null +++ b/app/auth/authz/public_access.go @@ -0,0 +1,55 @@ +// 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 authz + +import ( + "context" + + "github.com/harness/gitness/app/paths" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +func (a *MembershipAuthorizer) CheckPublicAccess( + ctx context.Context, + scope *types.Scope, + resource *types.Resource, + permission enum.Permission, +) (bool, error) { + var pubResType enum.PublicResourceType + + //nolint:exhaustive + switch resource.Type { + case enum.ResourceTypeSpace: + pubResType = enum.PublicResourceTypeSpace + + case enum.ResourceTypeRepo: + if resource.Identifier != "" { + pubResType = enum.PublicResourceTypeRepo + } else { // for spaceScope checks + pubResType = enum.PublicResourceTypeSpace + } + + case enum.ResourceTypePipeline: + pubResType = enum.PublicResourceTypeRepo + + default: + return false, nil + } + + pubResPath := paths.Concatenate(scope.SpacePath, resource.Identifier) + + return a.publicAccess.Get(ctx, pubResType, pubResPath) +} diff --git a/app/auth/authz/wire.go b/app/auth/authz/wire.go index 8d9aec6a93..9b500240d9 100644 --- a/app/auth/authz/wire.go +++ b/app/auth/authz/wire.go @@ -17,6 +17,7 @@ package authz import ( "time" + "github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/store" "github.com/google/wire" @@ -28,8 +29,12 @@ var WireSet = wire.NewSet( ProvidePermissionCache, ) -func ProvideAuthorizer(pCache PermissionCache, spaceStore store.SpaceStore) Authorizer { - return NewMembershipAuthorizer(pCache, spaceStore) +func ProvideAuthorizer( + pCache PermissionCache, + spaceStore store.SpaceStore, + publicAccess publicaccess.Service, +) Authorizer { + return NewMembershipAuthorizer(pCache, spaceStore, publicAccess) } func ProvidePermissionCache( diff --git a/app/pipeline/converter/converter.go b/app/pipeline/converter/converter.go index 87a61d1143..06b01cc05c 100644 --- a/app/pipeline/converter/converter.go +++ b/app/pipeline/converter/converter.go @@ -21,6 +21,8 @@ import ( "github.com/harness/gitness/app/pipeline/converter/jsonnet" "github.com/harness/gitness/app/pipeline/converter/starlark" "github.com/harness/gitness/app/pipeline/file" + "github.com/harness/gitness/app/services/publicaccess" + "github.com/harness/gitness/types/enum" ) const ( @@ -30,7 +32,8 @@ const ( ) type converter struct { - fileService file.Service + fileService file.Service + publicAccess publicaccess.Service } func newConverter(fileService file.Service) Service { @@ -40,14 +43,20 @@ func newConverter(fileService file.Service) Service { func (c *converter) Convert(_ context.Context, args *ConvertArgs) (*file.File, error) { path := args.Pipeline.ConfigPath + // get public access visibility of the repo + repoIsPublic, err := c.publicAccess.Get(context.Background(), enum.PublicResourceTypeRepo, args.Repo.Path) + if err != nil { + return nil, err + } + if isJSONNet(path) { - str, err := jsonnet.Parse(args.Repo, args.Pipeline, args.Execution, args.File, c.fileService, jsonnetImportLimit) + str, err := jsonnet.Parse(args.Repo, repoIsPublic, args.Pipeline, args.Execution, args.File, c.fileService, jsonnetImportLimit) if err != nil { return nil, err } return &file.File{Data: []byte(str)}, nil } else if isStarlark(path) { - str, err := starlark.Parse(args.Repo, args.Pipeline, args.Execution, args.File, starlarkStepLimit, starlarkSizeLimit) + str, err := starlark.Parse(args.Repo, repoIsPublic, args.Pipeline, args.Execution, args.File, starlarkStepLimit, starlarkSizeLimit) if err != nil { return nil, err } diff --git a/app/pipeline/converter/jsonnet/jsonnet.go b/app/pipeline/converter/jsonnet/jsonnet.go index b0205a3eff..f1e6f485c5 100644 --- a/app/pipeline/converter/jsonnet/jsonnet.go +++ b/app/pipeline/converter/jsonnet/jsonnet.go @@ -104,6 +104,7 @@ func (i *importer) Import(importedFrom, importedPath string) (contents jsonnet.C func Parse( repo *types.Repository, + repoIsPublic bool, pipeline *types.Pipeline, execution *types.Execution, file *file.File, @@ -130,7 +131,7 @@ func Parse( mapBuild(execution, vm) } if repo != nil { - mapRepo(repo, pipeline, vm) + mapRepo(repo, pipeline, vm, repoIsPublic) } jsonnetFile := file @@ -188,7 +189,7 @@ func mapBuild(v *types.Execution, vm *jsonnet.VM) { // mapBuild populates repo level variables available to jsonnet templates. // Since we want to maintain compatibility with drone 2.x, the older format // needs to be maintained (even if the variables do not exist in gitness). -func mapRepo(v *types.Repository, p *types.Pipeline, vm *jsonnet.VM) { +func mapRepo(v *types.Repository, p *types.Pipeline, vm *jsonnet.VM, publicRepo bool) { namespace := v.Path idx := strings.LastIndex(v.Path, "/") if idx != -1 { @@ -205,7 +206,7 @@ func mapRepo(v *types.Repository, p *types.Pipeline, vm *jsonnet.VM) { vm.ExtVar(repo+"link", v.GitURL) vm.ExtVar(repo+"branch", v.DefaultBranch) vm.ExtVar(repo+"config", p.ConfigPath) - vm.ExtVar(repo+"private", strconv.FormatBool(!v.IsPublic)) + vm.ExtVar(repo+"private", strconv.FormatBool(!publicRepo)) vm.ExtVar(repo+"visibility", "internal") vm.ExtVar(repo+"active", strconv.FormatBool(true)) vm.ExtVar(repo+"trusted", strconv.FormatBool(true)) diff --git a/app/pipeline/converter/service.go b/app/pipeline/converter/service.go index 7f84fb403c..e2bbceb1ef 100644 --- a/app/pipeline/converter/service.go +++ b/app/pipeline/converter/service.go @@ -25,10 +25,11 @@ type ( // ConvertArgs represents a request to the pipeline // conversion service. ConvertArgs struct { - Repo *types.Repository `json:"repository,omitempty"` - Pipeline *types.Pipeline `json:"pipeline,omitempty"` - Execution *types.Execution `json:"execution,omitempty"` - File *file.File `json:"config,omitempty"` + Repo *types.Repository `json:"repository,omitempty"` + RepoIsPublic bool `json:"repo_is_public,omitempty"` + Pipeline *types.Pipeline `json:"pipeline,omitempty"` + Execution *types.Execution `json:"execution,omitempty"` + File *file.File `json:"config,omitempty"` } // Service converts a file which is in starlark/jsonnet form by looking diff --git a/app/pipeline/converter/starlark/args.go b/app/pipeline/converter/starlark/args.go index c4465b875a..ff2f96d2ac 100644 --- a/app/pipeline/converter/starlark/args.go +++ b/app/pipeline/converter/starlark/args.go @@ -27,12 +27,13 @@ func createArgs( repo *types.Repository, pipeline *types.Pipeline, execution *types.Execution, + repoIsPublic bool, ) []starlark.Value { args := []starlark.Value{ starlarkstruct.FromStringDict( starlark.String("context"), starlark.StringDict{ - "repo": starlarkstruct.FromStringDict(starlark.String("repo"), fromRepo(repo, pipeline)), + "repo": starlarkstruct.FromStringDict(starlark.String("repo"), fromRepo(repo, pipeline, repoIsPublic)), "build": starlarkstruct.FromStringDict(starlark.String("build"), fromBuild(execution)), }, ), @@ -66,12 +67,13 @@ func fromBuild(v *types.Execution) starlark.StringDict { } } -func fromRepo(v *types.Repository, p *types.Pipeline) starlark.StringDict { +func fromRepo(v *types.Repository, p *types.Pipeline, publicRepo bool) starlark.StringDict { namespace := v.Path idx := strings.LastIndex(v.Path, "/") if idx != -1 { namespace = v.Path[:idx] } + return starlark.StringDict{ // TODO [CODE-1363]: remove after identifier migration? "uid": starlark.String(v.Identifier), @@ -84,7 +86,7 @@ func fromRepo(v *types.Repository, p *types.Pipeline) starlark.StringDict { "link": starlark.String(v.GitURL), "branch": starlark.String(v.DefaultBranch), "config": starlark.String(p.ConfigPath), - "private": !starlark.Bool(v.IsPublic), + "private": !starlark.Bool(publicRepo), "visibility": starlark.String("internal"), "active": starlark.Bool(true), "trusted": starlark.Bool(true), diff --git a/app/pipeline/converter/starlark/starlark.go b/app/pipeline/converter/starlark/starlark.go index 24c0442b9a..8807108308 100644 --- a/app/pipeline/converter/starlark/starlark.go +++ b/app/pipeline/converter/starlark/starlark.go @@ -57,6 +57,7 @@ var ( func Parse( repo *types.Repository, + repoIsPublic bool, pipeline *types.Pipeline, execution *types.Execution, file *file.File, @@ -95,7 +96,7 @@ func Parse( // create the input args and invoke the main method // using the input args. - args := createArgs(repo, pipeline, execution) + args := createArgs(repo, pipeline, execution, repoIsPublic) // set the maximum number of operations in the script. this // mitigates long running scripts. diff --git a/app/pipeline/manager/client.go b/app/pipeline/manager/client.go index e79ed2c8bf..68f01ce1ee 100644 --- a/app/pipeline/manager/client.go +++ b/app/pipeline/manager/client.go @@ -103,7 +103,7 @@ func (e *embedded) Detail(ctx context.Context, stage *drone.Stage) (*client.Cont return &client.Context{ Build: ConvertToDroneBuild(details.Execution), - Repo: ConvertToDroneRepo(details.Repo), + Repo: ConvertToDroneRepo(details.Repo, details.RepoIsPublic), Stage: ConvertToDroneStage(details.Stage), Secrets: ConvertToDroneSecrets(details.Secrets), Config: ConvertToDroneFile(details.Config), diff --git a/app/pipeline/manager/convert.go b/app/pipeline/manager/convert.go index bf0394efac..8c9bbd0904 100644 --- a/app/pipeline/manager/convert.go +++ b/app/pipeline/manager/convert.go @@ -207,7 +207,7 @@ func ConvertToDroneBuild(execution *types.Execution) *drone.Build { } } -func ConvertToDroneRepo(repo *types.Repository) *drone.Repo { +func ConvertToDroneRepo(repo *types.Repository, repoIsPublic bool) *drone.Repo { return &drone.Repo{ ID: repo.ID, Trusted: true, // as builds are running on user machines, the repo is marked trusted. @@ -217,7 +217,7 @@ func ConvertToDroneRepo(repo *types.Repository) *drone.Repo { Name: repo.Identifier, HTTPURL: repo.GitURL, Link: repo.GitURL, - Private: !repo.IsPublic, + Private: !repoIsPublic, Created: repo.Created, Updated: repo.Updated, Version: repo.Version, diff --git a/app/pipeline/manager/manager.go b/app/pipeline/manager/manager.go index 2dfdb2d547..1f968eba30 100644 --- a/app/pipeline/manager/manager.go +++ b/app/pipeline/manager/manager.go @@ -27,6 +27,7 @@ import ( "github.com/harness/gitness/app/pipeline/converter" "github.com/harness/gitness/app/pipeline/file" "github.com/harness/gitness/app/pipeline/scheduler" + "github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/sse" "github.com/harness/gitness/app/store" urlprovider "github.com/harness/gitness/app/url" @@ -80,12 +81,13 @@ type ( // ExecutionContext represents the minimum amount of information // required by the runner to execute a build. ExecutionContext struct { - Repo *types.Repository `json:"repository"` - Execution *types.Execution `json:"build"` - Stage *types.Stage `json:"stage"` - Secrets []*types.Secret `json:"secrets"` - Config *file.File `json:"config"` - Netrc *Netrc `json:"netrc"` + Repo *types.Repository `json:"repository"` + RepoIsPublic bool `json:"repository_is_public,omitempty"` + Execution *types.Execution `json:"build"` + Stage *types.Stage `json:"stage"` + Secrets []*types.Secret `json:"secrets"` + Config *file.File `json:"config"` + Netrc *Netrc `json:"netrc"` } // ExecutionManager encapsulates complex build operations and provides @@ -148,6 +150,8 @@ type Manager struct { // System *store.System Users store.PrincipalStore // Webhook store.WebhookSender + + publicAccess publicaccess.Service } func New( @@ -167,6 +171,7 @@ func New( stageStore store.StageStore, stepStore store.StepStore, userStore store.PrincipalStore, + publicAccess publicaccess.Service, ) *Manager { return &Manager{ Config: config, @@ -185,6 +190,7 @@ func New( Stages: stageStore, Steps: stepStore, Users: userStore, + publicAccess: publicAccess, } } @@ -300,6 +306,7 @@ func (m *Manager) Details(_ context.Context, stageID int64) (*ExecutionContext, log.Warn().Err(err).Msg("manager: cannot find repo") return nil, err } + // Backfill clone URL repo.GitURL = m.urlProvider.GenerateContainerGITCloneURL(repo.Path) @@ -329,12 +336,20 @@ func (m *Manager) Details(_ context.Context, stageID int64) (*ExecutionContext, return nil, err } + // Get public access settings of the repo + repoIsPublic, err := m.publicAccess.Get(noContext, enum.PublicResourceTypeRepo, repo.Path) + if err != nil { + log.Warn().Err(err).Msg("manager: cannot check if repo is public") + return nil, err + } + // Convert file contents in case templates are being used. args := &converter.ConvertArgs{ - Repo: repo, - Pipeline: pipeline, - Execution: execution, - File: file, + Repo: repo, + Pipeline: pipeline, + Execution: execution, + File: file, + RepoIsPublic: repoIsPublic, } file, err = m.ConverterService.Convert(noContext, args) if err != nil { @@ -349,12 +364,13 @@ func (m *Manager) Details(_ context.Context, stageID int64) (*ExecutionContext, } return &ExecutionContext{ - Repo: repo, - Execution: execution, - Stage: stage, - Secrets: secrets, - Config: file, - Netrc: netrc, + Repo: repo, + RepoIsPublic: repoIsPublic, + Execution: execution, + Stage: stage, + Secrets: secrets, + Config: file, + Netrc: netrc, }, nil } diff --git a/app/pipeline/manager/wire.go b/app/pipeline/manager/wire.go index 448deefd43..74a74a65b5 100644 --- a/app/pipeline/manager/wire.go +++ b/app/pipeline/manager/wire.go @@ -18,6 +18,7 @@ import ( "github.com/harness/gitness/app/pipeline/converter" "github.com/harness/gitness/app/pipeline/file" "github.com/harness/gitness/app/pipeline/scheduler" + "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" @@ -51,9 +52,11 @@ func ProvideExecutionManager( secretStore store.SecretStore, stageStore store.StageStore, stepStore store.StepStore, - userStore store.PrincipalStore) ExecutionManager { + userStore store.PrincipalStore, + PublicAccess publicaccess.Service, +) ExecutionManager { return New(config, executionStore, pipelineStore, urlProvider, sseStreamer, fileService, converterService, - logStore, logStream, checkStore, repoStore, scheduler, secretStore, stageStore, stepStore, userStore) + logStore, logStream, checkStore, repoStore, scheduler, secretStore, stageStore, stepStore, userStore, PublicAccess) } // ProvideExecutionClient provides a client implementation to interact with the execution manager. diff --git a/app/pipeline/triggerer/trigger.go b/app/pipeline/triggerer/trigger.go index 146b40f071..e44a80c52d 100644 --- a/app/pipeline/triggerer/trigger.go +++ b/app/pipeline/triggerer/trigger.go @@ -28,6 +28,7 @@ import ( "github.com/harness/gitness/app/pipeline/resolver" "github.com/harness/gitness/app/pipeline/scheduler" "github.com/harness/gitness/app/pipeline/triggerer/dag" + "github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/store" "github.com/harness/gitness/app/url" "github.com/harness/gitness/store/database/dbtx" @@ -92,6 +93,7 @@ type triggerer struct { repoStore store.RepoStore templateStore store.TemplateStore pluginStore store.PluginStore + publicAccess publicaccess.Service } func New( @@ -107,6 +109,7 @@ func New( converterService converter.Service, templateStore store.TemplateStore, pluginStore store.PluginStore, + publicAccess publicaccess.Service, ) Triggerer { return &triggerer{ executionStore: executionStore, @@ -121,6 +124,7 @@ func New( repoStore: repoStore, templateStore: templateStore, pluginStore: pluginStore, + publicAccess: publicAccess, } } @@ -154,6 +158,11 @@ func (t *triggerer) Trigger( return nil, err } + repoIsPublic, err := t.publicAccess.Get(ctx, enum.PublicResourceTypeRepo, repo.Path) + if err != nil { + return nil, fmt.Errorf("could not check if repo is public: %w", err) + } + file, err := t.fileService.Get(ctx, repo, pipeline.ConfigPath, base.After) if err != nil { log.Error().Err(err).Msg("trigger: could not find yaml") @@ -200,10 +209,11 @@ func (t *triggerer) Trigger( if !isV1Yaml(file.Data) { // Convert from jsonnet/starlark to drone yaml args := &converter.ConvertArgs{ - Repo: repo, - Pipeline: pipeline, - Execution: execution, - File: file, + Repo: repo, + Pipeline: pipeline, + Execution: execution, + File: file, + RepoIsPublic: repoIsPublic, } file, err = t.converterService.Convert(ctx, args) if err != nil { @@ -331,7 +341,7 @@ func (t *triggerer) Trigger( } } else { stages, err = parseV1Stages( - ctx, file.Data, repo, execution, t.templateStore, t.pluginStore) + ctx, file.Data, repo, execution, t.templateStore, t.pluginStore, t.publicAccess) if err != nil { return nil, fmt.Errorf("could not parse v1 YAML into stages: %w", err) } @@ -395,6 +405,7 @@ func parseV1Stages( execution *types.Execution, templateStore store.TemplateStore, pluginStore store.PluginStore, + publicAccess publicaccess.Service, ) ([]*types.Stage, error) { stages := []*types.Stage{} // For V1 YAML, just go through the YAML and create stages serially for now @@ -413,8 +424,14 @@ func parseV1Stages( return nil, fmt.Errorf("cannot support non-pipeline kinds in v1 at the moment: %w", err) } + // get repo public access + repoIsPublic, err := publicAccess.Get(ctx, enum.PublicResourceTypeRepo, repo.Path) + if err != nil { + return nil, fmt.Errorf("could not check repo public access: %w", err) + } + inputParams := map[string]interface{}{} - inputParams["repo"] = inputs.Repo(manager.ConvertToDroneRepo(repo)) + inputParams["repo"] = inputs.Repo(manager.ConvertToDroneRepo(repo, repoIsPublic)) inputParams["build"] = inputs.Build(manager.ConvertToDroneBuild(execution)) var prevStage string diff --git a/app/pipeline/triggerer/wire.go b/app/pipeline/triggerer/wire.go index 34cc0babf6..41652795de 100644 --- a/app/pipeline/triggerer/wire.go +++ b/app/pipeline/triggerer/wire.go @@ -18,6 +18,7 @@ import ( "github.com/harness/gitness/app/pipeline/converter" "github.com/harness/gitness/app/pipeline/file" "github.com/harness/gitness/app/pipeline/scheduler" + "github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/store" "github.com/harness/gitness/app/url" "github.com/harness/gitness/store/database/dbtx" @@ -44,8 +45,9 @@ func ProvideTriggerer( urlProvider url.Provider, templateStore store.TemplateStore, pluginStore store.PluginStore, + publicAccess publicaccess.Service, ) Triggerer { return New(executionStore, checkStore, stageStore, pipelineStore, tx, repoStore, urlProvider, scheduler, fileService, converterService, - templateStore, pluginStore) + templateStore, pluginStore, publicAccess) } diff --git a/app/router/api.go b/app/router/api.go index 94a8520228..7237cfc906 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -241,6 +241,7 @@ func setupSpaces(r chi.Router, appCtx context.Context, spaceCtrl *space.Controll r.Get("/templates", handlerspace.HandleListTemplates(spaceCtrl)) r.Post("/export", handlerspace.HandleExport(spaceCtrl)) r.Get("/export-progress", handlerspace.HandleExportProgress(spaceCtrl)) + r.Post("/public-access", handlerspace.HandleUpdatePublicAccess(spaceCtrl)) r.Route("/members", func(r chi.Router) { r.Get("/", handlerspace.HandleMembershipList(spaceCtrl)) @@ -277,6 +278,7 @@ func setupRepos(r chi.Router, r.Delete("/", handlerrepo.HandleSoftDelete(repoCtrl)) r.Post("/purge", handlerrepo.HandlePurge(repoCtrl)) r.Post("/restore", handlerrepo.HandleRestore(repoCtrl)) + r.Post("/public-access", handlerrepo.HandleUpdatePublicAccess(repoCtrl)) r.Route("/settings", func(r chi.Router) { r.Get("/security", handlerreposettings.HandleSecurityFind(repoSettingsCtrl)) diff --git a/app/services/exporter/repository.go b/app/services/exporter/repository.go index b9f492fbf1..c7ce1da2a9 100644 --- a/app/services/exporter/repository.go +++ b/app/services/exporter/repository.go @@ -25,6 +25,7 @@ import ( "time" "github.com/harness/gitness/app/api/controller/repo" + "github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/sse" "github.com/harness/gitness/app/store" gitnessurl "github.com/harness/gitness/app/url" @@ -48,12 +49,13 @@ var ( ) type Repository struct { - urlProvider gitnessurl.Provider - git git.Interface - repoStore store.RepoStore - scheduler *job.Scheduler - encrypter encrypt.Encrypter - sseStreamer sse.Streamer + urlProvider gitnessurl.Provider + git git.Interface + repoStore store.RepoStore + scheduler *job.Scheduler + encrypter encrypt.Encrypter + sseStreamer sse.Streamer + publicAccess publicaccess.Service } type Input struct { @@ -114,11 +116,16 @@ func (r *Repository) RunManyForSpace( jobDefinitions := make([]job.Definition, len(repos)) for i, repository := range repos { + isPublic, err := r.publicAccess.Get(ctx, enum.PublicResourceTypeRepo, repository.Path) + if err != nil { + return fmt.Errorf("failed to check repo public access: %w", err) + } + repoJobData := Input{ Identifier: repository.Identifier, ID: repository.ID, Description: repository.Description, - IsPublic: repository.IsPublic, + IsPublic: isPublic, HarnessCodeInfo: *harnessCodeInfo, } @@ -184,11 +191,12 @@ func (r *Repository) Handle(ctx context.Context, data string, _ job.ProgressRepo if err != nil { return "", err } + remoteRepo, err := client.CreateRepo(ctx, repo.CreateInput{ Identifier: repository.Identifier, DefaultBranch: repository.DefaultBranch, Description: repository.Description, - IsPublic: false, // todo: use repository.IsPublic once public is available. + IsPublic: false, // TODO: replace with publicaccess service response once deployed on HC. Readme: false, License: "", GitIgnore: "", diff --git a/app/services/importer/provider.go b/app/services/importer/provider.go index 32ddf7f7c0..9277fd2d4d 100644 --- a/app/services/importer/provider.go +++ b/app/services/importer/provider.go @@ -75,14 +75,14 @@ type RepositoryInfo struct { DefaultBranch string } -// ToRepo converts the RepositoryInfo into the types.Repository object marked as being imported. +// ToRepo converts the RepositoryInfo into the types.Repository object marked as being imported and is-public flag. func (r *RepositoryInfo) ToRepo( spaceID int64, identifier string, description string, principal *types.Principal, publicResourceCreationEnabled bool, -) *types.Repository { +) (*types.Repository, bool) { now := time.Now().UnixMilli() gitTempUID := fmt.Sprintf("importing-%s-%d", hash(fmt.Sprintf("%d:%s", spaceID, identifier)), now) return &types.Repository{ @@ -91,14 +91,13 @@ func (r *RepositoryInfo) ToRepo( Identifier: identifier, GitUID: gitTempUID, // the correct git UID will be set by the job handler Description: description, - IsPublic: publicResourceCreationEnabled && r.IsPublic, CreatedBy: principal.ID, Created: now, Updated: now, ForkID: 0, DefaultBranch: r.DefaultBranch, Importing: true, - } + }, (r.IsPublic && publicResourceCreationEnabled) } func hash(s string) string { diff --git a/app/services/importer/repository.go b/app/services/importer/repository.go index 5f4ae0a3ee..7407a2f1cb 100644 --- a/app/services/importer/repository.go +++ b/app/services/importer/repository.go @@ -27,6 +27,7 @@ import ( "github.com/harness/gitness/app/bootstrap" "github.com/harness/gitness/app/githook" "github.com/harness/gitness/app/services/keywordsearch" + "github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/sse" "github.com/harness/gitness/app/store" gitnessurl "github.com/harness/gitness/app/url" @@ -63,6 +64,7 @@ type Repository struct { scheduler *job.Scheduler sseStreamer sse.Streamer indexer keywordsearch.Indexer + publicAccess publicaccess.Service } var _ job.Handler = (*Repository)(nil) @@ -80,11 +82,12 @@ const ( ) type Input struct { - RepoID int64 `json:"repo_id"` - GitUser string `json:"git_user"` - GitPass string `json:"git_pass"` - CloneURL string `json:"clone_url"` - Pipelines PipelineOption `json:"pipelines"` + RepoID int64 `json:"repo_id"` + RepoIsPublic bool `json:"is_public"` + GitUser string `json:"git_user"` + GitPass string `json:"git_pass"` + CloneURL string `json:"clone_url"` + Pipelines PipelineOption `json:"pipelines"` } const jobType = "repository_import" @@ -99,14 +102,16 @@ func (r *Repository) Run( provider Provider, repo *types.Repository, cloneURL string, + isPublic bool, pipelines PipelineOption, ) error { jobDef, err := r.getJobDef(JobIDFromRepoID(repo.ID), Input{ - RepoID: repo.ID, - GitUser: provider.Username, - GitPass: provider.Password, - CloneURL: cloneURL, - Pipelines: pipelines, + RepoID: repo.ID, + RepoIsPublic: isPublic, + GitUser: provider.Username, + GitPass: provider.Password, + CloneURL: cloneURL, + Pipelines: pipelines, }) if err != nil { return err @@ -120,6 +125,7 @@ func (r *Repository) RunMany(ctx context.Context, groupID string, provider Provider, repoIDs []int64, + repoIsPublicVals []bool, cloneURLs []string, pipelines PipelineOption, ) error { @@ -134,13 +140,15 @@ func (r *Repository) RunMany(ctx context.Context, for k := 0; k < n; k++ { repoID := repoIDs[k] cloneURL := cloneURLs[k] + isPublic := repoIsPublicVals[k] jobDef, err := r.getJobDef(JobIDFromRepoID(repoID), Input{ - RepoID: repoID, - GitUser: provider.Username, - GitPass: provider.Password, - CloneURL: cloneURL, - Pipelines: pipelines, + RepoID: repoID, + RepoIsPublic: isPublic, + GitUser: provider.Username, + GitPass: provider.Password, + CloneURL: cloneURL, + Pipelines: pipelines, }) if err != nil { return err @@ -258,6 +266,15 @@ func (r *Repository) Handle(ctx context.Context, data string, _ job.ProgressRepo return fmt.Errorf("failed to update repository prior to the import: %w", err) } + log.Info().Msg("setting repository public access") + // setup public access + // Note: if a repository with the same reference had its public access not cleaned up + // it might retain its public status until this job sets the actual is-public value for the importing repo. + err = r.publicAccess.Set(ctx, enum.PublicResourceTypeRepo, repo.Path, input.RepoIsPublic) + if err != nil { + return fmt.Errorf("failed to set repo public access: %w", err) + } + log.Info().Msg("sync repository") defaultBranch, err := r.syncGitRepository(ctx, &systemPrincipal, repo, cloneURLWithAuth) diff --git a/app/services/importer/wire.go b/app/services/importer/wire.go index c5dac7f77f..012c058368 100644 --- a/app/services/importer/wire.go +++ b/app/services/importer/wire.go @@ -16,6 +16,7 @@ package importer import ( "github.com/harness/gitness/app/services/keywordsearch" + "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" @@ -45,6 +46,7 @@ func ProvideRepoImporter( executor *job.Executor, sseStreamer sse.Streamer, indexer keywordsearch.Indexer, + publicAccess publicaccess.Service, ) (*Repository, error) { importer := &Repository{ defaultBranch: config.Git.DefaultBranch, @@ -58,6 +60,7 @@ func ProvideRepoImporter( scheduler: scheduler, sseStreamer: sseStreamer, indexer: indexer, + publicAccess: publicAccess, } err := executor.Register(jobType, importer) diff --git a/app/services/publicaccess/public_access.go b/app/services/publicaccess/public_access.go new file mode 100644 index 0000000000..3bf7590e31 --- /dev/null +++ b/app/services/publicaccess/public_access.go @@ -0,0 +1,50 @@ +// 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 publicaccess + +import ( + "context" + + "github.com/harness/gitness/types/enum" +) + +// Service is an abstraction of an entity responsible for managing public access to resources. +type Service interface { + /* + * Get returns whether public access is enabled on the resource. + * Returns + * (true, nil) - public access to the resource is allowed + * (false, nil) - public access to the resource is not allowed + * (false, err) - an error occurred while performing public access check. + */ + Get( + ctx context.Context, + resourceType enum.PublicResourceType, + resourcePath string, + ) (bool, error) + + /* + * Sets or deletes public access mode for the resource based on the value of 'enable'. + * Returns + * nil - resource public access mode has been updated successfully + * err - an error occurred while performing public access set. + */ + Set( + ctx context.Context, + resourceType enum.PublicResourceType, + resourcePath string, + enable bool, + ) error +} diff --git a/app/services/publicaccess/resources.go b/app/services/publicaccess/resources.go new file mode 100644 index 0000000000..f3490b0992 --- /dev/null +++ b/app/services/publicaccess/resources.go @@ -0,0 +1,69 @@ +// 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 publicaccess + +import ( + "context" + "fmt" + + "github.com/harness/gitness/types/enum" +) + +func (s *service) getPublicResource( + ctx context.Context, + resourceType enum.PublicResourceType, + resourcePath string, +) (int64, error) { + var id int64 + var err error + switch resourceType { + case enum.PublicResourceTypeRepo: + id, err = s.getResourceRepo(ctx, resourcePath) + case enum.PublicResourceTypeSpace: + id, err = s.getResourceSpace(ctx, resourcePath) + default: + return 0, fmt.Errorf("invalid public resource type") + } + + if err != nil { + return 0, fmt.Errorf("failed to get public resource: %w", err) + } + + return id, nil +} + +func (s *service) getResourceRepo( + ctx context.Context, + path string, +) (int64, error) { + repo, err := s.repoStore.FindByRef(ctx, path) + if err != nil { + return 0, fmt.Errorf("failed to find repo: %w", err) + } + + return repo.ID, nil +} + +func (s *service) getResourceSpace( + ctx context.Context, + path string, +) (int64, error) { + space, err := s.spaceStore.FindByRef(ctx, path) + if err != nil { + return 0, fmt.Errorf("failed to find space: %w", err) + } + + return space.ID, nil +} diff --git a/app/services/publicaccess/service.go b/app/services/publicaccess/service.go new file mode 100644 index 0000000000..0f6a94a845 --- /dev/null +++ b/app/services/publicaccess/service.go @@ -0,0 +1,98 @@ +// 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 publicaccess + +import ( + "context" + "errors" + "fmt" + + "github.com/harness/gitness/app/store" + gitness_store "github.com/harness/gitness/store" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" + "github.com/rs/zerolog/log" +) + +var errPublicResourceCreationDisabled = errors.New("public resource creation is disabled") + +type service struct { + publicResourceCreationEnabled bool + publicAccessStore store.PublicAccessStore + repoStore store.RepoStore + spaceStore store.SpaceStore +} + +func NewService( + config *types.Config, + publicAccessStore store.PublicAccessStore, + repoStore store.RepoStore, + spaceStore store.SpaceStore, +) Service { + return &service{ + publicResourceCreationEnabled: config.PublicResourceCreationEnabled, + publicAccessStore: publicAccessStore, + repoStore: repoStore, + spaceStore: spaceStore, + } +} + +func (s *service) Get( + ctx context.Context, + resourceType enum.PublicResourceType, + resourcePath string, +) (bool, error) { + pubResID, err := s.getPublicResource(ctx, resourceType, resourcePath) + if err != nil { + return false, err + } + + err = s.publicAccessStore.Find(ctx, resourceType, pubResID) + if errors.Is(err, gitness_store.ErrResourceNotFound) { + return false, nil + } + if err != nil { + return false, fmt.Errorf("failed to get public access resource: %w", err) + } + + return true, nil +} + +func (s *service) Set( + ctx context.Context, + resourceType enum.PublicResourceType, + resourcePath string, + enable bool, +) error { + if enable && !s.publicResourceCreationEnabled { + return errPublicResourceCreationDisabled + } + + pubResID, err := s.getPublicResource(ctx, resourceType, resourcePath) + if err != nil { + return err + } + + if enable { + err := s.publicAccessStore.Create(ctx, resourceType, pubResID) + if errors.Is(err, gitness_store.ErrDuplicate) { + log.Ctx(ctx).Warn().Msgf("repo %d is already set for public access", pubResID) + return nil + } + return err + } else { + return s.publicAccessStore.Delete(ctx, resourceType, pubResID) + } +} diff --git a/app/services/publicaccess/wire.go b/app/services/publicaccess/wire.go new file mode 100644 index 0000000000..5dfbbaea4d --- /dev/null +++ b/app/services/publicaccess/wire.go @@ -0,0 +1,35 @@ +// 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 publicaccess + +import ( + "github.com/harness/gitness/app/store" + "github.com/harness/gitness/types" + + "github.com/google/wire" +) + +var WireSet = wire.NewSet( + ProvidePublicAccess, +) + +func ProvidePublicAccess( + config *types.Config, + publicAccessStore store.PublicAccessStore, + repoStore store.RepoStore, + spaceStore store.SpaceStore, +) Service { + return NewService(config, publicAccessStore, repoStore, spaceStore) +} diff --git a/app/store/database.go b/app/store/database.go index d75bf86541..17a4378da6 100644 --- a/app/store/database.go +++ b/app/store/database.go @@ -290,6 +290,13 @@ type ( ListSpaces(ctx context.Context, userID int64, filter types.MembershipSpaceFilter) ([]types.MembershipSpace, error) } + // PublicAccessStore defines the publicly accessible resources data storage. + PublicAccessStore interface { + Find(ctx context.Context, typ enum.PublicResourceType, id int64) error + Create(ctx context.Context, typ enum.PublicResourceType, id int64) error + Delete(ctx context.Context, typ enum.PublicResourceType, id int64) error + } + // TokenStore defines the token data storage. TokenStore interface { // Find finds the token by id diff --git a/app/store/database/migrate/postgres/0050_create_table_public_resources.down.sql b/app/store/database/migrate/postgres/0050_create_table_public_resources.down.sql new file mode 100644 index 0000000000..8abdb9bf8b --- /dev/null +++ b/app/store/database/migrate/postgres/0050_create_table_public_resources.down.sql @@ -0,0 +1,28 @@ +-- copy public repositories +ALTER TABLE repositories ADD COLUMN repo_is_public BOOLEAN; + +-- update repo public access +UPDATE repositories +SET repo_is_public = TRUE +WHERE repo_id IN ( + SELECT public_access_repo_id + FROM public_access + WHERE public_access_repo_id IS NOT NULL +); + +-- copy public spaces +ALTER TABLE spaces ADD COLUMN space_is_public BOOLEAN; + +-- update space public access +UPDATE spaces +SET space_is_public = TRUE +WHERE space_id IN ( + SELECT public_access_space_id + FROM public_access + WHERE public_access_space_id IS NOT NULL +); + +-- clear public access +DROP INDEX public_access_space_id_key; +DROP INDEX public_access_repo_id_key; +DROP TABLE public_access; \ No newline at end of file diff --git a/app/store/database/migrate/postgres/0050_create_table_public_resources.up.sql b/app/store/database/migrate/postgres/0050_create_table_public_resources.up.sql new file mode 100644 index 0000000000..875c6bdc6e --- /dev/null +++ b/app/store/database/migrate/postgres/0050_create_table_public_resources.up.sql @@ -0,0 +1,46 @@ +CREATE TABLE public_access ( + public_access_id SERIAL PRIMARY KEY +,public_access_space_id INTEGER +,public_access_repo_id INTEGER + +,CONSTRAINT fk_public_access_space_id FOREIGN KEY (public_access_space_id) + REFERENCES spaces (space_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +,CONSTRAINT fk_public_access_repo_id FOREIGN KEY (public_access_repo_id) + REFERENCES repositories (repo_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX public_access_space_id_key + ON public_access(public_access_space_id) + WHERE public_access_space_id IS NOT NULL; + +CREATE UNIQUE INDEX public_access_repo_id_key + ON public_access(public_access_repo_id) + WHERE public_access_repo_id IS NOT NULL; + +-- move public repos into public_access +INSERT INTO public_access ( + public_access_repo_id +) +SELECT + repo_id +FROM repositories +WHERE repo_is_public = TRUE; + +-- alter repo table +ALTER TABLE repositories DROP COLUMN repo_is_public; + +-- move public spaces into public_access +INSERT INTO public_access ( + public_access_space_id +) +SELECT + space_id +FROM spaces +WHERE space_is_public = TRUE; + +-- alter space table +ALTER TABLE spaces DROP COLUMN space_is_public; diff --git a/app/store/database/migrate/sqlite/0050_create_table_public_resources.down.sql b/app/store/database/migrate/sqlite/0050_create_table_public_resources.down.sql new file mode 100644 index 0000000000..ed6ffdca98 --- /dev/null +++ b/app/store/database/migrate/sqlite/0050_create_table_public_resources.down.sql @@ -0,0 +1,28 @@ +-- copy public repositories +ALTER TABLE repositories ADD COLUMN repo_is_public BOOLEAN; + +-- update repo public access +UPDATE repositories +SET repo_is_public = TRUE +WHERE repo_id IN ( + SELECT public_access_repo_id + FROM public_access + WHERE public_access_repo_id IS NOT NULL +); + +-- copy public spaces +ALTER TABLE spaces ADD COLUMN space_is_public BOOLEAN; + +-- update space public access +UPDATE spaces +SET space_is_public = TRUE +WHERE space_id IN ( + SELECT public_access_space_id + FROM public_access + WHERE public_access_space_id IS NOT NULL +); + +-- clear public_access +DROP INDEX public_access_space_id_key; +DROP INDEX public_access_repo_id_key; +DROP TABLE public_access; \ No newline at end of file diff --git a/app/store/database/migrate/sqlite/0050_create_table_public_resources.up.sql b/app/store/database/migrate/sqlite/0050_create_table_public_resources.up.sql new file mode 100644 index 0000000000..9031fa714e --- /dev/null +++ b/app/store/database/migrate/sqlite/0050_create_table_public_resources.up.sql @@ -0,0 +1,46 @@ +CREATE TABLE public_access ( + public_access_id INTEGER PRIMARY KEY AUTOINCREMENT +,public_access_space_id INTEGER +,public_access_repo_id INTEGER + +,CONSTRAINT fk_public_access_space_id FOREIGN KEY (public_access_space_id) + REFERENCES spaces (space_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +,CONSTRAINT fk_public_access_repo_id FOREIGN KEY (public_access_repo_id) + REFERENCES repositories (repo_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +); + +CREATE UNIQUE INDEX public_access_space_id_key + ON public_access(public_access_space_id) + WHERE public_access_space_id IS NOT NULL; + +CREATE UNIQUE INDEX public_access_repo_id_key + ON public_access(public_access_repo_id) + WHERE public_access_repo_id IS NOT NULL; + +-- move public repos into public_access +INSERT INTO public_access ( + public_access_repo_id +) +SELECT + repo_id +FROM repositories +WHERE repo_is_public = TRUE; + +-- alter repo table +ALTER TABLE repositories DROP COLUMN repo_is_public; + +-- move public spaces into public_access +INSERT INTO public_access ( + public_access_space_id +) +SELECT + space_id +FROM spaces +WHERE space_is_public = TRUE; + +-- alter space table +ALTER TABLE spaces DROP COLUMN space_is_public; diff --git a/app/store/database/public_access.go b/app/store/database/public_access.go new file mode 100644 index 0000000000..400c89819c --- /dev/null +++ b/app/store/database/public_access.go @@ -0,0 +1,153 @@ +// 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 database + +import ( + "context" + "fmt" + + "github.com/harness/gitness/app/store" + "github.com/harness/gitness/store/database" + "github.com/harness/gitness/store/database/dbtx" + "github.com/harness/gitness/types/enum" + + "github.com/guregu/null" + "github.com/jmoiron/sqlx" +) + +var _ store.PublicAccessStore = (*PublicAccessStore)(nil) + +// NewPublicAccessStore returns a new PublicAccessStore. +func NewPublicAccessStore(db *sqlx.DB) *PublicAccessStore { + return &PublicAccessStore{ + db: db, + } +} + +// PublicAccessStore implements store.PublicAccessStore backed by a relational database. +type PublicAccessStore struct { + db *sqlx.DB +} + +type publicAccess struct { + ID int64 `db:"public_access_id"` + SpaceID null.Int `db:"public_access_space_id"` + RepoID null.Int `db:"public_access_repo_id"` +} + +const ( + publicAccessColumns = ` + public_access_id + ,public_access_space_id + ,public_access_repo_id + ` +) + +func (p *PublicAccessStore) Find( + ctx context.Context, + typ enum.PublicResourceType, + id int64, +) error { + stmt := database.Builder. + Select(publicAccessColumns). + From("public_access") + + switch typ { + case enum.PublicResourceTypeRepo: + stmt = stmt.Where("public_access_repo_id = ?", id) + case enum.PublicResourceTypeSpace: + stmt = stmt.Where("public_access_space_id = ?", id) + default: + return fmt.Errorf("public resource type %q is not supported", typ) + } + + sql, args, err := stmt.ToSql() + if err != nil { + return fmt.Errorf("failed to convert query to sql: %w", err) + } + + db := dbtx.GetAccessor(ctx, p.db) + + dst := &publicAccess{} + if err = db.GetContext(ctx, dst, sql, args...); err != nil { + return database.ProcessSQLErrorf(ctx, err, "Select query failed") + } + + return nil +} + +func (p *PublicAccessStore) Create( + ctx context.Context, + typ enum.PublicResourceType, + id int64, +) error { + stmt := database.Builder. + Insert(""). + Into("public_access") + + switch typ { + case enum.PublicResourceTypeRepo: + stmt = stmt.Columns("public_access_repo_id").Values(null.IntFrom(id)) + case enum.PublicResourceTypeSpace: + stmt = stmt.Columns("public_access_space_id").Values(null.IntFrom(id)) + default: + return fmt.Errorf("public resource type %q is not supported", typ) + } + + sql, args, err := stmt.ToSql() + if err != nil { + return fmt.Errorf("failed to convert query to sql: %w", err) + } + + db := dbtx.GetAccessor(ctx, p.db) + + if _, err := db.ExecContext(ctx, sql, args...); err != nil { + return database.ProcessSQLErrorf(ctx, err, "Insert query failed") + } + + return nil +} + +func (p *PublicAccessStore) Delete( + ctx context.Context, + typ enum.PublicResourceType, + id int64, +) error { + stmt := database.Builder. + Delete("public_access") + + switch typ { + case enum.PublicResourceTypeRepo: + stmt = stmt.Where("public_access_repo_id = ?", id) + case enum.PublicResourceTypeSpace: + stmt = stmt.Where("public_access_space_id = ?", id) + default: + return fmt.Errorf("public resource type %q is not supported", typ) + } + + sql, args, err := stmt.ToSql() + if err != nil { + return fmt.Errorf("failed to convert delete public resource query to sql: %w", err) + } + + db := dbtx.GetAccessor(ctx, p.db) + + _, err = db.ExecContext(ctx, sql, args...) + if err != nil { + return database.ProcessSQLErrorf(ctx, err, "the delete query failed") + } + + return nil +} diff --git a/app/store/database/repo.go b/app/store/database/repo.go index 0ca67d6db6..a8f5946597 100644 --- a/app/store/database/repo.go +++ b/app/store/database/repo.go @@ -67,7 +67,6 @@ type repository struct { ParentID int64 `db:"repo_parent_id"` Identifier string `db:"repo_uid"` Description string `db:"repo_description"` - IsPublic bool `db:"repo_is_public"` CreatedBy int64 `db:"repo_created_by"` Created int64 `db:"repo_created"` Updated int64 `db:"repo_updated"` @@ -98,7 +97,6 @@ const ( ,repo_parent_id ,repo_uid ,repo_description - ,repo_is_public ,repo_created_by ,repo_created ,repo_updated @@ -223,7 +221,6 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error { ,repo_parent_id ,repo_uid ,repo_description - ,repo_is_public ,repo_created_by ,repo_created ,repo_updated @@ -246,7 +243,6 @@ func (s *RepoStore) Create(ctx context.Context, repo *types.Repository) error { ,:repo_parent_id ,:repo_uid ,:repo_description - ,:repo_is_public ,:repo_created_by ,:repo_created ,:repo_updated @@ -298,7 +294,6 @@ func (s *RepoStore) Update(ctx context.Context, repo *types.Repository) error { ,repo_uid = :repo_uid ,repo_git_uid = :repo_git_uid ,repo_description = :repo_description - ,repo_is_public = :repo_is_public ,repo_default_branch = :repo_default_branch ,repo_pullreq_seq = :repo_pullreq_seq ,repo_num_forks = :repo_num_forks @@ -734,7 +729,6 @@ func (s *RepoStore) mapToRepo( ParentID: in.ParentID, Identifier: in.Identifier, Description: in.Description, - IsPublic: in.IsPublic, Created: in.Created, CreatedBy: in.CreatedBy, Updated: in.Updated, @@ -818,7 +812,6 @@ func mapToInternalRepo(in *types.Repository) *repository { ParentID: in.ParentID, Identifier: in.Identifier, Description: in.Description, - IsPublic: in.IsPublic, Created: in.Created, CreatedBy: in.CreatedBy, Updated: in.Updated, diff --git a/app/store/database/space.go b/app/store/database/space.go index 01f3117632..7a71f73f4a 100644 --- a/app/store/database/space.go +++ b/app/store/database/space.go @@ -65,7 +65,6 @@ type space struct { ParentID null.Int `db:"space_parent_id"` Identifier string `db:"space_uid"` Description string `db:"space_description"` - IsPublic bool `db:"space_is_public"` CreatedBy int64 `db:"space_created_by"` Created int64 `db:"space_created"` Updated int64 `db:"space_updated"` @@ -79,7 +78,6 @@ const ( ,space_parent_id ,space_uid ,space_description - ,space_is_public ,space_created_by ,space_created ,space_updated @@ -233,7 +231,6 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error { ,space_parent_id ,space_uid ,space_description - ,space_is_public ,space_created_by ,space_created ,space_updated @@ -243,7 +240,6 @@ func (s *SpaceStore) Create(ctx context.Context, space *types.Space) error { ,:space_parent_id ,:space_uid ,:space_description - ,:space_is_public ,:space_created_by ,:space_created ,:space_updated @@ -278,7 +274,6 @@ func (s *SpaceStore) Update(ctx context.Context, space *types.Space) error { ,space_parent_id = :space_parent_id ,space_uid = :space_uid ,space_description = :space_description - ,space_is_public = :space_is_public ,space_deleted = :space_deleted WHERE space_id = :space_id AND space_version = :space_version - 1` @@ -722,7 +717,6 @@ func mapToSpace( Version: in.Version, Identifier: in.Identifier, Description: in.Description, - IsPublic: in.IsPublic, Created: in.Created, CreatedBy: in.CreatedBy, Updated: in.Updated, @@ -783,7 +777,6 @@ func mapToInternalSpace(s *types.Space) *space { Version: s.Version, Identifier: s.Identifier, Description: s.Description, - IsPublic: s.IsPublic, Created: s.Created, CreatedBy: s.CreatedBy, Updated: s.Updated, diff --git a/app/store/database/wire.go b/app/store/database/wire.go index 369b46f232..4078195a25 100644 --- a/app/store/database/wire.go +++ b/app/store/database/wire.go @@ -53,6 +53,7 @@ var WireSet = wire.NewSet( ProvideWebhookStore, ProvideWebhookExecutionStore, ProvideSettingsStore, + ProvidePublicAccessStore, ProvideCheckStore, ProvideConnectorStore, ProvideTemplateStore, @@ -247,3 +248,8 @@ func ProvideCheckStore(db *sqlx.DB, func ProvideSettingsStore(db *sqlx.DB) store.SettingsStore { return NewSettingsStore(db) } + +// ProvidePublicAccessStore provides a pulic access store. +func ProvidePublicAccessStore(db *sqlx.DB) store.PublicAccessStore { + return NewPublicAccessStore(db) +} diff --git a/cmd/gitness/wire.go b/cmd/gitness/wire.go index 236f92ffd2..6e8058ac9e 100644 --- a/cmd/gitness/wire.go +++ b/cmd/gitness/wire.go @@ -63,6 +63,7 @@ import ( "github.com/harness/gitness/app/services/notification" "github.com/harness/gitness/app/services/notification/mailer" "github.com/harness/gitness/app/services/protection" + "github.com/harness/gitness/app/services/publicaccess" pullreqservice "github.com/harness/gitness/app/services/pullreq" reposervice "github.com/harness/gitness/app/services/repo" "github.com/harness/gitness/app/services/settings" @@ -114,6 +115,7 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e url.WireSet, space.WireSet, limiter.WireSet, + publicaccess.WireSet, repo.WireSet, reposettings.WireSet, pullreq.WireSet, diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index a9b6870406..149fac5673 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -62,6 +62,7 @@ import ( "github.com/harness/gitness/app/services/notification" "github.com/harness/gitness/app/services/notification/mailer" "github.com/harness/gitness/app/services/protection" + "github.com/harness/gitness/app/services/publicaccess" "github.com/harness/gitness/app/services/pullreq" repo2 "github.com/harness/gitness/app/services/repo" "github.com/harness/gitness/app/services/settings" @@ -114,7 +115,10 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro principalInfoCache := cache.ProvidePrincipalInfoCache(principalInfoView) membershipStore := database.ProvideMembershipStore(db, principalInfoCache, spacePathStore, spaceStore) permissionCache := authz.ProvidePermissionCache(spaceStore, membershipStore) - authorizer := authz.ProvideAuthorizer(permissionCache, spaceStore) + publicAccessStore := database.ProvidePublicAccessStore(db) + repoStore := database.ProvideRepoStore(db, spacePathCache, spacePathStore, spaceStore) + publicaccessService := publicaccess.ProvidePublicAccess(config, publicAccessStore, repoStore, spaceStore) + authorizer := authz.ProvideAuthorizer(permissionCache, spaceStore, publicaccessService) principalUIDTransformation := store.ProvidePrincipalUIDTransformation() principalStore := database.ProvidePrincipalStore(db, principalUIDTransformation) tokenStore := database.ProvideTokenStore(db) @@ -126,7 +130,6 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro if err != nil { return nil, err } - repoStore := database.ProvideRepoStore(db, spacePathCache, spacePathStore, spaceStore) pipelineStore := database.ProvidePipelineStore(db) ruleStore := database.ProvideRuleStore(db, principalInfoCache) settingsStore := database.ProvideSettingsStore(db) @@ -173,7 +176,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro streamer := sse.ProvideEventsStreaming(pubSub) localIndexSearcher := keywordsearch.ProvideLocalIndexSearcher() indexer := keywordsearch.ProvideIndexer(localIndexSearcher) - repository, err := importer.ProvideRepoImporter(config, provider, gitInterface, transactor, repoStore, pipelineStore, triggerStore, encrypter, jobScheduler, executor, streamer, indexer) + repository, err := importer.ProvideRepoImporter(config, provider, gitInterface, transactor, repoStore, pipelineStore, triggerStore, encrypter, jobScheduler, executor, streamer, indexer, publicaccessService) if err != nil { return nil, err } @@ -197,7 +200,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro auditService := audit.ProvideAuditService() repoIdentifier := check.ProvideRepoIdentifierCheck() repoCheck := repo.ProvideRepoCheck() - repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, ruleStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, auditService, mutexManager, repoIdentifier, repoCheck) + repoController := repo.ProvideController(config, transactor, provider, authorizer, repoStore, spaceStore, pipelineStore, principalStore, ruleStore, settingsService, principalInfoCache, protectionManager, gitInterface, repository, codeownersService, reporter, indexer, resourceLimiter, lockerLocker, auditService, mutexManager, repoIdentifier, repoCheck, publicaccessService) reposettingsController := reposettings.ProvideController(authorizer, repoStore, settingsService, auditService) executionStore := database.ProvideExecutionStore(db) checkStore := database.ProvideCheckStore(db, principalInfoCache) @@ -213,7 +216,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro converterService := converter.ProvideService(fileService) templateStore := database.ProvideTemplateStore(db) pluginStore := database.ProvidePluginStore(db) - triggererTriggerer := triggerer.ProvideTriggerer(executionStore, checkStore, stageStore, transactor, pipelineStore, fileService, converterService, schedulerScheduler, repoStore, provider, templateStore, pluginStore) + triggererTriggerer := triggerer.ProvideTriggerer(executionStore, checkStore, stageStore, transactor, pipelineStore, fileService, converterService, schedulerScheduler, repoStore, provider, templateStore, pluginStore, publicaccessService) executionController := execution.ProvideController(transactor, authorizer, executionStore, checkStore, cancelerCanceler, commitService, triggererTriggerer, repoStore, stageStore, pipelineStore) logStore := logs.ProvideLogStore(db, config) logStream := livelog.ProvideLogStream() @@ -225,7 +228,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro if err != nil { return nil, err } - spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, repository, exporterRepository, resourceLimiter, auditService) + spaceController := space.ProvideController(config, transactor, provider, streamer, spaceIdentifier, authorizer, spacePathStore, pipelineStore, secretStore, connectorStore, templateStore, spaceStore, repoStore, principalStore, repoController, membershipStore, repository, exporterRepository, resourceLimiter, publicaccessService, auditService) pipelineController := pipeline.ProvideController(repoStore, triggerStore, authorizer, pipelineStore) secretController := secret.ProvideController(encrypter, secretStore, authorizer, spaceStore) triggerController := trigger.ProvideController(authorizer, triggerStore, pipelineStore, repoStore) @@ -284,7 +287,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro } githookController := githook.ProvideController(authorizer, principalStore, repoStore, reporter2, reporter, gitInterface, pullReqStore, provider, protectionManager, clientFactory, resourceLimiter, settingsService, preReceiveExtender, updateExtender, postReceiveExtender) serviceaccountController := serviceaccount.NewController(principalUID, authorizer, principalStore, spaceStore, repoStore, tokenStore) - principalController := principal.ProvideController(principalStore) + principalController := principal.ProvideController(principalStore, authorizer) v := check2.ProvideCheckSanitizers() checkController := check2.ProvideController(transactor, authorizer, repoStore, checkStore, gitInterface, v) systemController := system.NewController(principalStore, config) @@ -305,7 +308,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro webHandler := router.ProvideWebHandler(config, openapiService) routerRouter := router.ProvideRouter(apiHandler, gitHandler, webHandler, provider) serverServer := server2.ProvideServer(config, routerRouter) - executionManager := manager.ProvideExecutionManager(config, executionStore, pipelineStore, provider, streamer, fileService, converterService, logStore, logStream, checkStore, repoStore, schedulerScheduler, secretStore, stageStore, stepStore, principalStore) + executionManager := manager.ProvideExecutionManager(config, executionStore, pipelineStore, provider, streamer, fileService, converterService, logStore, logStream, checkStore, repoStore, schedulerScheduler, secretStore, stageStore, stepStore, principalStore, publicaccessService) client := manager.ProvideExecutionClient(executionManager, provider, config) resolverManager := resolver.ProvideResolver(config, pluginStore, templateStore, executionStore, repoStore) runtimeRunner, err := runner.ProvideExecutionRunner(config, client, resolverManager) diff --git a/types/check/common.go b/types/check/common.go index e2c0ac2e15..640581fc8f 100644 --- a/types/check/common.go +++ b/types/check/common.go @@ -18,6 +18,8 @@ import ( "fmt" "regexp" "strings" + + "github.com/harness/gitness/types" ) const ( @@ -74,6 +76,10 @@ var ( ErrIllegalRepoSpaceIdentifierSuffix = &ValidationError{ fmt.Sprintf("Space and repository identifiers cannot end with %q.", illegalRepoSpaceIdentifierSuffix), } + + ErrIllegalPrincipalUID = &ValidationError{ + fmt.Sprintf("Principal UID is not allowed to be %q.", types.AnonymousPrincipalUID), + } ) // DisplayName checks the provided display name and returns an error if it isn't valid. @@ -143,7 +149,15 @@ type PrincipalUID func(uid string) error // PrincipalUIDDefault performs the default Principal UID check. func PrincipalUIDDefault(uid string) error { - return Identifier(uid) + if err := Identifier(uid); err != nil { + return err + } + + if uid == types.AnonymousPrincipalUID { + return ErrIllegalPrincipalUID + } + + return nil } // SpaceIdentifier is an abstraction of a validation method that returns true diff --git a/types/enum/membership_role.go b/types/enum/membership_role.go index 31547de1a5..3ca27a2707 100644 --- a/types/enum/membership_role.go +++ b/types/enum/membership_role.go @@ -50,6 +50,7 @@ var membershipRoleExecutorPermissions = slices.Clip(slices.Insert(membershipRole var membershipRoleContributorPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, PermissionRepoPush, + PermissionRepoReview, )) var membershipRoleSpaceOwnerPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, @@ -57,6 +58,7 @@ var membershipRoleSpaceOwnerPermissions = slices.Clip(slices.Insert(membershipRo PermissionRepoDelete, PermissionRepoPush, PermissionRepoReportCommitCheck, + PermissionRepoReview, PermissionSpaceEdit, PermissionSpaceDelete, diff --git a/types/enum/permission.go b/types/enum/permission.go index 61912ef9c5..38d2185e92 100644 --- a/types/enum/permission.go +++ b/types/enum/permission.go @@ -49,6 +49,7 @@ const ( PermissionRepoEdit Permission = "repo_edit" PermissionRepoDelete Permission = "repo_delete" PermissionRepoPush Permission = "repo_push" + PermissionRepoReview Permission = "repo_review" PermissionRepoReportCommitCheck Permission = "repo_reportCommitCheck" ) diff --git a/types/enum/public_resource.go b/types/enum/public_resource.go new file mode 100644 index 0000000000..50b2e048e6 --- /dev/null +++ b/types/enum/public_resource.go @@ -0,0 +1,33 @@ +// +// 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 enum + +// PublicResourceType defines the type of the public resource. +type PublicResourceType string + +func (PublicResourceType) Enum() []interface{} { + return toInterfaceSlice(GetAllPublicResourceTypes()) +} + +const ( + PublicResourceTypeRepo PublicResourceType = "repository" + PublicResourceTypeSpace PublicResourceType = "space" +) + +func GetAllPublicResourceTypes() []PublicResourceType { + return []PublicResourceType{ + PublicResourceTypeRepo, + PublicResourceTypeSpace, + } +} diff --git a/types/principal.go b/types/principal.go index 52159e8914..ef6b2e3c8e 100644 --- a/types/principal.go +++ b/types/principal.go @@ -19,6 +19,9 @@ import ( "github.com/harness/gitness/types/enum" ) +// AnonymousPrincipalUID is an internal UID for anonymous principals. +const AnonymousPrincipalUID = "anonymous" + // Principal represents the identity of an acting entity (User, ServiceAccount, Service). type Principal struct { // TODO: int64 ID doesn't match DB diff --git a/types/repo.go b/types/repo.go index 4020c40ad0..497f14e24c 100644 --- a/types/repo.go +++ b/types/repo.go @@ -15,8 +15,6 @@ package types import ( - "encoding/json" - "github.com/harness/gitness/types/enum" ) @@ -29,7 +27,6 @@ type Repository struct { Identifier string `json:"identifier" yaml:"identifier"` Path string `json:"path" yaml:"path"` Description string `json:"description" yaml:"description"` - IsPublic bool `json:"is_public" yaml:"is_public"` CreatedBy int64 `json:"created_by" yaml:"created_by"` Created int64 `json:"created" yaml:"created"` Updated int64 `json:"updated" yaml:"updated"` @@ -66,20 +63,8 @@ func (r Repository) Clone() Repository { deleted = &id } r.Deleted = deleted - return r -} -// TODO [CODE-1363]: remove after identifier migration. -func (r Repository) MarshalJSON() ([]byte, error) { - // alias allows us to embed the original object while avoiding an infinite loop of marshaling. - type alias Repository - return json.Marshal(&struct { - alias - UID string `json:"uid"` - }{ - alias: (alias)(r), - UID: r.Identifier, - }) + return r } type RepositorySizeInfo struct { diff --git a/types/space.go b/types/space.go index 6140b0b676..869f3003d0 100644 --- a/types/space.go +++ b/types/space.go @@ -39,7 +39,6 @@ type Space struct { Path string `json:"path"` Identifier string `json:"identifier"` Description string `json:"description"` - IsPublic bool `json:"is_public"` CreatedBy int64 `json:"created_by"` Created int64 `json:"created"` Updated int64 `json:"updated"` diff --git a/web/src/App.tsx b/web/src/App.tsx index effac21cb7..8c6e72e478 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -48,7 +48,9 @@ const App: React.FC = React.memo(function App({ hooks, customComponents, currentUserProfileURL = '', - defaultSettingsURL = '' + defaultSettingsURL = '', + isPublicAccessEnabledOnResources = false, + isCurrentSessionPublic = false }: AppProps) { const [strings, setStrings] = useState() const getRequestOptions = useCallback( @@ -90,7 +92,9 @@ const App: React.FC = React.memo(function App({ currentUser: defaultCurrentUser, customComponents, currentUserProfileURL, - defaultSettingsURL + defaultSettingsURL, + isPublicAccessEnabledOnResources, + isCurrentSessionPublic }}> ({ }, currentUserProfileURL: '', routingId: '', - defaultSettingsURL: '' + defaultSettingsURL: '', + isPublicAccessEnabledOnResources: false, + isCurrentSessionPublic: false }) export const AppContextProvider: React.FC<{ value: AppProps }> = React.memo(function AppContextProvider({ diff --git a/web/src/AppProps.ts b/web/src/AppProps.ts index c4ae2d687a..ecd93f7f9e 100644 --- a/web/src/AppProps.ts +++ b/web/src/AppProps.ts @@ -78,4 +78,6 @@ export interface AppProps { currentUserProfileURL: string defaultSettingsURL: string + isPublicAccessEnabledOnResources: boolean + isCurrentSessionPublic: boolean } diff --git a/web/src/bootstrap.tsx b/web/src/bootstrap.tsx index a712f307c2..8b25b7c42c 100644 --- a/web/src/bootstrap.tsx +++ b/web/src/bootstrap.tsx @@ -50,6 +50,8 @@ ReactDOM.render( currentUserProfileURL="" routingId="" defaultSettingsURL="" + isPublicAccessEnabledOnResources + isCurrentSessionPublic={false} />, document.getElementById('react-root') ) diff --git a/web/src/components/CodeSearchBar/CodeSearchBar.tsx b/web/src/components/CodeSearchBar/CodeSearchBar.tsx index 599e68ac4f..4411345b8c 100644 --- a/web/src/components/CodeSearchBar/CodeSearchBar.tsx +++ b/web/src/components/CodeSearchBar/CodeSearchBar.tsx @@ -42,7 +42,7 @@ const KEYWORD_REGEX = /((?:(?:-{0,1})(?:repo|lang|file|case|count)):\S*|(?: or|a const CodeSearchBar: FC = ({ value, onChange, onSearch, onKeyDown, searchMode, setSearchMode }) => { const { getString } = useStrings() - const { hooks, routingId, defaultSettingsURL } = useAppContext() + const { hooks, routingId, defaultSettingsURL, isCurrentSessionPublic } = useAppContext() const { SEMANTIC_SEARCH_ENABLED: isSemanticSearchFFEnabled } = hooks?.useFeatureFlags() const { orgIdentifier, projectIdentifier } = useParams() const { data: aidaSettingResponse, loading: isAidaSettingLoading } = hooks?.useGetSettingValue({ @@ -51,7 +51,9 @@ const CodeSearchBar: FC = ({ value, onChange, onSearch, onKe }) const [enableSemanticSearch, setEnableSemanticSearch] = useState(false) useEffect(() => { - setEnableSemanticSearch(isSemanticSearchFFEnabled && aidaSettingResponse?.data?.value == 'true') + setEnableSemanticSearch( + isSemanticSearchFFEnabled && aidaSettingResponse?.data?.value == 'true' && !isCurrentSessionPublic + ) }, [isAidaSettingLoading, isSemanticSearchFFEnabled]) const isSemanticMode = enableSemanticSearch && searchMode === SEARCH_MODE.SEMANTIC return ( diff --git a/web/src/components/RepositoryPageHeader/RepositoryPageHeader.module.scss b/web/src/components/RepositoryPageHeader/RepositoryPageHeader.module.scss index d5194f3a0a..cc6ea5b837 100644 --- a/web/src/components/RepositoryPageHeader/RepositoryPageHeader.module.scss +++ b/web/src/components/RepositoryPageHeader/RepositoryPageHeader.module.scss @@ -26,3 +26,7 @@ .breadcrumb { align-items: center; } + +.hideBreadcrumbs { + display: none !important; +} diff --git a/web/src/components/RepositoryPageHeader/RepositoryPageHeader.module.scss.d.ts b/web/src/components/RepositoryPageHeader/RepositoryPageHeader.module.scss.d.ts index 71430f2e2e..3952f14a78 100644 --- a/web/src/components/RepositoryPageHeader/RepositoryPageHeader.module.scss.d.ts +++ b/web/src/components/RepositoryPageHeader/RepositoryPageHeader.module.scss.d.ts @@ -18,3 +18,4 @@ // This is an auto-generated file export declare const breadcrumb: string export declare const header: string +export declare const hideBreadcrumbs: string diff --git a/web/src/components/RepositoryPageHeader/RepositoryPageHeader.tsx b/web/src/components/RepositoryPageHeader/RepositoryPageHeader.tsx index bc6b62e7a0..ade5451d25 100644 --- a/web/src/components/RepositoryPageHeader/RepositoryPageHeader.tsx +++ b/web/src/components/RepositoryPageHeader/RepositoryPageHeader.tsx @@ -15,6 +15,7 @@ */ import React, { Fragment } from 'react' +import cx from 'classnames' import { Container, Layout, Text, PageHeader, PageHeaderProps } from '@harnessio/uicore' import { Icon } from '@harnessio/icons' import { Color, FontVariation } from '@harnessio/design-system' @@ -50,7 +51,7 @@ export function RepositoryPageHeader({ const { gitRef } = useParams() const { getString } = useStrings() const space = useGetSpaceParam() - const { routes } = useAppContext() + const { routes, isCurrentSessionPublic } = useAppContext() return ( - + {getString('repositories')} diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index 4b114ff301..38ea9c6494 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -179,6 +179,7 @@ export interface StringsMap { confirmNewPassword: string confirmPassRequired: string confirmPassword: string + confirmRepoVisButton: string confirmation: string content: string contents: string @@ -206,6 +207,7 @@ export interface StringsMap { 'createRepoModal.branchLabel': string 'createRepoModal.privateLabel': string 'createRepoModal.publicLabel': string + 'createRepoModal.publicWarning': string createRepoPerms: string createSpace: string createTag: string diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index d9bd67c9a7..ae03fe347a 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -122,6 +122,7 @@ createRepoModal: branch: ' branch.' publicLabel: Anyone with access to the Gitness environment can clone this repo. privateLabel: You choose who can see and commit to this repository. + publicWarning: Please note that anyone with access to the Gitness environment can clone this repo. validation: repoNamePatternIsNotValid: "Name can only contain alphanumerics, '-', '_', '.', and '$'" gitBranchNameInvalid: Branch name is invalid. @@ -893,7 +894,8 @@ enterGitlabPlaceholder: https://gitlab.com/ enterGithubPlaceholder: https://api.github.com/ enterBitbucketPlaceholder: https://bitbucket.org/ changeRepoVis: Change repository visibility -changeRepoVisContent: Are you sure you want to make this repository {repoVis}? {repoText} +changeRepoVisContent: Are you sure you want to make this repository {repoVis}? +confirmRepoVisButton: Yes, make the Repository {repoVis} repoVisibility: Repository visibility visibility: Visibility attachText: Attach images & videos by dragging & dropping, selecting or pasting them. diff --git a/web/src/pages/Repository/RepositoryContent/ContentHeader/ContentHeader.tsx b/web/src/pages/Repository/RepositoryContent/ContentHeader/ContentHeader.tsx index bba6f051ef..a90f34cd4e 100644 --- a/web/src/pages/Repository/RepositoryContent/ContentHeader/ContentHeader.tsx +++ b/web/src/pages/Repository/RepositoryContent/ContentHeader/ContentHeader.tsx @@ -43,7 +43,7 @@ export function ContentHeader({ resourceContent }: Pick) { const { getString } = useStrings() - const { routes, standalone, hooks } = useAppContext() + const { routes, standalone, hooks, isCurrentSessionPublic } = useAppContext() const history = useHistory() const _isDir = isDir(resourceContent) const space = useGetSpaceParam() @@ -164,7 +164,9 @@ export function ContentHeader({ )} -
{!standalone ? : null}
+
+ {!standalone && !isCurrentSessionPublic ? : null} +
) } diff --git a/web/src/pages/RepositorySettings/GeneralSettingsContent/GeneralSettingsContent.tsx b/web/src/pages/RepositorySettings/GeneralSettingsContent/GeneralSettingsContent.tsx index 1b149ba080..6dff3e5528 100644 --- a/web/src/pages/RepositorySettings/GeneralSettingsContent/GeneralSettingsContent.tsx +++ b/web/src/pages/RepositorySettings/GeneralSettingsContent/GeneralSettingsContent.tsx @@ -65,8 +65,7 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => { const { showError, showSuccess } = useToaster() const space = useGetSpaceParam() - const { standalone } = useAppContext() - const { hooks } = useAppContext() + const { standalone, hooks, isPublicAccessEnabledOnResources } = useAppContext() const { getString } = useStrings() const currRepoVisibility = repoMetadata?.is_public === true ? RepoVisibility.PUBLIC : RepoVisibility.PRIVATE @@ -77,6 +76,11 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => { path: `/api/v1/repos/${repoMetadata?.path}/+/` }) + const { mutate: changeVisibility } = useMutate({ + verb: 'POST', + path: `/api/v1/repos/${repoMetadata?.path}/+/public-access` + }) + const permEditResult = hooks?.usePermissionTranslate?.( { resource: { @@ -109,50 +113,71 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => { return ( {getString('changeRepoVis')}} isOpen onClose={hideModal}> - - {repoVis}, - repoText: - repoVis === RepoVisibility.PUBLIC - ? getString('createRepoModal.publicLabel') - : getString('createRepoModal.privateLabel') - }} - /> - -
- -
) } @@ -315,7 +340,7 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => {
- + @@ -330,6 +355,7 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => { onChange={evt => { setRepoVis((evt.target as HTMLInputElement).value as RepoVisibility) }} + {...permissionProps(permEditResult, standalone)} className={css.radioContainer} items={[ { @@ -391,6 +417,7 @@ const GeneralSettingsContent = (props: GeneralSettingsProps) => { setRepoVis(formik.values.isPublic) openModal() }} + {...permissionProps(permEditResult, standalone)} /> ) : null} diff --git a/web/src/pages/RepositorySettings/RepositorySettings.module.scss b/web/src/pages/RepositorySettings/RepositorySettings.module.scss index 4b25b2eab6..ad3329136f 100644 --- a/web/src/pages/RepositorySettings/RepositorySettings.module.scss +++ b/web/src/pages/RepositorySettings/RepositorySettings.module.scss @@ -122,8 +122,6 @@ } .dialogContainer { - padding-bottom: 27px !important; - :global(.bp3-dialog-header) { margin-bottom: var(--spacing-medium) !important; }