Skip to content

Commit 3feba9f

Browse files
authored
Allow everyone to read or write a wiki by a repo unit setting (#30495)
Replace #6312 Help #5833 Wiki solution for #639
1 parent bafb80f commit 3feba9f

File tree

24 files changed

+319
-128
lines changed

24 files changed

+319
-128
lines changed

models/issues/pull_list.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,13 @@ func CanMaintainerWriteToBranch(ctx context.Context, p access_model.Permission,
6262
return true
6363
}
6464

65-
if len(p.Units) < 1 {
65+
// the code below depends on units to get the repository ID, not ideal but just keep it for now
66+
firstUnitRepoID := p.GetFirstUnitRepoID()
67+
if firstUnitRepoID == 0 {
6668
return false
6769
}
6870

69-
prs, err := GetUnmergedPullRequestsByHeadInfo(ctx, p.Units[0].RepoID, branch)
71+
prs, err := GetUnmergedPullRequestsByHeadInfo(ctx, firstUnitRepoID, branch)
7072
if err != nil {
7173
return false
7274
}

models/migrations/migrations.go

+2
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,8 @@ var migrations = []Migration{
582582
NewMigration("Add commit status summary table", v1_23.AddCommitStatusSummary),
583583
// v296 -> v297
584584
NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2),
585+
// v297 -> v298
586+
NewMigration("Add everyone_access_mode for repo_unit", v1_23.AddRepoUnitEveryoneAccessMode),
585587
}
586588

587589
// GetCurrentDBVersion returns the current db version

models/migrations/v1_11/v111.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error {
336336
if err != nil {
337337
return false, err
338338
}
339-
if perm.UnitsMode == nil {
339+
if len(perm.UnitsMode) == 0 {
340340
for _, u := range perm.Units {
341341
if u.Type == UnitTypeCode {
342342
return AccessModeWrite <= perm.AccessMode, nil

models/migrations/v1_23/v297.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_23 //nolint
5+
6+
import (
7+
"code.gitea.io/gitea/models/perm"
8+
9+
"xorm.io/xorm"
10+
)
11+
12+
func AddRepoUnitEveryoneAccessMode(x *xorm.Engine) error {
13+
type RepoUnit struct { //revive:disable-line:exported
14+
EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
15+
}
16+
return x.Sync(&RepoUnit{})
17+
}

models/organization/team.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,11 @@ func (t *Team) GetUnitsMap() map[string]string {
130130
m := make(map[string]string)
131131
if t.AccessMode >= perm.AccessModeAdmin {
132132
for _, u := range unit.Units {
133-
m[u.NameKey] = t.AccessMode.String()
133+
m[u.NameKey] = t.AccessMode.ToString()
134134
}
135135
} else {
136136
for _, u := range t.Units {
137-
m[u.Unit().NameKey] = u.AccessMode.String()
137+
m[u.Unit().NameKey] = u.AccessMode.ToString()
138138
}
139139
}
140140
return m

models/perm/access/access.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,11 @@ func accessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Re
6363
}
6464

6565
func maxAccessMode(modes ...perm.AccessMode) perm.AccessMode {
66-
max := perm.AccessModeNone
66+
maxMode := perm.AccessModeNone
6767
for _, mode := range modes {
68-
if mode > max {
69-
max = mode
70-
}
68+
maxMode = max(maxMode, mode)
7169
}
72-
return max
70+
return maxMode
7371
}
7472

7573
type userAccess struct {

models/perm/access/repo_permission.go

+81-58
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package access
66
import (
77
"context"
88
"fmt"
9+
"slices"
910

1011
"code.gitea.io/gitea/models/db"
1112
"code.gitea.io/gitea/models/organization"
@@ -14,13 +15,15 @@ import (
1415
"code.gitea.io/gitea/models/unit"
1516
user_model "code.gitea.io/gitea/models/user"
1617
"code.gitea.io/gitea/modules/log"
18+
"code.gitea.io/gitea/modules/util"
1719
)
1820

1921
// Permission contains all the permissions related variables to a repository for a user
2022
type Permission struct {
2123
AccessMode perm_model.AccessMode
22-
Units []*repo_model.RepoUnit
23-
UnitsMode map[unit.Type]perm_model.AccessMode
24+
25+
units []*repo_model.RepoUnit
26+
unitsMode map[unit.Type]perm_model.AccessMode
2427
}
2528

2629
// IsOwner returns true if current user is the owner of repository.
@@ -33,25 +36,44 @@ func (p *Permission) IsAdmin() bool {
3336
return p.AccessMode >= perm_model.AccessModeAdmin
3437
}
3538

36-
// HasAccess returns true if the current user has at least read access to any unit of this repository
39+
// HasAccess returns true if the current user might have at least read access to any unit of this repository
3740
func (p *Permission) HasAccess() bool {
38-
if p.UnitsMode == nil {
39-
return p.AccessMode >= perm_model.AccessModeRead
41+
return len(p.unitsMode) > 0 || p.AccessMode >= perm_model.AccessModeRead
42+
}
43+
44+
// HasUnits returns true if the permission contains attached units
45+
func (p *Permission) HasUnits() bool {
46+
return len(p.units) > 0
47+
}
48+
49+
// GetFirstUnitRepoID returns the repo ID of the first unit, it is a fragile design and should NOT be used anymore
50+
// deprecated
51+
func (p *Permission) GetFirstUnitRepoID() int64 {
52+
if len(p.units) > 0 {
53+
return p.units[0].RepoID
4054
}
41-
return len(p.UnitsMode) > 0
55+
return 0
4256
}
4357

44-
// UnitAccessMode returns current user accessmode to the specify unit of the repository
58+
// UnitAccessMode returns current user access mode to the specify unit of the repository
4559
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
46-
if p.UnitsMode == nil {
47-
for _, u := range p.Units {
48-
if u.Type == unitType {
49-
return p.AccessMode
50-
}
60+
if p.unitsMode != nil {
61+
// if the units map contains the access mode, use it, but admin/owner mode could override it
62+
if m, ok := p.unitsMode[unitType]; ok {
63+
return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m)
5164
}
52-
return perm_model.AccessModeNone
5365
}
54-
return p.UnitsMode[unitType]
66+
// if the units map does not contain the access mode, return the default access mode if the unit exists
67+
hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType })
68+
return util.Iif(hasUnit, p.AccessMode, perm_model.AccessModeNone)
69+
}
70+
71+
func (p *Permission) SetUnitsWithDefaultAccessMode(units []*repo_model.RepoUnit, mode perm_model.AccessMode) {
72+
p.units = units
73+
p.unitsMode = make(map[unit.Type]perm_model.AccessMode)
74+
for _, u := range p.units {
75+
p.unitsMode[u.Type] = mode
76+
}
5577
}
5678

5779
// 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 {
103125
}
104126

105127
func (p *Permission) ReadableUnitTypes() []unit.Type {
106-
types := make([]unit.Type, 0, len(p.Units))
107-
for _, u := range p.Units {
128+
types := make([]unit.Type, 0, len(p.units))
129+
for _, u := range p.units {
108130
if p.CanRead(u.Type) {
109131
types = append(types, u.Type)
110132
}
@@ -114,45 +136,56 @@ func (p *Permission) ReadableUnitTypes() []unit.Type {
114136

115137
func (p *Permission) LogString() string {
116138
format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ "
117-
args := []any{p.AccessMode.String(), len(p.Units), len(p.UnitsMode)}
139+
args := []any{p.AccessMode.ToString(), len(p.units), len(p.unitsMode)}
118140

119-
for i, unit := range p.Units {
141+
for i, u := range p.units {
120142
config := ""
121-
if unit.Config != nil {
122-
configBytes, err := unit.Config.ToDB()
143+
if u.Config != nil {
144+
configBytes, err := u.Config.ToDB()
123145
config = string(configBytes)
124146
if err != nil {
125147
config = err.Error()
126148
}
127149
}
128150
format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s"
129-
args = append(args, i, unit.ID, unit.RepoID, unit.Type.LogString(), config)
151+
args = append(args, i, u.ID, u.RepoID, u.Type.LogString(), config)
130152
}
131-
for key, value := range p.UnitsMode {
153+
for key, value := range p.unitsMode {
132154
format += "\nUnitMode[%-v]: %-v"
133155
args = append(args, key.LogString(), value.LogString())
134156
}
135157
format += " ]>"
136158
return fmt.Sprintf(format, args...)
137159
}
138160

139-
// GetUserRepoPermission returns the user permissions to the repository
140-
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) {
141-
var perm Permission
142-
if log.IsTrace() {
143-
defer func() {
144-
if user == nil {
145-
log.Trace("Permission Loaded for anonymous user in %-v:\nPermissions: %-+v",
146-
repo,
147-
perm)
148-
return
161+
func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
162+
if user != nil && user.ID > 0 {
163+
for _, u := range perm.units {
164+
if perm.unitsMode == nil {
165+
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
149166
}
150-
log.Trace("Permission Loaded for %-v in %-v:\nPermissions: %-+v",
151-
user,
152-
repo,
153-
perm)
154-
}()
167+
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.unitsMode[u.Type] {
168+
perm.unitsMode[u.Type] = u.EveryoneAccessMode
169+
}
170+
}
171+
}
172+
}
173+
174+
// GetUserRepoPermission returns the user permissions to the repository
175+
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
176+
defer func() {
177+
if err == nil {
178+
applyEveryoneRepoPermission(user, &perm)
179+
}
180+
if log.IsTrace() {
181+
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
182+
}
183+
}()
184+
185+
if err = repo.LoadUnits(ctx); err != nil {
186+
return perm, err
155187
}
188+
perm.units = repo.Units
156189

157190
// anonymous user visit private repo.
158191
// TODO: anonymous user visit public unit of private repo???
@@ -162,15 +195,14 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
162195
}
163196

164197
var isCollaborator bool
165-
var err error
166198
if user != nil {
167199
isCollaborator, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID)
168200
if err != nil {
169201
return perm, err
170202
}
171203
}
172204

173-
if err := repo.LoadOwner(ctx); err != nil {
205+
if err = repo.LoadOwner(ctx); err != nil {
174206
return perm, err
175207
}
176208

@@ -181,12 +213,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
181213
return perm, nil
182214
}
183215

184-
if err := repo.LoadUnits(ctx); err != nil {
185-
return perm, err
186-
}
187-
188-
perm.Units = repo.Units
189-
190216
// anonymous visit public repo
191217
if user == nil {
192218
perm.AccessMode = perm_model.AccessModeRead
@@ -205,19 +231,16 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
205231
return perm, err
206232
}
207233

208-
if err := repo.LoadOwner(ctx); err != nil {
209-
return perm, err
210-
}
211234
if !repo.Owner.IsOrganization() {
212235
return perm, nil
213236
}
214237

215-
perm.UnitsMode = make(map[unit.Type]perm_model.AccessMode)
238+
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
216239

217240
// Collaborators on organization
218241
if isCollaborator {
219242
for _, u := range repo.Units {
220-
perm.UnitsMode[u.Type] = perm.AccessMode
243+
perm.unitsMode[u.Type] = perm.AccessMode
221244
}
222245
}
223246

@@ -231,7 +254,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
231254
for _, team := range teams {
232255
if team.AccessMode >= perm_model.AccessModeAdmin {
233256
perm.AccessMode = perm_model.AccessModeOwner
234-
perm.UnitsMode = nil
257+
perm.unitsMode = nil
235258
return perm, nil
236259
}
237260
}
@@ -240,25 +263,25 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
240263
var found bool
241264
for _, team := range teams {
242265
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
243-
perm.UnitsMode[u.Type] = max(perm.UnitsMode[u.Type], teamMode)
266+
perm.unitsMode[u.Type] = max(perm.unitsMode[u.Type], teamMode)
244267
found = true
245268
}
246269
}
247270

248271
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
249272
if !found && !repo.IsPrivate && !user.IsRestricted {
250-
if _, ok := perm.UnitsMode[u.Type]; !ok {
251-
perm.UnitsMode[u.Type] = perm_model.AccessModeRead
273+
if _, ok := perm.unitsMode[u.Type]; !ok {
274+
perm.unitsMode[u.Type] = perm_model.AccessModeRead
252275
}
253276
}
254277
}
255278

256279
// remove no permission units
257-
perm.Units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
258-
for t := range perm.UnitsMode {
280+
perm.units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
281+
for t := range perm.unitsMode {
259282
for _, u := range repo.Units {
260283
if u.Type == t {
261-
perm.Units = append(perm.Units, u)
284+
perm.units = append(perm.units, u)
262285
}
263286
}
264287
}
@@ -340,7 +363,7 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.
340363
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
341364
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
342365
if user.IsOrganization() {
343-
return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
366+
return false, fmt.Errorf("organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
344367
}
345368
perm, err := GetUserRepoPermission(ctx, repo, user)
346369
if err != nil {

0 commit comments

Comments
 (0)