@@ -6,6 +6,7 @@ package access
66import (
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
2022type 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
3740func (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
4559func (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
105127func (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
115137func (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 += "\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 )
130152 }
131- for key , value := range p .UnitsMode {
153+ for key , value := range p .unitsMode {
132154 format += "\n UnitMode[%-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:\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 )
149166 }
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
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.
341364func 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