44package  access
55
66import  (
7+ 	group_model "code.gitea.io/gitea/models/group" 
78	"context" 
89	"fmt" 
910	"slices" 
1011
1112	"code.gitea.io/gitea/models/db" 
12- 	group_model "code.gitea.io/gitea/models/group" 
1313	"code.gitea.io/gitea/models/organization" 
1414	perm_model "code.gitea.io/gitea/models/perm" 
1515	repo_model "code.gitea.io/gitea/models/repo" 
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 
187239			}
188- 			perm .everyoneAccessMode [u .Type ] =  u .EveryoneAccessMode 
240+ 		}
241+ 		for  t  :=  range  perm .anonymousAccessMode  {
242+ 			if  shouldKeep  =  shouldKeep  ||  u .Type  ==  t ; shouldKeep  {
243+ 				break 
244+ 			}
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