diff --git a/models/repo/repo.go b/models/repo/repo.go index 7814bb4876..590c62795b 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -303,6 +303,12 @@ func (repo *Repository) IsBeingCreated() bool { return repo.IsBeingMigrated() } +// IsSystemRepo returns true when the repository name starts with "." +// System repositories are always private and cannot be made public. +func (repo *Repository) IsSystemRepo() bool { + return strings.HasPrefix(repo.Name, ".") +} + // IsBroken indicates that repository is broken func (repo *Repository) IsBroken() bool { return repo.Status == RepositoryBroken diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index c7ec133e57..2eb1b70af2 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -2496,6 +2496,7 @@ "repo.settings.visibility.success": "Repository visibility changed.", "repo.settings.visibility.error": "An error occurred while trying to change the repo visibility.", "repo.settings.visibility.fork_error": "Can't change the visibility of a forked repo.", + "repo.settings.visibility.system_repo_private": "System repositories (dot-prefixed names) are always private and cannot be made public.", "repo.settings.archive.button": "Archive Repo", "repo.settings.archive.header": "Archive This Repo", "repo.settings.archive.text": "Archiving the repo will make it entirely read-only. It will be hidden from the dashboard. Nobody (not even you!) will be able to make new commits, or open any issues or pull requests.", diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index 7431493a3f..6e09af98e9 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -139,7 +139,7 @@ func Migrate(ctx *context.APIContext) { CloneAddr: remoteAddr, RepoName: form.RepoName, Description: form.Description, - Private: form.Private || setting.Repository.ForcePrivate, + Private: form.Private || setting.Repository.ForcePrivate || strings.HasPrefix(form.RepoName, "."), Mirror: form.Mirror, LFS: form.LFS, LFSEndpoint: form.LFSEndpoint, @@ -174,7 +174,7 @@ func Migrate(ctx *context.APIContext) { Description: opts.Description, OriginalURL: form.CloneAddr, GitServiceType: gitServiceType, - IsPrivate: opts.Private || setting.Repository.ForcePrivate, + IsPrivate: opts.Private || setting.Repository.ForcePrivate || strings.HasPrefix(opts.RepoName, "."), IsMirror: opts.Mirror, Status: repo_model.RepositoryBeingMigrated, }, false) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 8b0dc7c863..641a6f447d 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -253,6 +253,9 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre return } + // System repos (dot-prefixed names) are always private. + isPrivate := opt.Private || setting.Repository.ForcePrivate || strings.HasPrefix(opt.Name, ".") + repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_service.CreateRepoOptions{ Name: opt.Name, Description: opt.Description, @@ -260,7 +263,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre Gitignores: opt.Gitignores, License: opt.License, Readme: opt.Readme, - IsPrivate: opt.Private || setting.Repository.ForcePrivate, + IsPrivate: isPrivate, AutoInit: opt.AutoInit, DefaultBranch: opt.DefaultBranch, TrustModel: repo_model.ToTrustModel(opt.TrustModel), @@ -700,6 +703,12 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err } visibilityChanged = repo.IsPrivate != *opts.Private + // System repos (dot-prefixed) cannot be made public, regardless of user role. + if visibilityChanged && !*opts.Private && repo.IsSystemRepo() { + err := errors.New("system repositories (dot-prefixed) cannot be made public") + ctx.APIError(http.StatusForbidden, err) + return err + } // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.Doer.IsAdmin { err := errors.New("cannot change private repository to public") diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index 3d070a18ab..903b2c7fac 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -210,10 +210,15 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { // FIXME: these options are not quite right, for example: changing visibility should do more works than just setting the is_private flag // These options should only be used for "push-to-create" if isPrivate.Has() && repo.IsPrivate != isPrivate.Value() { - // TODO: it needs to do more work - repo.IsPrivate = isPrivate.Value() - if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil { - ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Failed to change visibility"}) + // System repos (dot-prefixed) are always private — ignore attempts to make them public. + if !isPrivate.Value() && repo.IsSystemRepo() { + log.Warn("Ignoring push option to make system repo %s public", repo.FullName()) + } else { + // TODO: it needs to do more work + repo.IsPrivate = isPrivate.Value() + if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil { + ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{Err: "Failed to change visibility"}) + } } } if isTemplate.Has() && repo.IsTemplate != isTemplate.Value() { diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go index bb6f1e6b7e..e0fe917243 100644 --- a/routers/web/repo/migrate.go +++ b/routers/web/repo/migrate.go @@ -210,7 +210,7 @@ func MigratePost(ctx *context.Context) { CloneAddr: remoteAddr, RepoName: form.RepoName, Description: form.Description, - Private: form.Private || setting.Repository.ForcePrivate, + Private: form.Private || setting.Repository.ForcePrivate || strings.HasPrefix(form.RepoName, "."), Mirror: form.Mirror, LFS: form.LFS, LFSEndpoint: form.LFSEndpoint, diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index c7813feae2..538074250b 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -243,7 +243,7 @@ func CreatePost(ctx *context.Context) { opts := repo_service.GenerateRepoOptions{ Name: form.RepoName, Description: form.Description, - Private: form.Private || setting.Repository.ForcePrivate, + Private: form.Private || setting.Repository.ForcePrivate || strings.HasPrefix(form.RepoName, "."), GitContent: form.GitContent, Topics: form.Topics, GitHooks: form.GitHooks, @@ -282,7 +282,7 @@ func CreatePost(ctx *context.Context) { IssueLabels: form.IssueLabels, License: form.License, Readme: form.Readme, - IsPrivate: form.Private || setting.Repository.ForcePrivate, + IsPrivate: form.Private || setting.Repository.ForcePrivate || strings.HasPrefix(form.RepoName, "."), DefaultBranch: form.DefaultBranch, AutoInit: form.AutoInit, IsTemplate: form.Template, diff --git a/routers/web/repo/setting/public_access.go b/routers/web/repo/setting/public_access.go index 368d34294a..1f9180383c 100644 --- a/routers/web/repo/setting/public_access.go +++ b/routers/web/repo/setting/public_access.go @@ -127,14 +127,22 @@ func repoUnitPublicAccesses(ctx *context.Context) []*repoUnitPublicAccess { func PublicAccess(ctx *context.Context) { ctx.Data["PageIsSettingsPublicAccess"] = true ctx.Data["RepoUnitPublicAccesses"] = repoUnitPublicAccesses(ctx) - ctx.Data["GlobalForcePrivate"] = setting.Repository.ForcePrivate - if setting.Repository.ForcePrivate { + ctx.Data["GlobalForcePrivate"] = setting.Repository.ForcePrivate || ctx.Repo.Repository.IsSystemRepo() + if ctx.Repo.Repository.IsSystemRepo() { + ctx.Flash.Error(ctx.Tr("repo.settings.visibility.system_repo_private"), true) + } else if setting.Repository.ForcePrivate { ctx.Flash.Error(ctx.Tr("form.repository_force_private"), true) } ctx.HTML(http.StatusOK, tplRepoSettingsPublicAccess) } func PublicAccessPost(ctx *context.Context) { + // System repos (dot-prefixed) cannot have public access on any unit. + if ctx.Repo.Repository.IsSystemRepo() { + ctx.Flash.Error(ctx.Tr("repo.settings.visibility.system_repo_private"), true) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/public_access") + return + } accesses := repoUnitPublicAccesses(ctx) for _, ua := range accesses { formVal := ctx.FormString(ua.FormKey) diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 816fd91cd8..c94499ad6e 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -54,7 +54,7 @@ const ( func SettingsCtxData(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings.options") ctx.Data["PageIsSettingsOptions"] = true - ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate + ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate || ctx.Repo.Repository.IsSystemRepo() ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush @@ -103,7 +103,7 @@ func Settings(ctx *context.Context) { // SettingsPost response for changes of a repository func SettingsPost(ctx *context.Context) { - ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate + ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate || ctx.Repo.Repository.IsSystemRepo() ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush @@ -1008,6 +1008,11 @@ func handleSettingsPostVisibility(ctx *context.Context) { private := ctx.FormOptionalBool("private").ValueOrDefault(true) // default to true for privacy & safety + // System repos (dot-prefixed) cannot be made public, regardless of user role. + if !private && repo.IsSystemRepo() { + ctx.JSONError(ctx.Tr("repo.settings.visibility.system_repo_private")) + return + } // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public if !private && setting.Repository.ForcePrivate && !ctx.Doer.IsAdmin { ctx.JSONError(ctx.Tr("form.repository_force_private")) diff --git a/services/repository/repository.go b/services/repository/repository.go index 2ac95ffe3a..d285480442 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -123,6 +123,10 @@ func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibili } func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository, private bool) (err error) { + // System repos (dot-prefixed) are always private and cannot be made public. + if !private && repo.IsSystemRepo() { + return errors.New("system repositories (dot-prefixed) cannot be made public") + } return db.WithTx(ctx, func(ctx context.Context) error { repo.IsPrivate = private if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {