mirror of
https://github.com/go-gitea/gitea.git
synced 2025-04-20 00:19:08 +03:00
Add anonymous access support for private/unlisted repositories (#34051)
Follow #33127 Fix #8649, fix #639 This is a complete solution. A repo unit could be set to: * Anonymous read (non-signed-in user) * Everyone read (signed-in user) * Everyone write (wiki-only)
This commit is contained in:
@ -15,6 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ func (p *Permission) HasAnyUnitAccess() bool {
|
|||||||
return p.AccessMode >= perm_model.AccessModeRead
|
return p.AccessMode >= perm_model.AccessModeRead
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool {
|
func (p *Permission) HasAnyUnitPublicAccess() bool {
|
||||||
for _, v := range p.anonymousAccessMode {
|
for _, v := range p.anonymousAccessMode {
|
||||||
if v >= perm_model.AccessModeRead {
|
if v >= perm_model.AccessModeRead {
|
||||||
return true
|
return true
|
||||||
@ -61,7 +62,11 @@ func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return p.HasAnyUnitAccess()
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool {
|
||||||
|
return p.HasAnyUnitPublicAccess() || p.HasAnyUnitAccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasUnits returns true if the permission contains attached units
|
// HasUnits returns true if the permission contains attached units
|
||||||
@ -188,6 +193,9 @@ func (p *Permission) LogString() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) {
|
func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) {
|
||||||
|
if setting.Repository.ForcePrivate {
|
||||||
|
return
|
||||||
|
}
|
||||||
if accessMode >= perm_model.AccessModeRead && accessMode > (*modeMap)[unitType] {
|
if accessMode >= perm_model.AccessModeRead && accessMode > (*modeMap)[unitType] {
|
||||||
if *modeMap == nil {
|
if *modeMap == nil {
|
||||||
*modeMap = make(map[unit.Type]perm_model.AccessMode)
|
*modeMap = make(map[unit.Type]perm_model.AccessMode)
|
||||||
|
@ -342,3 +342,9 @@ func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error {
|
|||||||
_, err := db.GetEngine(ctx).ID(unit.ID).Update(unit)
|
_, err := db.GetEngine(ctx).ID(unit.ID).Update(unit)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateRepoUnitPublicAccess(ctx context.Context, unit *RepoUnit) error {
|
||||||
|
_, err := db.GetEngine(ctx).Where("repo_id=? AND `type`=?", unit.RepoID, unit.Type).
|
||||||
|
Cols("anonymous_access_mode", "everyone_access_mode").Update(unit)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -926,6 +926,9 @@ permission_not_set = Not set
|
|||||||
permission_no_access = No Access
|
permission_no_access = No Access
|
||||||
permission_read = Read
|
permission_read = Read
|
||||||
permission_write = Read and Write
|
permission_write = Read and Write
|
||||||
|
permission_anonymous_read = Anonymous Read
|
||||||
|
permission_everyone_read = Everyone Read
|
||||||
|
permission_everyone_write = Everyone Write
|
||||||
access_token_desc = Selected token permissions limit authorization only to the corresponding <a %s>API</a> routes. Read the <a %s>documentation</a> for more information.
|
access_token_desc = Selected token permissions limit authorization only to the corresponding <a %s>API</a> routes. Read the <a %s>documentation</a> for more information.
|
||||||
at_least_one_permission = You must select at least one permission to create a token
|
at_least_one_permission = You must select at least one permission to create a token
|
||||||
permissions_list = Permissions:
|
permissions_list = Permissions:
|
||||||
@ -1138,6 +1141,7 @@ transfer.no_permission_to_reject = You do not have permission to reject this tra
|
|||||||
|
|
||||||
desc.private = Private
|
desc.private = Private
|
||||||
desc.public = Public
|
desc.public = Public
|
||||||
|
desc.public_access = Public Access
|
||||||
desc.template = Template
|
desc.template = Template
|
||||||
desc.internal = Internal
|
desc.internal = Internal
|
||||||
desc.archived = Archived
|
desc.archived = Archived
|
||||||
@ -2133,6 +2137,7 @@ contributors.contribution_type.deletions = Deletions
|
|||||||
settings = Settings
|
settings = Settings
|
||||||
settings.desc = Settings is where you can manage the settings for the repository
|
settings.desc = Settings is where you can manage the settings for the repository
|
||||||
settings.options = Repository
|
settings.options = Repository
|
||||||
|
settings.public_access = Public Access
|
||||||
settings.collaboration = Collaborators
|
settings.collaboration = Collaborators
|
||||||
settings.collaboration.admin = Administrator
|
settings.collaboration.admin = Administrator
|
||||||
settings.collaboration.write = Write
|
settings.collaboration.write = Write
|
||||||
@ -2179,7 +2184,6 @@ settings.advanced_settings = Advanced Settings
|
|||||||
settings.wiki_desc = Enable Repository Wiki
|
settings.wiki_desc = Enable Repository Wiki
|
||||||
settings.use_internal_wiki = Use Built-In Wiki
|
settings.use_internal_wiki = Use Built-In Wiki
|
||||||
settings.default_wiki_branch_name = Default Wiki Branch Name
|
settings.default_wiki_branch_name = Default Wiki Branch Name
|
||||||
settings.default_permission_everyone_access = Default access permission for all signed-in users:
|
|
||||||
settings.failed_to_change_default_wiki_branch = Failed to change the default wiki branch.
|
settings.failed_to_change_default_wiki_branch = Failed to change the default wiki branch.
|
||||||
settings.use_external_wiki = Use External Wiki
|
settings.use_external_wiki = Use External Wiki
|
||||||
settings.external_wiki_url = External Wiki URL
|
settings.external_wiki_url = External Wiki URL
|
||||||
|
155
routers/web/repo/setting/public_access.go
Normal file
155
routers/web/repo/setting/public_access.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
"code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/templates"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tplRepoSettingsPublicAccess templates.TplName = "repo/settings/public_access"
|
||||||
|
|
||||||
|
func parsePublicAccessMode(permission string, allowed []string) (ret struct {
|
||||||
|
AnonymousAccessMode, EveryoneAccessMode perm.AccessMode
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
ret.AnonymousAccessMode = perm.AccessModeNone
|
||||||
|
ret.EveryoneAccessMode = perm.AccessModeNone
|
||||||
|
|
||||||
|
// if site admin forces repositories to be private, then do not allow any other access mode,
|
||||||
|
// otherwise the "force private" setting would be bypassed
|
||||||
|
if setting.Repository.ForcePrivate {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
if !slices.Contains(allowed, permission) {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
switch permission {
|
||||||
|
case paAnonymousRead:
|
||||||
|
ret.AnonymousAccessMode = perm.AccessModeRead
|
||||||
|
case paEveryoneRead:
|
||||||
|
ret.EveryoneAccessMode = perm.AccessModeRead
|
||||||
|
case paEveryoneWrite:
|
||||||
|
ret.EveryoneAccessMode = perm.AccessModeWrite
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
paNotSet = "not-set"
|
||||||
|
paAnonymousRead = "anonymous-read"
|
||||||
|
paEveryoneRead = "everyone-read"
|
||||||
|
paEveryoneWrite = "everyone-write"
|
||||||
|
)
|
||||||
|
|
||||||
|
type repoUnitPublicAccess struct {
|
||||||
|
UnitType unit.Type
|
||||||
|
FormKey string
|
||||||
|
DisplayName string
|
||||||
|
PublicAccessTypes []string
|
||||||
|
UnitPublicAccess string
|
||||||
|
}
|
||||||
|
|
||||||
|
func repoUnitPublicAccesses(ctx *context.Context) []*repoUnitPublicAccess {
|
||||||
|
accesses := []*repoUnitPublicAccess{
|
||||||
|
{
|
||||||
|
UnitType: unit.TypeCode,
|
||||||
|
DisplayName: ctx.Locale.TrString("repo.code"),
|
||||||
|
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UnitType: unit.TypeIssues,
|
||||||
|
DisplayName: ctx.Locale.TrString("issues"),
|
||||||
|
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UnitType: unit.TypePullRequests,
|
||||||
|
DisplayName: ctx.Locale.TrString("pull_requests"),
|
||||||
|
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UnitType: unit.TypeReleases,
|
||||||
|
DisplayName: ctx.Locale.TrString("repo.releases"),
|
||||||
|
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UnitType: unit.TypeWiki,
|
||||||
|
DisplayName: ctx.Locale.TrString("repo.wiki"),
|
||||||
|
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead, paEveryoneWrite},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UnitType: unit.TypeProjects,
|
||||||
|
DisplayName: ctx.Locale.TrString("repo.projects"),
|
||||||
|
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UnitType: unit.TypePackages,
|
||||||
|
DisplayName: ctx.Locale.TrString("repo.packages"),
|
||||||
|
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
UnitType: unit.TypeActions,
|
||||||
|
DisplayName: ctx.Locale.TrString("repo.actions"),
|
||||||
|
PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, ua := range accesses {
|
||||||
|
ua.FormKey = "repo-unit-access-" + strconv.Itoa(int(ua.UnitType))
|
||||||
|
for _, u := range ctx.Repo.Repository.Units {
|
||||||
|
if u.Type == ua.UnitType {
|
||||||
|
ua.UnitPublicAccess = paNotSet
|
||||||
|
switch {
|
||||||
|
case u.EveryoneAccessMode == perm.AccessModeWrite:
|
||||||
|
ua.UnitPublicAccess = paEveryoneWrite
|
||||||
|
case u.EveryoneAccessMode == perm.AccessModeRead:
|
||||||
|
ua.UnitPublicAccess = paEveryoneRead
|
||||||
|
case u.AnonymousAccessMode == perm.AccessModeRead:
|
||||||
|
ua.UnitPublicAccess = paAnonymousRead
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slices.DeleteFunc(accesses, func(ua *repoUnitPublicAccess) bool {
|
||||||
|
return ua.UnitPublicAccess == ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Flash.Error(ctx.Tr("form.repository_force_private"), true)
|
||||||
|
}
|
||||||
|
ctx.HTML(http.StatusOK, tplRepoSettingsPublicAccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicAccessPost(ctx *context.Context) {
|
||||||
|
accesses := repoUnitPublicAccesses(ctx)
|
||||||
|
for _, ua := range accesses {
|
||||||
|
formVal := ctx.FormString(ua.FormKey)
|
||||||
|
parsed := parsePublicAccessMode(formVal, ua.PublicAccessTypes)
|
||||||
|
err := repo.UpdateRepoUnitPublicAccess(ctx, &repo.RepoUnit{
|
||||||
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
Type: ua.UnitType,
|
||||||
|
AnonymousAccessMode: parsed.AnonymousAccessMode,
|
||||||
|
EveryoneAccessMode: parsed.EveryoneAccessMode,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("UpdateRepoUnitPublicAccess", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||||
|
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/public_access")
|
||||||
|
}
|
@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
unit_model "code.gitea.io/gitea/models/unit"
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -37,6 +36,8 @@ import (
|
|||||||
mirror_service "code.gitea.io/gitea/services/mirror"
|
mirror_service "code.gitea.io/gitea/services/mirror"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
wiki_service "code.gitea.io/gitea/services/wiki"
|
wiki_service "code.gitea.io/gitea/services/wiki"
|
||||||
|
|
||||||
|
"xorm.io/xorm/convert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -48,15 +49,6 @@ const (
|
|||||||
tplDeployKeys templates.TplName = "repo/settings/deploy_keys"
|
tplDeployKeys templates.TplName = "repo/settings/deploy_keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseEveryoneAccessMode(permission string, allowed ...perm.AccessMode) perm.AccessMode {
|
|
||||||
// if site admin forces repositories to be private, then do not allow any other access mode,
|
|
||||||
// otherwise the "force private" setting would be bypassed
|
|
||||||
if setting.Repository.ForcePrivate {
|
|
||||||
return perm.AccessModeNone
|
|
||||||
}
|
|
||||||
return perm.ParseAccessMode(permission, allowed...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SettingsCtxData is a middleware that sets all the general context data for the
|
// SettingsCtxData is a middleware that sets all the general context data for the
|
||||||
// settings template.
|
// settings template.
|
||||||
func SettingsCtxData(ctx *context.Context) {
|
func SettingsCtxData(ctx *context.Context) {
|
||||||
@ -504,6 +496,17 @@ func handleSettingsPostPushMirrorAdd(ctx *context.Context) {
|
|||||||
ctx.Redirect(repo.Link() + "/settings")
|
ctx.Redirect(repo.Link() + "/settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRepoUnit(repo *repo_model.Repository, unitType unit_model.Type, config convert.Conversion) repo_model.RepoUnit {
|
||||||
|
repoUnit := repo_model.RepoUnit{RepoID: repo.ID, Type: unitType, Config: config}
|
||||||
|
for _, u := range repo.Units {
|
||||||
|
if u.Type == unitType {
|
||||||
|
repoUnit.EveryoneAccessMode = u.EveryoneAccessMode
|
||||||
|
repoUnit.AnonymousAccessMode = u.AnonymousAccessMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return repoUnit
|
||||||
|
}
|
||||||
|
|
||||||
func handleSettingsPostAdvanced(ctx *context.Context) {
|
func handleSettingsPostAdvanced(ctx *context.Context) {
|
||||||
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
form := web.GetForm(ctx).(*forms.RepoSettingForm)
|
||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
@ -521,11 +524,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() {
|
if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() {
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypeCode, nil))
|
||||||
RepoID: repo.ID,
|
|
||||||
Type: unit_model.TypeCode,
|
|
||||||
EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultCodeEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead),
|
|
||||||
})
|
|
||||||
} else if !unit_model.TypeCode.UnitGlobalDisabled() {
|
} else if !unit_model.TypeCode.UnitGlobalDisabled() {
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode)
|
||||||
}
|
}
|
||||||
@ -537,21 +536,12 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypeExternalWiki, &repo_model.ExternalWikiConfig{
|
||||||
RepoID: repo.ID,
|
ExternalWikiURL: form.ExternalWikiURL,
|
||||||
Type: unit_model.TypeExternalWiki,
|
}))
|
||||||
Config: &repo_model.ExternalWikiConfig{
|
|
||||||
ExternalWikiURL: form.ExternalWikiURL,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki)
|
||||||
} else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
|
} else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() {
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypeWiki, new(repo_model.UnitConfig)))
|
||||||
RepoID: repo.ID,
|
|
||||||
Type: unit_model.TypeWiki,
|
|
||||||
Config: new(repo_model.UnitConfig),
|
|
||||||
EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite),
|
|
||||||
})
|
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
|
||||||
} else {
|
} else {
|
||||||
if !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
|
if !unit_model.TypeExternalWiki.UnitGlobalDisabled() {
|
||||||
@ -580,28 +570,19 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
|||||||
ctx.Redirect(repo.Link() + "/settings")
|
ctx.Redirect(repo.Link() + "/settings")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypeExternalTracker, &repo_model.ExternalTrackerConfig{
|
||||||
RepoID: repo.ID,
|
ExternalTrackerURL: form.ExternalTrackerURL,
|
||||||
Type: unit_model.TypeExternalTracker,
|
ExternalTrackerFormat: form.TrackerURLFormat,
|
||||||
Config: &repo_model.ExternalTrackerConfig{
|
ExternalTrackerStyle: form.TrackerIssueStyle,
|
||||||
ExternalTrackerURL: form.ExternalTrackerURL,
|
ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern,
|
||||||
ExternalTrackerFormat: form.TrackerURLFormat,
|
}))
|
||||||
ExternalTrackerStyle: form.TrackerIssueStyle,
|
|
||||||
ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues)
|
||||||
} else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() {
|
} else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() {
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypeIssues, &repo_model.IssuesConfig{
|
||||||
RepoID: repo.ID,
|
EnableTimetracker: form.EnableTimetracker,
|
||||||
Type: unit_model.TypeIssues,
|
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
|
||||||
Config: &repo_model.IssuesConfig{
|
EnableDependencies: form.EnableIssueDependencies,
|
||||||
EnableTimetracker: form.EnableTimetracker,
|
}))
|
||||||
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
|
|
||||||
EnableDependencies: form.EnableIssueDependencies,
|
|
||||||
},
|
|
||||||
EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultIssuesEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead),
|
|
||||||
})
|
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
|
||||||
} else {
|
} else {
|
||||||
if !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
|
if !unit_model.TypeExternalTracker.UnitGlobalDisabled() {
|
||||||
@ -613,63 +594,46 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() {
|
if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() {
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypeProjects, &repo_model.ProjectsConfig{
|
||||||
RepoID: repo.ID,
|
ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode),
|
||||||
Type: unit_model.TypeProjects,
|
}))
|
||||||
Config: &repo_model.ProjectsConfig{
|
|
||||||
ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if !unit_model.TypeProjects.UnitGlobalDisabled() {
|
} else if !unit_model.TypeProjects.UnitGlobalDisabled() {
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() {
|
if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() {
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypeReleases, nil))
|
||||||
RepoID: repo.ID,
|
|
||||||
Type: unit_model.TypeReleases,
|
|
||||||
})
|
|
||||||
} else if !unit_model.TypeReleases.UnitGlobalDisabled() {
|
} else if !unit_model.TypeReleases.UnitGlobalDisabled() {
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() {
|
if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() {
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypePackages, nil))
|
||||||
RepoID: repo.ID,
|
|
||||||
Type: unit_model.TypePackages,
|
|
||||||
})
|
|
||||||
} else if !unit_model.TypePackages.UnitGlobalDisabled() {
|
} else if !unit_model.TypePackages.UnitGlobalDisabled() {
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() {
|
if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() {
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypeActions, nil))
|
||||||
RepoID: repo.ID,
|
|
||||||
Type: unit_model.TypeActions,
|
|
||||||
})
|
|
||||||
} else if !unit_model.TypeActions.UnitGlobalDisabled() {
|
} else if !unit_model.TypeActions.UnitGlobalDisabled() {
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() {
|
if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() {
|
||||||
units = append(units, repo_model.RepoUnit{
|
units = append(units, newRepoUnit(repo, unit_model.TypePullRequests, &repo_model.PullRequestsConfig{
|
||||||
RepoID: repo.ID,
|
IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace,
|
||||||
Type: unit_model.TypePullRequests,
|
AllowMerge: form.PullsAllowMerge,
|
||||||
Config: &repo_model.PullRequestsConfig{
|
AllowRebase: form.PullsAllowRebase,
|
||||||
IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace,
|
AllowRebaseMerge: form.PullsAllowRebaseMerge,
|
||||||
AllowMerge: form.PullsAllowMerge,
|
AllowSquash: form.PullsAllowSquash,
|
||||||
AllowRebase: form.PullsAllowRebase,
|
AllowFastForwardOnly: form.PullsAllowFastForwardOnly,
|
||||||
AllowRebaseMerge: form.PullsAllowRebaseMerge,
|
AllowManualMerge: form.PullsAllowManualMerge,
|
||||||
AllowSquash: form.PullsAllowSquash,
|
AutodetectManualMerge: form.EnableAutodetectManualMerge,
|
||||||
AllowFastForwardOnly: form.PullsAllowFastForwardOnly,
|
AllowRebaseUpdate: form.PullsAllowRebaseUpdate,
|
||||||
AllowManualMerge: form.PullsAllowManualMerge,
|
DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
|
||||||
AutodetectManualMerge: form.EnableAutodetectManualMerge,
|
DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
|
||||||
AllowRebaseUpdate: form.PullsAllowRebaseUpdate,
|
DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
|
||||||
DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge,
|
}))
|
||||||
DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle),
|
|
||||||
DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if !unit_model.TypePullRequests.UnitGlobalDisabled() {
|
} else if !unit_model.TypePullRequests.UnitGlobalDisabled() {
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
|
||||||
}
|
}
|
||||||
|
@ -1078,6 +1078,8 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo_setting.SettingsAvatar)
|
m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo_setting.SettingsAvatar)
|
||||||
m.Post("/avatar/delete", repo_setting.SettingsDeleteAvatar)
|
m.Post("/avatar/delete", repo_setting.SettingsDeleteAvatar)
|
||||||
|
|
||||||
|
m.Combo("/public_access").Get(repo_setting.PublicAccess).Post(repo_setting.PublicAccessPost)
|
||||||
|
|
||||||
m.Group("/collaboration", func() {
|
m.Group("/collaboration", func() {
|
||||||
m.Combo("").Get(repo_setting.Collaboration).Post(repo_setting.CollaborationPost)
|
m.Combo("").Get(repo_setting.Collaboration).Post(repo_setting.CollaborationPost)
|
||||||
m.Post("/access_mode", repo_setting.ChangeCollaborationAccessMode)
|
m.Post("/access_mode", repo_setting.ChangeCollaborationAccessMode)
|
||||||
|
@ -110,17 +110,14 @@ type RepoSettingForm struct {
|
|||||||
EnablePrune bool
|
EnablePrune bool
|
||||||
|
|
||||||
// Advanced settings
|
// Advanced settings
|
||||||
EnableCode bool
|
EnableCode bool
|
||||||
DefaultCodeEveryoneAccess string
|
|
||||||
|
|
||||||
EnableWiki bool
|
EnableWiki bool
|
||||||
EnableExternalWiki bool
|
EnableExternalWiki bool
|
||||||
DefaultWikiBranch string
|
DefaultWikiBranch string
|
||||||
DefaultWikiEveryoneAccess string
|
ExternalWikiURL string
|
||||||
ExternalWikiURL string
|
|
||||||
|
|
||||||
EnableIssues bool
|
EnableIssues bool
|
||||||
DefaultIssuesEveryoneAccess string
|
|
||||||
EnableExternalTracker bool
|
EnableExternalTracker bool
|
||||||
ExternalTrackerURL string
|
ExternalTrackerURL string
|
||||||
TrackerURLFormat string
|
TrackerURLFormat string
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
<div class="repo-icon only-mobile" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.internal"}}">{{svg "octicon-shield-lock" 18}}</div>
|
<div class="repo-icon only-mobile" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.internal"}}">{{svg "octicon-shield-lock" 18}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if $.Permission.HasAnyUnitPublicAccess}}
|
||||||
|
<span class="ui basic orange label">{{ctx.Locale.Tr "repo.desc.public_access"}}</span>
|
||||||
|
{{end}}
|
||||||
{{if .IsTemplate}}
|
{{if .IsTemplate}}
|
||||||
<span class="ui basic label not-mobile">{{ctx.Locale.Tr "repo.desc.template"}}</span>
|
<span class="ui basic label not-mobile">{{ctx.Locale.Tr "repo.desc.template"}}</span>
|
||||||
<div class="repo-icon only-mobile" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.template"}}">{{svg "octicon-repo-template" 18}}</div>
|
<div class="repo-icon only-mobile" data-tooltip-content="{{ctx.Locale.Tr "repo.desc.template"}}">{{svg "octicon-repo-template" 18}}</div>
|
||||||
@ -208,7 +211,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if and (.Permission.CanReadAny ctx.Consts.RepoUnitTypePullRequests ctx.Consts.RepoUnitTypeIssues ctx.Consts.RepoUnitTypeReleases) (not .IsEmptyRepo)}}
|
{{if and (.Permission.CanReadAny ctx.Consts.RepoUnitTypePullRequests ctx.Consts.RepoUnitTypeIssues ctx.Consts.RepoUnitTypeReleases ctx.Consts.RepoUnitTypeCode) (not .IsEmptyRepo)}}
|
||||||
<a class="{{if .PageIsActivity}}active {{end}}item" href="{{.RepoLink}}/activity">
|
<a class="{{if .PageIsActivity}}active {{end}}item" href="{{.RepoLink}}/activity">
|
||||||
{{svg "octicon-pulse"}} {{ctx.Locale.Tr "repo.activity"}}
|
{{svg "octicon-pulse"}} {{ctx.Locale.Tr "repo.activity"}}
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{{$canReadCode := $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
|
{{$canReadCode := $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
|
||||||
|
|
||||||
<div class="ui fluid vertical menu">
|
<div class="ui fluid vertical menu">
|
||||||
|
{{/* the default activity page "pulse" could work with any permission: code, issue, pr, release*/}}
|
||||||
<a class="{{if .PageIsPulse}}active {{end}}item" href="{{.RepoLink}}/activity">
|
<a class="{{if .PageIsPulse}}active {{end}}item" href="{{.RepoLink}}/activity">
|
||||||
{{ctx.Locale.Tr "repo.activity.navbar.pulse"}}
|
{{ctx.Locale.Tr "repo.activity.navbar.pulse"}}
|
||||||
</a>
|
</a>
|
||||||
|
@ -4,6 +4,11 @@
|
|||||||
<a class="{{if .PageIsSettingsOptions}}active {{end}}item" href="{{.RepoLink}}/settings">
|
<a class="{{if .PageIsSettingsOptions}}active {{end}}item" href="{{.RepoLink}}/settings">
|
||||||
{{ctx.Locale.Tr "repo.settings.options"}}
|
{{ctx.Locale.Tr "repo.settings.options"}}
|
||||||
</a>
|
</a>
|
||||||
|
{{if or .Repository.IsPrivate .Permission.HasAnyUnitPublicAccess}}
|
||||||
|
<a class="{{if .PageIsSettingsPublicAccess}}active {{end}}item" href="{{.RepoLink}}/settings/public_access">
|
||||||
|
{{ctx.Locale.Tr "repo.settings.public_access"}}
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
<a class="{{if .PageIsSettingsCollaboration}}active {{end}}item" href="{{.RepoLink}}/settings/collaboration">
|
<a class="{{if .PageIsSettingsCollaboration}}active {{end}}item" href="{{.RepoLink}}/settings/collaboration">
|
||||||
{{ctx.Locale.Tr "repo.settings.collaboration"}}
|
{{ctx.Locale.Tr "repo.settings.collaboration"}}
|
||||||
</a>
|
</a>
|
||||||
|
@ -310,15 +310,6 @@
|
|||||||
<input class="enable-system" name="enable_code" type="checkbox"{{if $isCodeEnabled}} checked{{end}}>
|
<input class="enable-system" name="enable_code" type="checkbox"{{if $isCodeEnabled}} checked{{end}}>
|
||||||
<label>{{ctx.Locale.Tr "repo.code.desc"}}</label>
|
<label>{{ctx.Locale.Tr "repo.code.desc"}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline field tw-pl-4">
|
|
||||||
{{$unitCode := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeCode}}
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
|
|
||||||
<select name="default_code_everyone_access" class="ui selection dropdown">
|
|
||||||
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
|
|
||||||
<option value="none" {{Iif (eq $unitCode.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
|
|
||||||
<option value="read" {{Iif (eq $unitCode.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}}
|
{{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}}
|
||||||
@ -346,16 +337,6 @@
|
|||||||
<label>{{ctx.Locale.Tr "repo.settings.default_wiki_branch_name"}}</label>
|
<label>{{ctx.Locale.Tr "repo.settings.default_wiki_branch_name"}}</label>
|
||||||
<input name="default_wiki_branch" value="{{.Repository.DefaultWikiBranch}}">
|
<input name="default_wiki_branch" value="{{.Repository.DefaultWikiBranch}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="inline field">
|
|
||||||
{{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}}
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
|
|
||||||
<select name="default_wiki_everyone_access" class="ui selection dropdown">
|
|
||||||
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
|
|
||||||
<option value="none" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
|
|
||||||
<option value="read" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option>
|
|
||||||
<option value="write" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 2) "selected"}}>{{ctx.Locale.Tr "settings.permission_write"}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui radio checkbox{{if $isExternalWikiGlobalDisabled}} disabled{{end}}"{{if $isExternalWikiGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
|
<div class="ui radio checkbox{{if $isExternalWikiGlobalDisabled}} disabled{{end}}"{{if $isExternalWikiGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
|
||||||
@ -391,15 +372,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field tw-pl-4 {{if (.Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box">
|
<div class="field tw-pl-4 {{if (.Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box">
|
||||||
<div class="inline field">
|
|
||||||
{{$unitIssue := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeIssues}}
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
|
|
||||||
<select name="default_issues_everyone_access" class="ui selection dropdown">
|
|
||||||
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
|
|
||||||
<option value="none" {{Iif (eq $unitIssue.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
|
|
||||||
<option value="read" {{Iif (eq $unitIssue.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{{if .Repository.CanEnableTimetracker}}
|
{{if .Repository.CanEnableTimetracker}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
|
36
templates/repo/settings/public_access.tmpl
Normal file
36
templates/repo/settings/public_access.tmpl
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings")}}
|
||||||
|
<div class="repo-setting-content">
|
||||||
|
{{$paNotSet := "not-set"}}
|
||||||
|
{{$paAnonymousRead := "anonymous-read"}}
|
||||||
|
{{$paEveryoneRead := "everyone-read"}}
|
||||||
|
{{$paEveryoneWrite := "everyone-write"}}
|
||||||
|
<form class="ui form" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<table class="ui table unstackable tw-my-2">
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>{{ctx.Locale.Tr "settings.permission_not_set"}}</th>
|
||||||
|
<th>{{ctx.Locale.Tr "settings.permission_anonymous_read"}}</th>
|
||||||
|
<th>{{ctx.Locale.Tr "settings.permission_everyone_read"}}</th>
|
||||||
|
<th>{{ctx.Locale.Tr "settings.permission_everyone_write"}}</th>
|
||||||
|
</tr>
|
||||||
|
{{range $ua := .RepoUnitPublicAccesses}}
|
||||||
|
<tr>
|
||||||
|
<td>{{$ua.DisplayName}}</td>
|
||||||
|
<td class="tw-text-center"><label><input type="radio" name="{{$ua.FormKey}}" value="{{$paNotSet}}" {{Iif (eq $paNotSet $ua.UnitPublicAccess) "checked"}}></label></td>
|
||||||
|
<td class="tw-text-center"><label><input type="radio" name="{{$ua.FormKey}}" value="{{$paAnonymousRead}}" {{Iif (eq $paAnonymousRead $ua.UnitPublicAccess) "checked"}}></label></td>
|
||||||
|
<td class="tw-text-center"><label><input type="radio" name="{{$ua.FormKey}}" value="{{$paEveryoneRead}}" {{Iif (eq $paEveryoneRead $ua.UnitPublicAccess) "checked"}}></label></td>
|
||||||
|
<td class="tw-text-center">
|
||||||
|
{{if SliceUtils.Contains $ua.PublicAccessTypes $paEveryoneWrite}}
|
||||||
|
<label><input type="radio" name="{{$ua.FormKey}}" value="{{$paEveryoneWrite}}" {{Iif (eq $paEveryoneWrite $ua.UnitPublicAccess) "checked"}}></label>
|
||||||
|
{{else}}
|
||||||
|
-
|
||||||
|
{{end}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</table>
|
||||||
|
<button class="ui primary button {{if .GlobalForcePrivate}}disabled{{end}}">{{ctx.Locale.Tr "repo.settings.update_settings"}}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{template "repo/settings/layout_footer" .}}
|
@ -7,10 +7,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
@ -19,8 +21,26 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestViewRepo(t *testing.T) {
|
func TestRepoView(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
t.Run("ViewRepoPublic", testViewRepoPublic)
|
||||||
|
t.Run("ViewRepoWithCache", testViewRepoWithCache)
|
||||||
|
t.Run("ViewRepoPrivate", testViewRepoPrivate)
|
||||||
|
t.Run("ViewRepo1CloneLinkAnonymous", testViewRepo1CloneLinkAnonymous)
|
||||||
|
t.Run("ViewRepo1CloneLinkAuthorized", testViewRepo1CloneLinkAuthorized)
|
||||||
|
t.Run("ViewRepoWithSymlinks", testViewRepoWithSymlinks)
|
||||||
|
t.Run("ViewFileInRepo", testViewFileInRepo)
|
||||||
|
t.Run("BlameFileInRepo", testBlameFileInRepo)
|
||||||
|
t.Run("ViewRepoDirectory", testViewRepoDirectory)
|
||||||
|
t.Run("ViewRepoDirectoryReadme", testViewRepoDirectoryReadme)
|
||||||
|
t.Run("MarkDownReadmeImage", testMarkDownReadmeImage)
|
||||||
|
t.Run("MarkDownReadmeImageSubfolder", testMarkDownReadmeImageSubfolder)
|
||||||
|
t.Run("GeneratedSourceLink", testGeneratedSourceLink)
|
||||||
|
t.Run("ViewCommit", testViewCommit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testViewRepoPublic(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
@ -41,87 +61,118 @@ func TestViewRepo(t *testing.T) {
|
|||||||
session.MakeRequest(t, req, http.StatusNotFound)
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testViewRepo(t *testing.T) {
|
func testViewRepoWithCache(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
testView := func(t *testing.T) {
|
||||||
|
req := NewRequest(t, "GET", "/org3/repo3")
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
req := NewRequest(t, "GET", "/org3/repo3")
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
session := loginUser(t, "user2")
|
files := htmlDoc.doc.Find("#repo-files-table .repo-file-item")
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
|
||||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
type file struct {
|
||||||
files := htmlDoc.doc.Find("#repo-files-table .repo-file-item")
|
fileName string
|
||||||
|
commitID string
|
||||||
|
commitMsg string
|
||||||
|
commitTime string
|
||||||
|
}
|
||||||
|
|
||||||
type file struct {
|
var items []file
|
||||||
fileName string
|
|
||||||
commitID string
|
|
||||||
commitMsg string
|
|
||||||
commitTime string
|
|
||||||
}
|
|
||||||
|
|
||||||
var items []file
|
files.Each(func(i int, s *goquery.Selection) {
|
||||||
|
tds := s.Find(".repo-file-cell")
|
||||||
|
var f file
|
||||||
|
tds.Each(func(i int, s *goquery.Selection) {
|
||||||
|
if i == 0 {
|
||||||
|
f.fileName = strings.TrimSpace(s.Text())
|
||||||
|
} else if i == 1 {
|
||||||
|
a := s.Find("a")
|
||||||
|
f.commitMsg = strings.TrimSpace(a.Text())
|
||||||
|
l, _ := a.Attr("href")
|
||||||
|
f.commitID = path.Base(l)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
files.Each(func(i int, s *goquery.Selection) {
|
// convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC"
|
||||||
tds := s.Find(".repo-file-cell")
|
htmlTimeString, _ := s.Find("relative-time").Attr("datetime")
|
||||||
var f file
|
htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString)
|
||||||
tds.Each(func(i int, s *goquery.Selection) {
|
f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123)
|
||||||
if i == 0 {
|
items = append(items, f)
|
||||||
f.fileName = strings.TrimSpace(s.Text())
|
|
||||||
} else if i == 1 {
|
|
||||||
a := s.Find("a")
|
|
||||||
f.commitMsg = strings.TrimSpace(a.Text())
|
|
||||||
l, _ := a.Attr("href")
|
|
||||||
f.commitID = path.Base(l)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC"
|
commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123)
|
||||||
htmlTimeString, _ := s.Find("relative-time").Attr("datetime")
|
assert.EqualValues(t, []file{
|
||||||
htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString)
|
{
|
||||||
f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123)
|
fileName: "doc",
|
||||||
items = append(items, f)
|
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
|
||||||
})
|
commitMsg: "init project",
|
||||||
|
commitTime: commitT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fileName: "README.md",
|
||||||
|
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
|
||||||
|
commitMsg: "init project",
|
||||||
|
commitTime: commitT,
|
||||||
|
},
|
||||||
|
}, items)
|
||||||
|
}
|
||||||
|
|
||||||
commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123)
|
// FIXME: these test don't seem quite right, no enough assert
|
||||||
assert.EqualValues(t, []file{
|
|
||||||
{
|
|
||||||
fileName: "doc",
|
|
||||||
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
|
|
||||||
commitMsg: "init project",
|
|
||||||
commitTime: commitT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fileName: "README.md",
|
|
||||||
commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6",
|
|
||||||
commitMsg: "init project",
|
|
||||||
commitTime: commitT,
|
|
||||||
},
|
|
||||||
}, items)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestViewRepo2(t *testing.T) {
|
|
||||||
// no last commit cache
|
// no last commit cache
|
||||||
testViewRepo(t)
|
testView(t)
|
||||||
|
|
||||||
// enable last commit cache for all repositories
|
// enable last commit cache for all repositories
|
||||||
oldCommitsCount := setting.CacheService.LastCommit.CommitsCount
|
oldCommitsCount := setting.CacheService.LastCommit.CommitsCount
|
||||||
setting.CacheService.LastCommit.CommitsCount = 0
|
setting.CacheService.LastCommit.CommitsCount = 0
|
||||||
// first view will not hit the cache
|
// first view will not hit the cache
|
||||||
testViewRepo(t)
|
testView(t)
|
||||||
// second view will hit the cache
|
// second view will hit the cache
|
||||||
testViewRepo(t)
|
testView(t)
|
||||||
setting.CacheService.LastCommit.CommitsCount = oldCommitsCount
|
setting.CacheService.LastCommit.CommitsCount = oldCommitsCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRepo3(t *testing.T) {
|
func testViewRepoPrivate(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", "/org3/repo3")
|
req := NewRequest(t, "GET", "/org3/repo3")
|
||||||
session := loginUser(t, "user4")
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
|
t.Run("OrgMemberAccess", func(t *testing.T) {
|
||||||
|
req = NewRequest(t, "GET", "/org3/repo3")
|
||||||
|
session := loginUser(t, "user4")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), `<div id="repo-files-table"`)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PublicAccess-AnonymousAccess", func(t *testing.T) {
|
||||||
|
session := loginUser(t, "user1")
|
||||||
|
|
||||||
|
// set unit code to "anonymous read"
|
||||||
|
req = NewRequestWithValues(t, "POST", "/org3/repo3/settings/public_access", map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
"repo-unit-access-" + strconv.Itoa(int(unit.TypeCode)): "anonymous-read",
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
|
||||||
|
// try to "anonymous read" (ok)
|
||||||
|
req = NewRequest(t, "GET", "/org3/repo3")
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), `<span class="ui basic orange label">Public Access</span>`)
|
||||||
|
|
||||||
|
// remove "anonymous read"
|
||||||
|
req = NewRequestWithValues(t, "POST", "/org3/repo3/settings/public_access", map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, session),
|
||||||
|
})
|
||||||
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
|
||||||
|
// try to "anonymous read" (not found)
|
||||||
|
req = NewRequest(t, "GET", "/org3/repo3")
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
|
func testViewRepo1CloneLinkAnonymous(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", "/user2/repo1")
|
req := NewRequest(t, "GET", "/user2/repo1")
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
@ -139,8 +190,8 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) {
|
|||||||
assert.Equal(t, "tea clone user2/repo1", link)
|
assert.Equal(t, "tea clone user2/repo1", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
|
func testViewRepo1CloneLinkAuthorized(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
@ -162,8 +213,8 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) {
|
|||||||
assert.Equal(t, "tea clone user2/repo1", link)
|
assert.Equal(t, "tea clone user2/repo1", link)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewRepoWithSymlinks(t *testing.T) {
|
func testViewRepoWithSymlinks(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
defer test.MockVariableValue(&setting.UI.FileIconTheme, "basic")()
|
defer test.MockVariableValue(&setting.UI.FileIconTheme, "basic")()
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
@ -186,8 +237,8 @@ func TestViewRepoWithSymlinks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file
|
// TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file
|
||||||
func TestViewFileInRepo(t *testing.T) {
|
func testViewFileInRepo(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
@ -205,8 +256,8 @@ func TestViewFileInRepo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestBlameFileInRepo repo description, topics and summary should not be displayed when running blame on a file
|
// TestBlameFileInRepo repo description, topics and summary should not be displayed when running blame on a file
|
||||||
func TestBlameFileInRepo(t *testing.T) {
|
func testBlameFileInRepo(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
@ -224,8 +275,8 @@ func TestBlameFileInRepo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestViewRepoDirectory repo description, topics and summary should not be displayed when within a directory
|
// TestViewRepoDirectory repo description, topics and summary should not be displayed when within a directory
|
||||||
func TestViewRepoDirectory(t *testing.T) {
|
func testViewRepoDirectory(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
@ -246,8 +297,8 @@ func TestViewRepoDirectory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure that the all the different ways to find and render a README work
|
// ensure that the all the different ways to find and render a README work
|
||||||
func TestViewRepoDirectoryReadme(t *testing.T) {
|
func testViewRepoDirectoryReadme(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
// there are many combinations:
|
// there are many combinations:
|
||||||
// - READMEs can be .md, .txt, or have no extension
|
// - READMEs can be .md, .txt, or have no extension
|
||||||
@ -353,8 +404,8 @@ func TestViewRepoDirectoryReadme(t *testing.T) {
|
|||||||
missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/")
|
missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkDownReadmeImage(t *testing.T) {
|
func testMarkDownReadmeImage(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
@ -375,8 +426,8 @@ func TestMarkDownReadmeImage(t *testing.T) {
|
|||||||
assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src)
|
assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkDownReadmeImageSubfolder(t *testing.T) {
|
func testMarkDownReadmeImageSubfolder(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
session := loginUser(t, "user2")
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
@ -398,8 +449,8 @@ func TestMarkDownReadmeImageSubfolder(t *testing.T) {
|
|||||||
assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src)
|
assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGeneratedSourceLink(t *testing.T) {
|
func testGeneratedSourceLink(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
t.Run("Rendered file", func(t *testing.T) {
|
t.Run("Rendered file", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
@ -434,8 +485,8 @@ func TestGeneratedSourceLink(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewCommit(t *testing.T) {
|
func testViewCommit(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", "/user2/repo1/commit/0123456789012345678901234567890123456789")
|
req := NewRequest(t, "GET", "/user2/repo1/commit/0123456789012345678901234567890123456789")
|
||||||
req.Header.Add("Accept", "text/html")
|
req.Header.Add("Accept", "text/html")
|
||||||
|
Reference in New Issue
Block a user