mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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:
		| @@ -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 | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								models/migrations/v1_23/v297.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								models/migrations/v1_23/v297.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 maxMode | ||||||
| 	return max |  | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 |  | ||||||
|  | // HasUnits returns true if the permission contains attached units | ||||||
|  | func (p *Permission) HasUnits() bool { | ||||||
|  | 	return len(p.units) > 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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 { | ||||||
|  | 	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 | // UnitAccessMode returns current user access mode to the specify unit of the repository | ||||||
| func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode { | func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode { | ||||||
| 	if p.UnitsMode == nil { | 	if p.unitsMode != nil { | ||||||
| 		for _, u := range p.Units { | 		// if the units map contains the access mode, use it, but admin/owner mode could override it | ||||||
| 			if u.Type == unitType { | 		if m, ok := p.unitsMode[unitType]; ok { | ||||||
| 				return p.AccessMode | 			return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 		return perm_model.AccessModeNone | 	// 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...) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) { | ||||||
|  | 	if user != nil && user.ID > 0 { | ||||||
|  | 		for _, u := range perm.units { | ||||||
|  | 			if perm.unitsMode == nil { | ||||||
|  | 				perm.unitsMode = make(map[unit.Type]perm_model.AccessMode) | ||||||
|  | 			} | ||||||
|  | 			if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.unitsMode[u.Type] { | ||||||
|  | 				perm.unitsMode[u.Type] = u.EveryoneAccessMode | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| // GetUserRepoPermission returns the user permissions to the repository | // GetUserRepoPermission returns the user permissions to the repository | ||||||
| func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) { | func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { | ||||||
| 	var perm Permission |  | ||||||
| 	if log.IsTrace() { |  | ||||||
| 	defer func() { | 	defer func() { | ||||||
| 			if user == nil { | 		if err == nil { | ||||||
| 				log.Trace("Permission Loaded for anonymous user in %-v:\nPermissions: %-+v", | 			applyEveryoneRepoPermission(user, &perm) | ||||||
| 					repo, | 		} | ||||||
| 					perm) | 		if log.IsTrace() { | ||||||
| 				return | 			log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm) | ||||||
| 		} | 		} | ||||||
| 			log.Trace("Permission Loaded for %-v in %-v:\nPermissions: %-+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 { | ||||||
|   | |||||||
							
								
								
									
										98
									
								
								models/perm/access/repo_permission_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								models/perm/access/repo_permission_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								models/perm/access_mode_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								models/perm/access_mode_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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" | ||||||
| @@ -46,6 +47,7 @@ type RepoUnit struct { //revive:disable-line:exported | |||||||
| 	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" | ||||||
| @@ -479,6 +480,7 @@ func SettingsPost(ctx *context.Context) { | |||||||
| 				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}}"> | ||||||
|  | 						<div class="inline field"> | ||||||
| 							<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_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 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). | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user