@@ -6,6 +6,7 @@ package access
6
6
import (
7
7
"context"
8
8
"fmt"
9
+ "slices"
9
10
10
11
"code.gitea.io/gitea/models/db"
11
12
"code.gitea.io/gitea/models/organization"
@@ -14,13 +15,15 @@ import (
14
15
"code.gitea.io/gitea/models/unit"
15
16
user_model "code.gitea.io/gitea/models/user"
16
17
"code.gitea.io/gitea/modules/log"
18
+ "code.gitea.io/gitea/modules/util"
17
19
)
18
20
19
21
// Permission contains all the permissions related variables to a repository for a user
20
22
type Permission struct {
21
23
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
24
27
}
25
28
26
29
// IsOwner returns true if current user is the owner of repository.
@@ -33,25 +36,44 @@ func (p *Permission) IsAdmin() bool {
33
36
return p .AccessMode >= perm_model .AccessModeAdmin
34
37
}
35
38
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
37
40
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
40
54
}
41
- return len ( p . UnitsMode ) > 0
55
+ return 0
42
56
}
43
57
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
45
59
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 )
51
64
}
52
- return perm_model .AccessModeNone
53
65
}
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
+ }
55
77
}
56
78
57
79
// 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 {
103
125
}
104
126
105
127
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 {
108
130
if p .CanRead (u .Type ) {
109
131
types = append (types , u .Type )
110
132
}
@@ -114,45 +136,56 @@ func (p *Permission) ReadableUnitTypes() []unit.Type {
114
136
115
137
func (p * Permission ) LogString () string {
116
138
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 )}
118
140
119
- for i , unit := range p .Units {
141
+ for i , u := range p .units {
120
142
config := ""
121
- if unit .Config != nil {
122
- configBytes , err := unit .Config .ToDB ()
143
+ if u .Config != nil {
144
+ configBytes , err := u .Config .ToDB ()
123
145
config = string (configBytes )
124
146
if err != nil {
125
147
config = err .Error ()
126
148
}
127
149
}
128
150
format += "\n Units[%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 )
130
152
}
131
- for key , value := range p .UnitsMode {
153
+ for key , value := range p .unitsMode {
132
154
format += "\n UnitMode[%-v]: %-v"
133
155
args = append (args , key .LogString (), value .LogString ())
134
156
}
135
157
format += " ]>"
136
158
return fmt .Sprintf (format , args ... )
137
159
}
138
160
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:\n Permissions: %-+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 )
149
166
}
150
- log .Trace ("Permission Loaded for %-v in %-v:\n Permissions: %-+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
155
187
}
188
+ perm .units = repo .Units
156
189
157
190
// anonymous user visit private repo.
158
191
// TODO: anonymous user visit public unit of private repo???
@@ -162,15 +195,14 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
162
195
}
163
196
164
197
var isCollaborator bool
165
- var err error
166
198
if user != nil {
167
199
isCollaborator , err = repo_model .IsCollaborator (ctx , repo .ID , user .ID )
168
200
if err != nil {
169
201
return perm , err
170
202
}
171
203
}
172
204
173
- if err : = repo .LoadOwner (ctx ); err != nil {
205
+ if err = repo .LoadOwner (ctx ); err != nil {
174
206
return perm , err
175
207
}
176
208
@@ -181,12 +213,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
181
213
return perm , nil
182
214
}
183
215
184
- if err := repo .LoadUnits (ctx ); err != nil {
185
- return perm , err
186
- }
187
-
188
- perm .Units = repo .Units
189
-
190
216
// anonymous visit public repo
191
217
if user == nil {
192
218
perm .AccessMode = perm_model .AccessModeRead
@@ -205,19 +231,16 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
205
231
return perm , err
206
232
}
207
233
208
- if err := repo .LoadOwner (ctx ); err != nil {
209
- return perm , err
210
- }
211
234
if ! repo .Owner .IsOrganization () {
212
235
return perm , nil
213
236
}
214
237
215
- perm .UnitsMode = make (map [unit.Type ]perm_model.AccessMode )
238
+ perm .unitsMode = make (map [unit.Type ]perm_model.AccessMode )
216
239
217
240
// Collaborators on organization
218
241
if isCollaborator {
219
242
for _ , u := range repo .Units {
220
- perm .UnitsMode [u .Type ] = perm .AccessMode
243
+ perm .unitsMode [u .Type ] = perm .AccessMode
221
244
}
222
245
}
223
246
@@ -231,7 +254,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
231
254
for _ , team := range teams {
232
255
if team .AccessMode >= perm_model .AccessModeAdmin {
233
256
perm .AccessMode = perm_model .AccessModeOwner
234
- perm .UnitsMode = nil
257
+ perm .unitsMode = nil
235
258
return perm , nil
236
259
}
237
260
}
@@ -240,25 +263,25 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
240
263
var found bool
241
264
for _ , team := range teams {
242
265
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 )
244
267
found = true
245
268
}
246
269
}
247
270
248
271
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
249
272
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
252
275
}
253
276
}
254
277
}
255
278
256
279
// 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 {
259
282
for _ , u := range repo .Units {
260
283
if u .Type == t {
261
- perm .Units = append (perm .Units , u )
284
+ perm .units = append (perm .units , u )
262
285
}
263
286
}
264
287
}
@@ -340,7 +363,7 @@ func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.
340
363
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
341
364
func CanBeAssigned (ctx context.Context , user * user_model.User , repo * repo_model.Repository , _ bool ) (bool , error ) {
342
365
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 )
344
367
}
345
368
perm , err := GetUserRepoPermission (ctx , repo , user )
346
369
if err != nil {
0 commit comments