@@ -16,6 +16,7 @@ import (
1616 "code.gitea.io/gitea/models/unit"
1717 user_model "code.gitea.io/gitea/models/user"
1818 "code.gitea.io/gitea/modules/log"
19+ "code.gitea.io/gitea/modules/setting"
1920 "code.gitea.io/gitea/modules/util"
2021)
2122
@@ -26,7 +27,8 @@ type Permission struct {
2627 units []* repo_model.RepoUnit
2728 unitsMode map [unit.Type ]perm_model.AccessMode
2829
29- everyoneAccessMode map [unit.Type ]perm_model.AccessMode
30+ everyoneAccessMode map [unit.Type ]perm_model.AccessMode // the unit's minimal access mode for every signed-in user
31+ anonymousAccessMode map [unit.Type ]perm_model.AccessMode // the unit's minimal access mode for anonymous (non-signed-in) user
3032}
3133
3234// IsOwner returns true if current user is the owner of repository.
@@ -40,7 +42,8 @@ func (p *Permission) IsAdmin() bool {
4042}
4143
4244// HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository.
43- // It doesn't count the "everyone access mode".
45+ // It doesn't count the "public(anonymous/everyone) access mode".
46+ // TODO: most calls to this function should be replaced with `HasAnyUnitAccessOrPublicAccess`
4447func (p * Permission ) HasAnyUnitAccess () bool {
4548 for _ , v := range p .unitsMode {
4649 if v >= perm_model .AccessModeRead {
@@ -50,13 +53,22 @@ func (p *Permission) HasAnyUnitAccess() bool {
5053 return p .AccessMode >= perm_model .AccessModeRead
5154}
5255
53- func (p * Permission ) HasAnyUnitAccessOrEveryoneAccess () bool {
56+ func (p * Permission ) HasAnyUnitPublicAccess () bool {
57+ for _ , v := range p .anonymousAccessMode {
58+ if v >= perm_model .AccessModeRead {
59+ return true
60+ }
61+ }
5462 for _ , v := range p .everyoneAccessMode {
5563 if v >= perm_model .AccessModeRead {
5664 return true
5765 }
5866 }
59- return p .HasAnyUnitAccess ()
67+ return false
68+ }
69+
70+ func (p * Permission ) HasAnyUnitAccessOrPublicAccess () bool {
71+ return p .HasAnyUnitPublicAccess () || p .HasAnyUnitAccess ()
6072}
6173
6274// HasUnits returns true if the permission contains attached units
@@ -74,14 +86,16 @@ func (p *Permission) GetFirstUnitRepoID() int64 {
7486}
7587
7688// UnitAccessMode returns current user access mode to the specify unit of the repository
77- // It also considers "everyone access mode"
89+ // It also considers "public (anonymous/ everyone) access mode"
7890func (p * Permission ) UnitAccessMode (unitType unit.Type ) perm_model.AccessMode {
7991 // if the units map contains the access mode, use it, but admin/owner mode could override it
8092 if m , ok := p .unitsMode [unitType ]; ok {
8193 return util .Iif (p .AccessMode >= perm_model .AccessModeAdmin , p .AccessMode , m )
8294 }
8395 // if the units map does not contain the access mode, return the default access mode if the unit exists
84- unitDefaultAccessMode := max (p .AccessMode , p .everyoneAccessMode [unitType ])
96+ unitDefaultAccessMode := p .AccessMode
97+ unitDefaultAccessMode = max (unitDefaultAccessMode , p .anonymousAccessMode [unitType ])
98+ unitDefaultAccessMode = max (unitDefaultAccessMode , p .everyoneAccessMode [unitType ])
8599 hasUnit := slices .ContainsFunc (p .units , func (u * repo_model.RepoUnit ) bool { return u .Type == unitType })
86100 return util .Iif (hasUnit , unitDefaultAccessMode , perm_model .AccessModeNone )
87101}
@@ -153,7 +167,7 @@ func (p *Permission) ReadableUnitTypes() []unit.Type {
153167}
154168
155169func (p * Permission ) LogString () string {
156- format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ "
170+ format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): ["
157171 args := []any {p .AccessMode .ToString (), len (p .units ), len (p .unitsMode )}
158172
159173 for i , u := range p .units {
@@ -165,27 +179,77 @@ func (p *Permission) LogString() string {
165179 config = err .Error ()
166180 }
167181 }
168- format += "\n Units [%d]: ID: %d RepoID: %d Type: %s Config: %s"
182+ format += "\n \t units [%d]: ID= %d RepoID= %d Type= %s Config= %s"
169183 args = append (args , i , u .ID , u .RepoID , u .Type .LogString (), config )
170184 }
171185 for key , value := range p .unitsMode {
172- format += "\n UnitMode [%-v]: %-v"
186+ format += "\n \t unitsMode [%-v]: %-v"
173187 args = append (args , key .LogString (), value .LogString ())
174188 }
175- format += " ]>"
189+ format += "\n \t anonymousAccessMode: %-v"
190+ args = append (args , p .anonymousAccessMode )
191+ format += "\n \t everyoneAccessMode: %-v"
192+ args = append (args , p .everyoneAccessMode )
193+ format += "\n \t ]>"
176194 return fmt .Sprintf (format , args ... )
177195}
178196
179- func applyEveryoneRepoPermission (user * user_model.User , perm * Permission ) {
197+ func applyPublicAccessPermission (unitType unit.Type , accessMode perm_model.AccessMode , modeMap * map [unit.Type ]perm_model.AccessMode ) {
198+ if setting .Repository .ForcePrivate {
199+ return
200+ }
201+ if accessMode >= perm_model .AccessModeRead && accessMode > (* modeMap )[unitType ] {
202+ if * modeMap == nil {
203+ * modeMap = make (map [unit.Type ]perm_model.AccessMode )
204+ }
205+ (* modeMap )[unitType ] = accessMode
206+ }
207+ }
208+
209+ func finalProcessRepoUnitPermission (user * user_model.User , perm * Permission ) {
210+ // apply public (anonymous) access permissions
211+ for _ , u := range perm .units {
212+ applyPublicAccessPermission (u .Type , u .AnonymousAccessMode , & perm .anonymousAccessMode )
213+ }
214+
180215 if user == nil || user .ID <= 0 {
216+ // for anonymous access, it could be:
217+ // AccessMode is None or Read, units has repo units, unitModes is nil
181218 return
182219 }
220+
221+ // apply public (everyone) access permissions
183222 for _ , u := range perm .units {
184- if u .EveryoneAccessMode >= perm_model .AccessModeRead && u .EveryoneAccessMode > perm .everyoneAccessMode [u .Type ] {
185- if perm .everyoneAccessMode == nil {
186- perm .everyoneAccessMode = make (map [unit.Type ]perm_model.AccessMode )
223+ applyPublicAccessPermission (u .Type , u .EveryoneAccessMode , & perm .everyoneAccessMode )
224+ }
225+
226+ if perm .unitsMode == nil {
227+ // if unitsMode is not set, then it means that the default p.AccessMode applies to all units
228+ return
229+ }
230+
231+ // remove no permission units
232+ origPermUnits := perm .units
233+ perm .units = make ([]* repo_model.RepoUnit , 0 , len (perm .units ))
234+ for _ , u := range origPermUnits {
235+ shouldKeep := false
236+ for t := range perm .unitsMode {
237+ if shouldKeep = u .Type == t ; shouldKeep {
238+ break
239+ }
240+ }
241+ for t := range perm .anonymousAccessMode {
242+ if shouldKeep = shouldKeep || u .Type == t ; shouldKeep {
243+ break
187244 }
188- perm .everyoneAccessMode [u .Type ] = u .EveryoneAccessMode
245+ }
246+ for t := range perm .everyoneAccessMode {
247+ if shouldKeep = shouldKeep || u .Type == t ; shouldKeep {
248+ break
249+ }
250+ }
251+ if shouldKeep {
252+ perm .units = append (perm .units , u )
189253 }
190254 }
191255}
@@ -194,11 +258,9 @@ func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
194258func GetUserRepoPermission (ctx context.Context , repo * repo_model.Repository , user * user_model.User ) (perm Permission , err error ) {
195259 defer func () {
196260 if err == nil {
197- applyEveryoneRepoPermission (user , & perm )
198- }
199- if log .IsTrace () {
200- log .Trace ("Permission Loaded for user %-v in repo %-v, permissions: %-+v" , user , repo , perm )
261+ finalProcessRepoUnitPermission (user , & perm )
201262 }
263+ log .Trace ("Permission Loaded for user %-v in repo %-v, permissions: %-+v" , user , repo , perm )
202264 }()
203265
204266 if err = repo .LoadUnits (ctx ); err != nil {
@@ -207,7 +269,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
207269 perm .units = repo .Units
208270
209271 // anonymous user visit private repo.
210- // TODO: anonymous user visit public unit of private repo???
211272 if user == nil && repo .IsPrivate {
212273 perm .AccessMode = perm_model .AccessModeNone
213274 return perm , nil
@@ -226,7 +287,8 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
226287 }
227288
228289 // Prevent strangers from checking out public repo of private organization/users
229- // Allow user if they are collaborator of a repo within a private user or a private organization but not a member of the organization itself
290+ // Allow user if they are a collaborator of a repo within a private user or a private organization but not a member of the organization itself
291+ // TODO: rename it to "IsOwnerVisibleToDoer"
230292 if ! organization .HasOrgOrUserVisible (ctx , repo .Owner , user ) && ! isCollaborator {
231293 perm .AccessMode = perm_model .AccessModeNone
232294 return perm , nil
@@ -244,7 +306,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
244306 return perm , nil
245307 }
246308
247- // plain user
309+ // plain user TODO: this check should be replaced, only need to check collaborator access mode
248310 perm .AccessMode , err = accessLevel (ctx , user , repo )
249311 if err != nil {
250312 return perm , err
@@ -254,6 +316,19 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
254316 return perm , nil
255317 }
256318
319+ // now: the owner is visible to doer, if the repo is public, then the min access mode is read
320+ minAccessMode := util .Iif (! repo .IsPrivate && ! user .IsRestricted , perm_model .AccessModeRead , perm_model .AccessModeNone )
321+ perm .AccessMode = max (perm .AccessMode , minAccessMode )
322+
323+ // get units mode from teams
324+ teams , err := organization .GetUserRepoTeams (ctx , repo .OwnerID , user .ID , repo .ID )
325+ if err != nil {
326+ return perm , err
327+ }
328+ if len (teams ) == 0 {
329+ return perm , nil
330+ }
331+
257332 perm .unitsMode = make (map [unit.Type ]perm_model.AccessMode )
258333
259334 // Collaborators on organization
@@ -263,15 +338,9 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
263338 }
264339 }
265340
266- // get units mode from teams
267- teams , err := organization .GetUserRepoTeams (ctx , repo .OwnerID , user .ID , repo .ID )
268- if err != nil {
269- return perm , err
270- }
271-
272341 // if user in an owner team
273342 for _ , team := range teams {
274- if team .AccessMode >= perm_model . AccessModeAdmin {
343+ if team .HasAdminAccess () {
275344 perm .AccessMode = perm_model .AccessModeOwner
276345 perm .unitsMode = nil
277346 return perm , nil
@@ -285,7 +354,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
285354 return perm , nil
286355 }
287356 }
288-
289357 for _ , u := range repo .Units {
290358 var found bool
291359 for _ , team := range groupTeams {
@@ -296,27 +364,12 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
296364 }
297365 if ! found {
298366 for _ , team := range teams {
367+ unitAccessMode := minAccessMode
299368 if teamMode , exist := team .UnitAccessModeEx (ctx , u .Type ); exist {
300- perm . unitsMode [ u . Type ] = max (perm .unitsMode [u .Type ], teamMode )
369+ unitAccessMode = max (perm .unitsMode [u .Type ], unitAccessMode , teamMode )
301370 found = true
302371 }
303- }
304- }
305-
306- // for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
307- if ! found && ! repo .IsPrivate && ! user .IsRestricted {
308- if _ , ok := perm .unitsMode [u .Type ]; ! ok {
309- perm .unitsMode [u .Type ] = perm_model .AccessModeRead
310- }
311- }
312- }
313-
314- // remove no permission units
315- perm .units = make ([]* repo_model.RepoUnit , 0 , len (repo .Units ))
316- for t := range perm .unitsMode {
317- for _ , u := range repo .Units {
318- if u .Type == t {
319- perm .units = append (perm .units , u )
372+ perm .unitsMode [u .Type ] = unitAccessMode
320373 }
321374 }
322375 }
@@ -359,17 +412,6 @@ func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *use
359412 return true , nil
360413 }
361414
362- groupTeams , err := organization .GetUserGroupTeams (ctx , repo .GroupID , user .ID )
363- if err != nil {
364- return false , err
365- }
366-
367- for _ , team := range groupTeams {
368- if team .AccessMode >= perm_model .AccessModeAdmin {
369- return true , nil
370- }
371- }
372-
373415 teams , err := organization .GetUserRepoTeams (ctx , repo .OwnerID , user .ID , repo .ID )
374416 if err != nil {
375417 return false , err
0 commit comments