mirror of https://github.com/go-gitea/gitea.git
Allow everyone to read or write a wiki by a repo unit setting (#30495)
Replace #6312 Help #5833 Wiki solution for #639
This commit is contained in:
parent
bafb80f80d
commit
3feba9f1f4
|
@ -62,11 +62,13 @@ func CanMaintainerWriteToBranch(ctx context.Context, p access_model.Permission,
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.Units) < 1 {
|
// the code below depends on units to get the repository ID, not ideal but just keep it for now
|
||||||
|
firstUnitRepoID := p.GetFirstUnitRepoID()
|
||||||
|
if firstUnitRepoID == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
prs, err := GetUnmergedPullRequestsByHeadInfo(ctx, p.Units[0].RepoID, branch)
|
prs, err := GetUnmergedPullRequestsByHeadInfo(ctx, firstUnitRepoID, branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -582,6 +582,8 @@ var migrations = []Migration{
|
||||||
NewMigration("Add commit status summary table", v1_23.AddCommitStatusSummary),
|
NewMigration("Add commit status summary table", v1_23.AddCommitStatusSummary),
|
||||||
// v296 -> v297
|
// v296 -> v297
|
||||||
NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
|
NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
|
||||||
|
// v297 -> v298
|
||||||
|
NewMigration("Add everyone_access_mode for repo_unit", v1_23.AddRepoUnitEveryoneAccessMode),
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentDBVersion returns the current db version
|
// GetCurrentDBVersion returns the current db version
|
||||||
|
|
|
@ -336,7 +336,7 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if perm.UnitsMode == nil {
|
if len(perm.UnitsMode) == 0 {
|
||||||
for _, u := range perm.Units {
|
for _, u := range perm.Units {
|
||||||
if u.Type == UnitTypeCode {
|
if u.Type == UnitTypeCode {
|
||||||
return AccessModeWrite <= perm.AccessMode, nil
|
return AccessModeWrite <= perm.AccessMode, nil
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddRepoUnitEveryoneAccessMode(x *xorm.Engine) error {
|
||||||
|
type RepoUnit struct { //revive:disable-line:exported
|
||||||
|
EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
}
|
||||||
|
return x.Sync(&RepoUnit{})
|
||||||
|
}
|
|
@ -130,11 +130,11 @@ func (t *Team) GetUnitsMap() map[string]string {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
if t.AccessMode >= perm.AccessModeAdmin {
|
if t.AccessMode >= perm.AccessModeAdmin {
|
||||||
for _, u := range unit.Units {
|
for _, u := range unit.Units {
|
||||||
m[u.NameKey] = t.AccessMode.String()
|
m[u.NameKey] = t.AccessMode.ToString()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, u := range t.Units {
|
for _, u := range t.Units {
|
||||||
m[u.Unit().NameKey] = u.AccessMode.String()
|
m[u.Unit().NameKey] = u.AccessMode.ToString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
|
|
|
@ -63,13 +63,11 @@ func accessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Re
|
||||||
}
|
}
|
||||||
|
|
||||||
func maxAccessMode(modes ...perm.AccessMode) perm.AccessMode {
|
func maxAccessMode(modes ...perm.AccessMode) perm.AccessMode {
|
||||||
max := perm.AccessModeNone
|
maxMode := perm.AccessModeNone
|
||||||
for _, mode := range modes {
|
for _, mode := range modes {
|
||||||
if mode > max {
|
maxMode = max(maxMode, mode)
|
||||||
max = mode
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return max
|
return maxMode
|
||||||
}
|
}
|
||||||
|
|
||||||
type userAccess struct {
|
type userAccess struct {
|
||||||
|
|
|
@ -6,6 +6,7 @@ package access
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
@ -14,13 +15,15 @@ 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/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Permission contains all the permissions related variables to a repository for a user
|
// Permission contains all the permissions related variables to a repository for a user
|
||||||
type Permission struct {
|
type Permission struct {
|
||||||
AccessMode perm_model.AccessMode
|
AccessMode perm_model.AccessMode
|
||||||
Units []*repo_model.RepoUnit
|
|
||||||
UnitsMode map[unit.Type]perm_model.AccessMode
|
units []*repo_model.RepoUnit
|
||||||
|
unitsMode map[unit.Type]perm_model.AccessMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOwner returns true if current user is the owner of repository.
|
// IsOwner returns true if current user is the owner of repository.
|
||||||
|
@ -33,25 +36,44 @@ func (p *Permission) IsAdmin() bool {
|
||||||
return p.AccessMode >= perm_model.AccessModeAdmin
|
return p.AccessMode >= perm_model.AccessModeAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasAccess returns true if the current user has at least read access to any unit of this repository
|
// HasAccess returns true if the current user might have at least read access to any unit of this repository
|
||||||
func (p *Permission) HasAccess() bool {
|
func (p *Permission) HasAccess() bool {
|
||||||
if p.UnitsMode == nil {
|
return len(p.unitsMode) > 0 || p.AccessMode >= perm_model.AccessModeRead
|
||||||
return p.AccessMode >= perm_model.AccessModeRead
|
|
||||||
}
|
|
||||||
return len(p.UnitsMode) > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnitAccessMode returns current user accessmode to the specify unit of the repository
|
// HasUnits returns true if the permission contains attached units
|
||||||
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
|
func (p *Permission) HasUnits() bool {
|
||||||
if p.UnitsMode == nil {
|
return len(p.units) > 0
|
||||||
for _, u := range p.Units {
|
}
|
||||||
if u.Type == unitType {
|
|
||||||
return p.AccessMode
|
// GetFirstUnitRepoID returns the repo ID of the first unit, it is a fragile design and should NOT be used anymore
|
||||||
}
|
// deprecated
|
||||||
}
|
func (p *Permission) GetFirstUnitRepoID() int64 {
|
||||||
return perm_model.AccessModeNone
|
if len(p.units) > 0 {
|
||||||
|
return p.units[0].RepoID
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnitAccessMode returns current user access mode to the specify unit of the repository
|
||||||
|
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
|
||||||
|
if p.unitsMode != nil {
|
||||||
|
// if the units map contains the access mode, use it, but admin/owner mode could override it
|
||||||
|
if m, ok := p.unitsMode[unitType]; ok {
|
||||||
|
return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the units map does not contain the access mode, return the default access mode if the unit exists
|
||||||
|
hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType })
|
||||||
|
return util.Iif(hasUnit, p.AccessMode, perm_model.AccessModeNone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Permission) SetUnitsWithDefaultAccessMode(units []*repo_model.RepoUnit, mode perm_model.AccessMode) {
|
||||||
|
p.units = units
|
||||||
|
p.unitsMode = make(map[unit.Type]perm_model.AccessMode)
|
||||||
|
for _, u := range p.units {
|
||||||
|
p.unitsMode[u.Type] = mode
|
||||||
}
|
}
|
||||||
return p.UnitsMode[unitType]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanAccess returns true if user has mode access to the unit of the repository
|
// CanAccess returns true if user has mode access to the unit of the repository
|
||||||
|
@ -103,8 +125,8 @@ func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Permission) ReadableUnitTypes() []unit.Type {
|
func (p *Permission) ReadableUnitTypes() []unit.Type {
|
||||||
types := make([]unit.Type, 0, len(p.Units))
|
types := make([]unit.Type, 0, len(p.units))
|
||||||
for _, u := range p.Units {
|
for _, u := range p.units {
|
||||||
if p.CanRead(u.Type) {
|
if p.CanRead(u.Type) {
|
||||||
types = append(types, u.Type)
|
types = append(types, u.Type)
|
||||||
}
|
}
|
||||||
|
@ -114,21 +136,21 @@ func (p *Permission) ReadableUnitTypes() []unit.Type {
|
||||||
|
|
||||||
func (p *Permission) LogString() string {
|
func (p *Permission) LogString() string {
|
||||||
format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ "
|
format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ "
|
||||||
args := []any{p.AccessMode.String(), len(p.Units), len(p.UnitsMode)}
|
args := []any{p.AccessMode.ToString(), len(p.units), len(p.unitsMode)}
|
||||||
|
|
||||||
for i, unit := range p.Units {
|
for i, u := range p.units {
|
||||||
config := ""
|
config := ""
|
||||||
if unit.Config != nil {
|
if u.Config != nil {
|
||||||
configBytes, err := unit.Config.ToDB()
|
configBytes, err := u.Config.ToDB()
|
||||||
config = string(configBytes)
|
config = string(configBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
config = err.Error()
|
config = err.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s"
|
format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s"
|
||||||
args = append(args, i, unit.ID, unit.RepoID, unit.Type.LogString(), config)
|
args = append(args, i, u.ID, u.RepoID, u.Type.LogString(), config)
|
||||||
}
|
}
|
||||||
for key, value := range p.UnitsMode {
|
for key, value := range p.unitsMode {
|
||||||
format += "\nUnitMode[%-v]: %-v"
|
format += "\nUnitMode[%-v]: %-v"
|
||||||
args = append(args, key.LogString(), value.LogString())
|
args = append(args, key.LogString(), value.LogString())
|
||||||
}
|
}
|
||||||
|
@ -136,23 +158,34 @@ func (p *Permission) LogString() string {
|
||||||
return fmt.Sprintf(format, args...)
|
return fmt.Sprintf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserRepoPermission returns the user permissions to the repository
|
func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
|
||||||
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) {
|
if user != nil && user.ID > 0 {
|
||||||
var perm Permission
|
for _, u := range perm.units {
|
||||||
if log.IsTrace() {
|
if perm.unitsMode == nil {
|
||||||
defer func() {
|
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
|
||||||
if user == nil {
|
|
||||||
log.Trace("Permission Loaded for anonymous user in %-v:\nPermissions: %-+v",
|
|
||||||
repo,
|
|
||||||
perm)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Trace("Permission Loaded for %-v in %-v:\nPermissions: %-+v",
|
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.unitsMode[u.Type] {
|
||||||
user,
|
perm.unitsMode[u.Type] = u.EveryoneAccessMode
|
||||||
repo,
|
}
|
||||||
perm)
|
}
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserRepoPermission returns the user permissions to the repository
|
||||||
|
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
applyEveryoneRepoPermission(user, &perm)
|
||||||
|
}
|
||||||
|
if log.IsTrace() {
|
||||||
|
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = repo.LoadUnits(ctx); err != nil {
|
||||||
|
return perm, err
|
||||||
|
}
|
||||||
|
perm.units = repo.Units
|
||||||
|
|
||||||
// anonymous user visit private repo.
|
// anonymous user visit private repo.
|
||||||
// TODO: anonymous user visit public unit of private repo???
|
// TODO: anonymous user visit public unit of private repo???
|
||||||
|
@ -162,7 +195,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||||
}
|
}
|
||||||
|
|
||||||
var isCollaborator bool
|
var isCollaborator bool
|
||||||
var err error
|
|
||||||
if user != nil {
|
if user != nil {
|
||||||
isCollaborator, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID)
|
isCollaborator, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -170,7 +202,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.LoadOwner(ctx); err != nil {
|
if err = repo.LoadOwner(ctx); err != nil {
|
||||||
return perm, err
|
return perm, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,12 +213,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.LoadUnits(ctx); err != nil {
|
|
||||||
return perm, err
|
|
||||||
}
|
|
||||||
|
|
||||||
perm.Units = repo.Units
|
|
||||||
|
|
||||||
// anonymous visit public repo
|
// anonymous visit public repo
|
||||||
if user == nil {
|
if user == nil {
|
||||||
perm.AccessMode = perm_model.AccessModeRead
|
perm.AccessMode = perm_model.AccessModeRead
|
||||||
|
@ -205,19 +231,16 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||||
return perm, err
|
return perm, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.LoadOwner(ctx); err != nil {
|
|
||||||
return perm, err
|
|
||||||
}
|
|
||||||
if !repo.Owner.IsOrganization() {
|
if !repo.Owner.IsOrganization() {
|
||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
perm.UnitsMode = make(map[unit.Type]perm_model.AccessMode)
|
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
|
||||||
|
|
||||||
// Collaborators on organization
|
// Collaborators on organization
|
||||||
if isCollaborator {
|
if isCollaborator {
|
||||||
for _, u := range repo.Units {
|
for _, u := range repo.Units {
|
||||||
perm.UnitsMode[u.Type] = perm.AccessMode
|
perm.unitsMode[u.Type] = perm.AccessMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +254,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||||
for _, team := range teams {
|
for _, team := range teams {
|
||||||
if team.AccessMode >= perm_model.AccessModeAdmin {
|
if team.AccessMode >= perm_model.AccessModeAdmin {
|
||||||
perm.AccessMode = perm_model.AccessModeOwner
|
perm.AccessMode = perm_model.AccessModeOwner
|
||||||
perm.UnitsMode = nil
|
perm.unitsMode = nil
|
||||||
return perm, nil
|
return perm, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,25 +263,25 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||||
var found bool
|
var found bool
|
||||||
for _, team := range teams {
|
for _, team := range teams {
|
||||||
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
|
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
|
||||||
perm.UnitsMode[u.Type] = max(perm.UnitsMode[u.Type], teamMode)
|
perm.unitsMode[u.Type] = max(perm.unitsMode[u.Type], teamMode)
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
|
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
|
||||||
if !found && !repo.IsPrivate && !user.IsRestricted {
|
if !found && !repo.IsPrivate && !user.IsRestricted {
|
||||||
if _, ok := perm.UnitsMode[u.Type]; !ok {
|
if _, ok := perm.unitsMode[u.Type]; !ok {
|
||||||
perm.UnitsMode[u.Type] = perm_model.AccessModeRead
|
perm.unitsMode[u.Type] = perm_model.AccessModeRead
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove no permission units
|
// remove no permission units
|
||||||
perm.Units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
|
perm.units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
|
||||||
for t := range perm.UnitsMode {
|
for t := range perm.unitsMode {
|
||||||
for _, u := range repo.Units {
|
for _, u := range repo.Units {
|
||||||
if u.Type == t {
|
if u.Type == t {
|
||||||
perm.Units = append(perm.Units, u)
|
perm.units = append(perm.units, u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -340,7 +363,7 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.
|
||||||
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
|
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
|
||||||
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
|
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
|
||||||
if user.IsOrganization() {
|
if user.IsOrganization() {
|
||||||
return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
|
return false, fmt.Errorf("organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
|
||||||
}
|
}
|
||||||
perm, err := GetUserRepoPermission(ctx, repo, user)
|
perm, err := GetUserRepoPermission(ctx, repo, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package access
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
perm_model "code.gitea.io/gitea/models/perm"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApplyEveryoneRepoPermission(t *testing.T) {
|
||||||
|
perm := Permission{
|
||||||
|
AccessMode: perm_model.AccessModeNone,
|
||||||
|
units: []*repo_model.RepoUnit{
|
||||||
|
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeNone},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
applyEveryoneRepoPermission(nil, &perm)
|
||||||
|
assert.False(t, perm.CanRead(unit.TypeWiki))
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
AccessMode: perm_model.AccessModeNone,
|
||||||
|
units: []*repo_model.RepoUnit{
|
||||||
|
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
|
||||||
|
assert.True(t, perm.CanRead(unit.TypeWiki))
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
AccessMode: perm_model.AccessModeWrite,
|
||||||
|
units: []*repo_model.RepoUnit{
|
||||||
|
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
|
||||||
|
assert.True(t, perm.CanRead(unit.TypeWiki))
|
||||||
|
assert.False(t, perm.CanWrite(unit.TypeWiki)) // because there is no unit mode, so the everyone-mode is used as the unit's access mode
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
units: []*repo_model.RepoUnit{
|
||||||
|
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||||
|
},
|
||||||
|
unitsMode: map[unit.Type]perm_model.AccessMode{
|
||||||
|
unit.TypeWiki: perm_model.AccessModeWrite,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
|
||||||
|
assert.True(t, perm.CanWrite(unit.TypeWiki))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnitAccessMode(t *testing.T) {
|
||||||
|
perm := Permission{
|
||||||
|
AccessMode: perm_model.AccessModeNone,
|
||||||
|
}
|
||||||
|
assert.Equal(t, perm_model.AccessModeNone, perm.UnitAccessMode(unit.TypeWiki), "no unit, no map, use AccessMode")
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
AccessMode: perm_model.AccessModeRead,
|
||||||
|
units: []*repo_model.RepoUnit{
|
||||||
|
{Type: unit.TypeWiki},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "only unit, no map, use AccessMode")
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
AccessMode: perm_model.AccessModeAdmin,
|
||||||
|
unitsMode: map[unit.Type]perm_model.AccessMode{
|
||||||
|
unit.TypeWiki: perm_model.AccessModeRead,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, perm_model.AccessModeAdmin, perm.UnitAccessMode(unit.TypeWiki), "no unit, only map, admin overrides map")
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
AccessMode: perm_model.AccessModeNone,
|
||||||
|
unitsMode: map[unit.Type]perm_model.AccessMode{
|
||||||
|
unit.TypeWiki: perm_model.AccessModeRead,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "no unit, only map, use map")
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
AccessMode: perm_model.AccessModeNone,
|
||||||
|
units: []*repo_model.RepoUnit{
|
||||||
|
{Type: unit.TypeWiki},
|
||||||
|
},
|
||||||
|
unitsMode: map[unit.Type]perm_model.AccessMode{
|
||||||
|
unit.TypeWiki: perm_model.AccessModeRead,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map")
|
||||||
|
}
|
|
@ -5,25 +5,25 @@ package perm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccessMode specifies the users access mode
|
// AccessMode specifies the users access mode
|
||||||
type AccessMode int
|
type AccessMode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// AccessModeNone no access
|
AccessModeNone AccessMode = iota // 0: no access
|
||||||
AccessModeNone AccessMode = iota // 0
|
|
||||||
// AccessModeRead read access
|
AccessModeRead // 1: read access
|
||||||
AccessModeRead // 1
|
AccessModeWrite // 2: write access
|
||||||
// AccessModeWrite write access
|
AccessModeAdmin // 3: admin access
|
||||||
AccessModeWrite // 2
|
AccessModeOwner // 4: owner access
|
||||||
// AccessModeAdmin admin access
|
|
||||||
AccessModeAdmin // 3
|
|
||||||
// AccessModeOwner owner access
|
|
||||||
AccessModeOwner // 4
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (mode AccessMode) String() string {
|
// ToString returns the string representation of the access mode, do not make it a Stringer, otherwise it's difficult to render in templates
|
||||||
|
func (mode AccessMode) ToString() string {
|
||||||
switch mode {
|
switch mode {
|
||||||
case AccessModeRead:
|
case AccessModeRead:
|
||||||
return "read"
|
return "read"
|
||||||
|
@ -39,19 +39,24 @@ func (mode AccessMode) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mode AccessMode) LogString() string {
|
func (mode AccessMode) LogString() string {
|
||||||
return fmt.Sprintf("<AccessMode:%d:%s>", mode, mode.String())
|
return fmt.Sprintf("<AccessMode:%d:%s>", mode, mode.ToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAccessMode returns corresponding access mode to given permission string.
|
// ParseAccessMode returns corresponding access mode to given permission string.
|
||||||
func ParseAccessMode(permission string) AccessMode {
|
func ParseAccessMode(permission string, allowed ...AccessMode) AccessMode {
|
||||||
|
m := AccessModeNone
|
||||||
switch permission {
|
switch permission {
|
||||||
case "read":
|
case "read":
|
||||||
return AccessModeRead
|
m = AccessModeRead
|
||||||
case "write":
|
case "write":
|
||||||
return AccessModeWrite
|
m = AccessModeWrite
|
||||||
case "admin":
|
case "admin":
|
||||||
return AccessModeAdmin
|
m = AccessModeAdmin
|
||||||
default:
|
default:
|
||||||
return AccessModeNone
|
// the "owner" access is not really used for user input, it's mainly for checking access level in code, so don't parse it
|
||||||
}
|
}
|
||||||
|
if len(allowed) == 0 {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return util.Iif(slices.Contains(allowed, m), m, AccessModeNone)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package perm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessMode(t *testing.T) {
|
||||||
|
names := []string{"none", "read", "write", "admin"}
|
||||||
|
for i, name := range names {
|
||||||
|
m := ParseAccessMode(name)
|
||||||
|
assert.Equal(t, AccessMode(i), m)
|
||||||
|
}
|
||||||
|
assert.Equal(t, AccessMode(4), AccessModeOwner)
|
||||||
|
assert.Equal(t, "owner", AccessModeOwner.ToString())
|
||||||
|
assert.Equal(t, AccessModeNone, ParseAccessMode("owner"))
|
||||||
|
assert.Equal(t, AccessModeNone, ParseAccessMode("invalid"))
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -41,11 +42,12 @@ func (err ErrUnitTypeNotExist) Unwrap() error {
|
||||||
|
|
||||||
// RepoUnit describes all units of a repository
|
// RepoUnit describes all units of a repository
|
||||||
type RepoUnit struct { //revive:disable-line:exported
|
type RepoUnit struct { //revive:disable-line:exported
|
||||||
ID int64
|
ID int64
|
||||||
RepoID int64 `xorm:"INDEX(s)"`
|
RepoID int64 `xorm:"INDEX(s)"`
|
||||||
Type unit.Type `xorm:"INDEX(s)"`
|
Type unit.Type `xorm:"INDEX(s)"`
|
||||||
Config convert.Conversion `xorm:"TEXT"`
|
Config convert.Conversion `xorm:"TEXT"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
|
||||||
|
EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -191,16 +191,13 @@ type Unit struct {
|
||||||
NameKey string
|
NameKey string
|
||||||
URI string
|
URI string
|
||||||
DescKey string
|
DescKey string
|
||||||
Idx int
|
Priority int
|
||||||
MaxAccessMode perm.AccessMode // The max access mode of the unit. i.e. Read means this unit can only be read.
|
MaxAccessMode perm.AccessMode // The max access mode of the unit. i.e. Read means this unit can only be read.
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLessThan compares order of two units
|
// IsLessThan compares order of two units
|
||||||
func (u Unit) IsLessThan(unit Unit) bool {
|
func (u Unit) IsLessThan(unit Unit) bool {
|
||||||
if (u.Type == TypeExternalTracker || u.Type == TypeExternalWiki) && unit.Type != TypeExternalTracker && unit.Type != TypeExternalWiki {
|
return u.Priority < unit.Priority
|
||||||
return false
|
|
||||||
}
|
|
||||||
return u.Idx < unit.Idx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxPerm returns the max perms of this unit
|
// MaxPerm returns the max perms of this unit
|
||||||
|
@ -236,7 +233,7 @@ var (
|
||||||
"repo.ext_issues",
|
"repo.ext_issues",
|
||||||
"/issues",
|
"/issues",
|
||||||
"repo.ext_issues.desc",
|
"repo.ext_issues.desc",
|
||||||
1,
|
101,
|
||||||
perm.AccessModeRead,
|
perm.AccessModeRead,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +269,7 @@ var (
|
||||||
"repo.ext_wiki",
|
"repo.ext_wiki",
|
||||||
"/wiki",
|
"/wiki",
|
||||||
"repo.ext_wiki.desc",
|
"repo.ext_wiki.desc",
|
||||||
4,
|
102,
|
||||||
perm.AccessModeRead,
|
perm.AccessModeRead,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ func NewFuncMap() template.FuncMap {
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
// html/template related functions
|
// html/template related functions
|
||||||
"dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
|
"dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
|
||||||
|
"Iif": Iif,
|
||||||
"Eval": Eval,
|
"Eval": Eval,
|
||||||
"SafeHTML": SafeHTML,
|
"SafeHTML": SafeHTML,
|
||||||
"HTMLFormat": HTMLFormat,
|
"HTMLFormat": HTMLFormat,
|
||||||
|
@ -238,6 +239,17 @@ func DotEscape(raw string) string {
|
||||||
return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
|
return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version,
|
||||||
|
// and it could be simply used as "{{Iif expr trueVal}}" (omit the falseVal).
|
||||||
|
func Iif(condition bool, vals ...any) any {
|
||||||
|
if condition {
|
||||||
|
return vals[0]
|
||||||
|
} else if len(vals) > 1 {
|
||||||
|
return vals[1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Eval the expression and return the result, see the comment of eval.Expr for details.
|
// Eval the expression and return the result, see the comment of eval.Expr for details.
|
||||||
// To use this helper function in templates, pass each token as a separate parameter.
|
// To use this helper function in templates, pass each token as a separate parameter.
|
||||||
//
|
//
|
||||||
|
|
|
@ -885,6 +885,7 @@ repo_and_org_access = Repository and Organization Access
|
||||||
permissions_public_only = Public only
|
permissions_public_only = Public only
|
||||||
permissions_access_all = All (public, private, and limited)
|
permissions_access_all = All (public, private, and limited)
|
||||||
select_permissions = Select permissions
|
select_permissions = Select permissions
|
||||||
|
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
|
||||||
|
@ -2096,6 +2097,7 @@ 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_wiki_everyone_access = Default Access Permission for 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
|
||||||
|
|
|
@ -209,11 +209,7 @@ func repoAssignment() func(ctx *context.APIContext) {
|
||||||
ctx.Error(http.StatusInternalServerError, "LoadUnits", err)
|
ctx.Error(http.StatusInternalServerError, "LoadUnits", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Repo.Permission.Units = ctx.Repo.Repository.Units
|
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
|
||||||
ctx.Repo.Permission.UnitsMode = make(map[unit.Type]perm.AccessMode)
|
|
||||||
for _, u := range ctx.Repo.Repository.Units {
|
|
||||||
ctx.Repo.Permission.UnitsMode[u.Type] = ctx.Repo.Permission.AccessMode
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -481,11 +481,7 @@ func (ctx *preReceiveContext) loadPusherAndPermission() bool {
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ctx.userPerm.Units = ctx.Repo.Repository.Units
|
ctx.userPerm.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.userPerm.AccessMode)
|
||||||
ctx.userPerm.UnitsMode = make(map[unit.Type]perm_model.AccessMode)
|
|
||||||
for _, u := range ctx.Repo.Repository.Units {
|
|
||||||
ctx.userPerm.UnitsMode[u.Type] = ctx.userPerm.AccessMode
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
user, err := user_model.GetUserByID(ctx, ctx.opts.UserID)
|
user, err := user_model.GetUserByID(ctx, ctx.opts.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"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"
|
||||||
|
@ -476,9 +477,10 @@ func SettingsPost(ctx *context.Context) {
|
||||||
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, repo_model.RepoUnit{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
Type: unit_model.TypeWiki,
|
Type: unit_model.TypeWiki,
|
||||||
Config: new(repo_model.UnitConfig),
|
Config: new(repo_model.UnitConfig),
|
||||||
|
EveryoneAccessMode: perm.ParseAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite),
|
||||||
})
|
})
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -684,7 +684,7 @@ func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input i
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkHomeCodeViewable(ctx *context.Context) {
|
func checkHomeCodeViewable(ctx *context.Context) {
|
||||||
if len(ctx.Repo.Units) > 0 {
|
if ctx.Repo.HasUnits() {
|
||||||
if ctx.Repo.Repository.IsBeingCreated() {
|
if ctx.Repo.Repository.IsBeingCreated() {
|
||||||
task, err := admin_model.GetMigratingTask(ctx, ctx.Repo.Repository.ID)
|
task, err := admin_model.GetMigratingTask(ctx, ctx.Repo.Repository.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -723,6 +723,7 @@ func checkHomeCodeViewable(ctx *context.Context) {
|
||||||
var firstUnit *unit_model.Unit
|
var firstUnit *unit_model.Unit
|
||||||
for _, repoUnitType := range ctx.Repo.Permission.ReadableUnitTypes() {
|
for _, repoUnitType := range ctx.Repo.Permission.ReadableUnitTypes() {
|
||||||
if repoUnitType == unit_model.TypeCode {
|
if repoUnitType == unit_model.TypeCode {
|
||||||
|
// we are doing this check in "code" unit related pages, so if the code unit is readable, no need to do any further redirection
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -336,7 +336,7 @@ func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]
|
||||||
Description: t.Description,
|
Description: t.Description,
|
||||||
IncludesAllRepositories: t.IncludesAllRepositories,
|
IncludesAllRepositories: t.IncludesAllRepositories,
|
||||||
CanCreateOrgRepo: t.CanCreateOrgRepo,
|
CanCreateOrgRepo: t.CanCreateOrgRepo,
|
||||||
Permission: t.AccessMode.String(),
|
Permission: t.AccessMode.ToString(),
|
||||||
Units: t.GetUnitNames(),
|
Units: t.GetUnitNames(),
|
||||||
UnitsMap: t.GetUnitsMap(),
|
UnitsMap: t.GetUnitsMap(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,12 +25,13 @@ func ToRepo(ctx context.Context, repo *repo_model.Repository, permissionInRepo a
|
||||||
func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInRepo access_model.Permission, isParent bool) *api.Repository {
|
func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInRepo access_model.Permission, isParent bool) *api.Repository {
|
||||||
var parent *api.Repository
|
var parent *api.Repository
|
||||||
|
|
||||||
if permissionInRepo.Units == nil && permissionInRepo.UnitsMode == nil {
|
if !permissionInRepo.HasUnits() && permissionInRepo.AccessMode > perm.AccessModeNone {
|
||||||
// If Units and UnitsMode are both nil, it means that it's a hard coded permission,
|
// If units is empty, it means that it's a hard-coded permission, like access_model.Permission{AccessMode: perm.AccessModeAdmin}
|
||||||
// like access_model.Permission{AccessMode: perm.AccessModeAdmin}.
|
// So we need to load units for the repo, otherwise UnitAccessMode will just return perm.AccessModeNone.
|
||||||
// So we need to load units for the repo, or UnitAccessMode will always return perm.AccessModeNone.
|
// TODO: this logic is still not right (because unit modes are not correctly prepared)
|
||||||
|
// the caller should prepare a proper "permission" before calling this function.
|
||||||
_ = repo.LoadUnits(ctx) // the error is not important, so ignore it
|
_ = repo.LoadUnits(ctx) // the error is not important, so ignore it
|
||||||
permissionInRepo.Units = repo.Units
|
permissionInRepo.SetUnitsWithDefaultAccessMode(repo.Units, permissionInRepo.AccessMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
cloneLink := repo.CloneLink()
|
cloneLink := repo.CloneLink()
|
||||||
|
|
|
@ -103,7 +103,7 @@ func User2UserSettings(user *user_model.User) api.UserSettings {
|
||||||
func ToUserAndPermission(ctx context.Context, user, doer *user_model.User, accessMode perm.AccessMode) api.RepoCollaboratorPermission {
|
func ToUserAndPermission(ctx context.Context, user, doer *user_model.User, accessMode perm.AccessMode) api.RepoCollaboratorPermission {
|
||||||
return api.RepoCollaboratorPermission{
|
return api.RepoCollaboratorPermission{
|
||||||
User: ToUser(ctx, user, doer),
|
User: ToUser(ctx, user, doer),
|
||||||
Permission: accessMode.String(),
|
Permission: accessMode.ToString(),
|
||||||
RoleName: accessMode.String(),
|
RoleName: accessMode.ToString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,7 @@ type RepoSettingForm struct {
|
||||||
EnableWiki bool
|
EnableWiki bool
|
||||||
EnableExternalWiki bool
|
EnableExternalWiki bool
|
||||||
DefaultWikiBranch string
|
DefaultWikiBranch string
|
||||||
|
DefaultWikiEveryoneAccess string
|
||||||
ExternalWikiURL string
|
ExternalWikiURL string
|
||||||
EnableIssues bool
|
EnableIssues bool
|
||||||
EnableExternalTracker bool
|
EnableExternalTracker bool
|
||||||
|
|
|
@ -317,7 +317,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{$isWikiEnabled := or (.Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeWiki) (.Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeExternalWiki)}}
|
{{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}}
|
||||||
|
{{$isExternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeExternalWiki}}
|
||||||
|
{{$isWikiEnabled := or $isInternalWikiEnabled $isExternalWikiEnabled}}
|
||||||
{{$isWikiGlobalDisabled := ctx.Consts.RepoUnitTypeWiki.UnitGlobalDisabled}}
|
{{$isWikiGlobalDisabled := ctx.Consts.RepoUnitTypeWiki.UnitGlobalDisabled}}
|
||||||
{{$isExternalWikiGlobalDisabled := ctx.Consts.RepoUnitTypeExternalWiki.UnitGlobalDisabled}}
|
{{$isExternalWikiGlobalDisabled := ctx.Consts.RepoUnitTypeExternalWiki.UnitGlobalDisabled}}
|
||||||
{{$isBothWikiGlobalDisabled := and $isWikiGlobalDisabled $isExternalWikiGlobalDisabled}}
|
{{$isBothWikiGlobalDisabled := and $isWikiGlobalDisabled $isExternalWikiGlobalDisabled}}
|
||||||
|
@ -331,21 +333,33 @@
|
||||||
<div class="field{{if not $isWikiEnabled}} disabled{{end}}" id="wiki_box">
|
<div class="field{{if not $isWikiEnabled}} disabled{{end}}" id="wiki_box">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui radio checkbox{{if $isWikiGlobalDisabled}} disabled{{end}}"{{if $isWikiGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
|
<div class="ui radio checkbox{{if $isWikiGlobalDisabled}} disabled{{end}}"{{if $isWikiGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
|
||||||
<input class="enable-system-radio" name="enable_external_wiki" type="radio" value="false" data-target="#external_wiki_box" {{if not (.Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeExternalWiki)}}checked{{end}}>
|
<input class="enable-system-radio" name="enable_external_wiki" type="radio" value="false" data-context="#internal_wiki_box" data-target="#external_wiki_box" {{if $isInternalWikiEnabled}}checked{{end}}>
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.use_internal_wiki"}}</label>
|
<label>{{ctx.Locale.Tr "repo.settings.use_internal_wiki"}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline field tw-pl-4">
|
<div id="internal_wiki_box" class="field tw-pl-4 {{if not $isInternalWikiEnabled}}disabled{{end}}">
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.default_wiki_branch_name"}}</label>
|
<div class="inline field">
|
||||||
<input name="default_wiki_branch" value="{{.Repository.DefaultWikiBranch}}">
|
<label>{{ctx.Locale.Tr "repo.settings.default_wiki_branch_name"}}</label>
|
||||||
|
<input name="default_wiki_branch" value="{{.Repository.DefaultWikiBranch}}">
|
||||||
|
</div>
|
||||||
|
<div class="inline field">
|
||||||
|
{{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}}
|
||||||
|
<label>{{ctx.Locale.Tr "repo.settings.default_wiki_everyone_access"}}</label>
|
||||||
|
<select name="default_wiki_everyone_access" class="ui 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}}>
|
||||||
<input class="enable-system-radio" name="enable_external_wiki" type="radio" value="true" data-target="#external_wiki_box" {{if .Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeExternalWiki}}checked{{end}}>
|
<input class="enable-system-radio" name="enable_external_wiki" type="radio" value="true" data-context="#internal_wiki_box" data-target="#external_wiki_box" {{if $isExternalWikiEnabled}}checked{{end}}>
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.use_external_wiki"}}</label>
|
<label>{{ctx.Locale.Tr "repo.settings.use_external_wiki"}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field tw-pl-4 {{if not (.Repository.UnitEnabled $.Context ctx.Consts.RepoUnitTypeExternalWiki)}}disabled{{end}}" id="external_wiki_box">
|
<div id="external_wiki_box" class="field tw-pl-4 {{if not $isExternalWikiEnabled}}disabled{{end}}">
|
||||||
<label for="external_wiki_url">{{ctx.Locale.Tr "repo.settings.external_wiki_url"}}</label>
|
<label for="external_wiki_url">{{ctx.Locale.Tr "repo.settings.external_wiki_url"}}</label>
|
||||||
<input id="external_wiki_url" name="external_wiki_url" type="url" value="{{(.Repository.MustGetUnit $.Context ctx.Consts.RepoUnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}">
|
<input id="external_wiki_url" name="external_wiki_url" type="url" value="{{(.Repository.MustGetUnit $.Context ctx.Consts.RepoUnitTypeExternalWiki).ExternalWikiConfig.ExternalWikiURL}}">
|
||||||
<p class="help">{{ctx.Locale.Tr "repo.settings.external_wiki_url_desc"}}</p>
|
<p class="help">{{ctx.Locale.Tr "repo.settings.external_wiki_url_desc"}}</p>
|
||||||
|
|
|
@ -126,7 +126,7 @@ func TestAPITeam(t *testing.T) {
|
||||||
apiTeam = api.Team{}
|
apiTeam = api.Team{}
|
||||||
DecodeJSON(t, resp, &apiTeam)
|
DecodeJSON(t, resp, &apiTeam)
|
||||||
checkTeamResponse(t, "ReadTeam1", &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
|
checkTeamResponse(t, "ReadTeam1", &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
|
||||||
teamRead.AccessMode.String(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
|
teamRead.AccessMode.ToString(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
|
||||||
|
|
||||||
// Delete team.
|
// Delete team.
|
||||||
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID).
|
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID).
|
||||||
|
@ -197,7 +197,7 @@ func TestAPITeam(t *testing.T) {
|
||||||
DecodeJSON(t, resp, &apiTeam)
|
DecodeJSON(t, resp, &apiTeam)
|
||||||
assert.NoError(t, teamRead.LoadUnits(db.DefaultContext))
|
assert.NoError(t, teamRead.LoadUnits(db.DefaultContext))
|
||||||
checkTeamResponse(t, "ReadTeam2", &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
|
checkTeamResponse(t, "ReadTeam2", &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
|
||||||
teamRead.AccessMode.String(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
|
teamRead.AccessMode.ToString(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
|
||||||
|
|
||||||
// Delete team.
|
// Delete team.
|
||||||
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID).
|
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID).
|
||||||
|
|
Loading…
Reference in New Issue