Skip to content

Commit d6d3c96

Browse files
authored
Fix bug when a token is given public only (#32204)
1 parent d3ada91 commit d6d3c96

File tree

11 files changed

+176
-55
lines changed

11 files changed

+176
-55
lines changed

models/user/user.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,10 @@ func (u *User) IsIndividual() bool {
408408
return u.Type == UserTypeIndividual
409409
}
410410

411+
func (u *User) IsUser() bool {
412+
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
413+
}
414+
411415
// IsBot returns whether or not the user is of type bot
412416
func (u *User) IsBot() bool {
413417
return u.Type == UserTypeBot

routers/api/packages/api.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
6363
ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
6464
return
6565
}
66+
67+
// check if scope only applies to public resources
68+
publicOnly, err := scope.PublicOnly()
69+
if err != nil {
70+
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
71+
return
72+
}
73+
74+
if publicOnly {
75+
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
76+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
77+
return
78+
}
79+
}
6680
}
6781
}
6882

routers/api/v1/api.go

Lines changed: 83 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,62 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext)
235235
}
236236
}
237237

238+
func checkTokenPublicOnly() func(ctx *context.APIContext) {
239+
return func(ctx *context.APIContext) {
240+
if !ctx.PublicOnly {
241+
return
242+
}
243+
244+
requiredScopeCategories, ok := ctx.Data["requiredScopeCategories"].([]auth_model.AccessTokenScopeCategory)
245+
if !ok || len(requiredScopeCategories) == 0 {
246+
return
247+
}
248+
249+
// public Only permission check
250+
switch {
251+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
252+
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
253+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
254+
return
255+
}
256+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
257+
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
258+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public issues")
259+
return
260+
}
261+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
262+
if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
263+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
264+
return
265+
}
266+
if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
267+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
268+
return
269+
}
270+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
271+
if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
272+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users")
273+
return
274+
}
275+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
276+
if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
277+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub")
278+
return
279+
}
280+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
281+
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
282+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public notifications")
283+
return
284+
}
285+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
286+
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
287+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
288+
return
289+
}
290+
}
291+
}
292+
}
293+
238294
// if a token is being used for auth, we check that it contains the required scope
239295
// if a token is not being used, reqToken will enforce other sign in methods
240296
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
@@ -250,9 +306,6 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
250306
return
251307
}
252308

253-
ctx.Data["ApiTokenScopePublicRepoOnly"] = false
254-
ctx.Data["ApiTokenScopePublicOrgOnly"] = false
255-
256309
// use the http method to determine the access level
257310
requiredScopeLevel := auth_model.Read
258311
if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
@@ -261,29 +314,28 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
261314

262315
// get the required scope for the given access level and category
263316
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
264-
265-
// check if scope only applies to public resources
266-
publicOnly, err := scope.PublicOnly()
317+
allow, err := scope.HasScope(requiredScopes...)
267318
if err != nil {
268-
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
319+
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
269320
return
270321
}
271322

272-
// this context is used by the middleware in the specific route
273-
ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository)
274-
ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization)
275-
276-
allow, err := scope.HasScope(requiredScopes...)
277-
if err != nil {
278-
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
323+
if !allow {
324+
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
279325
return
280326
}
281327

282-
if allow {
328+
ctx.Data["requiredScopeCategories"] = requiredScopeCategories
329+
330+
// check if scope only applies to public resources
331+
publicOnly, err := scope.PublicOnly()
332+
if err != nil {
333+
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
283334
return
284335
}
285336

286-
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
337+
// assign to true so that those searching should only filter public repositories/users/organizations
338+
ctx.PublicOnly = publicOnly
287339
}
288340
}
289341

@@ -295,25 +347,6 @@ func reqToken() func(ctx *context.APIContext) {
295347
return
296348
}
297349

298-
if true == ctx.Data["IsApiToken"] {
299-
publicRepo, pubRepoExists := ctx.Data["ApiTokenScopePublicRepoOnly"]
300-
publicOrg, pubOrgExists := ctx.Data["ApiTokenScopePublicOrgOnly"]
301-
302-
if pubRepoExists && publicRepo.(bool) &&
303-
ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
304-
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
305-
return
306-
}
307-
308-
if pubOrgExists && publicOrg.(bool) &&
309-
ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
310-
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
311-
return
312-
}
313-
314-
return
315-
}
316-
317350
if ctx.IsSigned {
318351
return
319352
}
@@ -879,11 +912,11 @@ func Routes() *web.Router {
879912
m.Group("/user/{username}", func() {
880913
m.Get("", activitypub.Person)
881914
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
882-
}, context.UserAssignmentAPI())
915+
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
883916
m.Group("/user-id/{user-id}", func() {
884917
m.Get("", activitypub.Person)
885918
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
886-
}, context.UserIDAssignmentAPI())
919+
}, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
887920
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
888921
}
889922

@@ -939,7 +972,7 @@ func Routes() *web.Router {
939972
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
940973

941974
m.Get("/activities/feeds", user.ListUserActivityFeeds)
942-
}, context.UserAssignmentAPI(), individualPermsChecker)
975+
}, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
943976
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
944977

945978
// Users (requires user scope)
@@ -957,7 +990,7 @@ func Routes() *web.Router {
957990
m.Get("/starred", user.GetStarredRepos)
958991

959992
m.Get("/subscriptions", user.GetWatchedRepos)
960-
}, context.UserAssignmentAPI())
993+
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
961994
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
962995

963996
// Users (requires user scope)
@@ -1044,7 +1077,7 @@ func Routes() *web.Router {
10441077
m.Get("", user.IsStarring)
10451078
m.Put("", user.Star)
10461079
m.Delete("", user.Unstar)
1047-
}, repoAssignment())
1080+
}, repoAssignment(), checkTokenPublicOnly())
10481081
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
10491082
m.Get("/times", repo.ListMyTrackedTimes)
10501083
m.Get("/stopwatches", repo.GetStopwatches)
@@ -1069,18 +1102,20 @@ func Routes() *web.Router {
10691102
m.Get("", user.CheckUserBlock)
10701103
m.Put("", user.BlockUser)
10711104
m.Delete("", user.UnblockUser)
1072-
}, context.UserAssignmentAPI())
1105+
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
10731106
})
10741107
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
10751108

10761109
// Repositories (requires repo scope, org scope)
10771110
m.Post("/org/{org}/repos",
1111+
// FIXME: we need org in context
10781112
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
10791113
reqToken(),
10801114
bind(api.CreateRepoOption{}),
10811115
repo.CreateOrgRepoDeprecated)
10821116

10831117
// requires repo scope
1118+
// FIXME: Don't expose repository id outside of the system
10841119
m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID)
10851120

10861121
// Repos (requires repo scope)
@@ -1334,7 +1369,7 @@ func Routes() *web.Router {
13341369
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
13351370
m.Delete("", repo.DeleteAvatar)
13361371
}, reqAdmin(), reqToken())
1337-
}, repoAssignment())
1372+
}, repoAssignment(), checkTokenPublicOnly())
13381373
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
13391374

13401375
// Notifications (requires notifications scope)
@@ -1343,7 +1378,7 @@ func Routes() *web.Router {
13431378
m.Combo("/notifications", reqToken()).
13441379
Get(notify.ListRepoNotifications).
13451380
Put(notify.ReadRepoNotifications)
1346-
}, repoAssignment())
1381+
}, repoAssignment(), checkTokenPublicOnly())
13471382
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
13481383

13491384
// Issue (requires issue scope)
@@ -1457,7 +1492,7 @@ func Routes() *web.Router {
14571492
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
14581493
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
14591494
})
1460-
}, repoAssignment())
1495+
}, repoAssignment(), checkTokenPublicOnly())
14611496
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
14621497

14631498
// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
@@ -1468,14 +1503,14 @@ func Routes() *web.Router {
14681503
m.Get("/files", reqToken(), packages.ListPackageFiles)
14691504
})
14701505
m.Get("/", reqToken(), packages.ListPackages)
1471-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
1506+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
14721507

14731508
// Organizations
14741509
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
14751510
m.Group("/users/{username}/orgs", func() {
14761511
m.Get("", reqToken(), org.ListUserOrgs)
14771512
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
1478-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI())
1513+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI(), checkTokenPublicOnly())
14791514
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
14801515
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
14811516
m.Group("/orgs/{org}", func() {
@@ -1533,7 +1568,7 @@ func Routes() *web.Router {
15331568
m.Delete("", org.UnblockUser)
15341569
})
15351570
}, reqToken(), reqOrgOwnership())
1536-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
1571+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
15371572
m.Group("/teams/{teamid}", func() {
15381573
m.Combo("").Get(reqToken(), org.GetTeam).
15391574
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
@@ -1553,7 +1588,7 @@ func Routes() *web.Router {
15531588
Get(reqToken(), org.GetTeamRepo)
15541589
})
15551590
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
1556-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership())
1591+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly())
15571592

15581593
m.Group("/admin", func() {
15591594
m.Group("/cron", func() {

routers/api/v1/org/org.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ func GetAll(ctx *context.APIContext) {
191191
// "$ref": "#/responses/OrganizationList"
192192

193193
vMode := []api.VisibleType{api.VisibleTypePublic}
194-
if ctx.IsSigned {
194+
if ctx.IsSigned && !ctx.PublicOnly {
195195
vMode = append(vMode, api.VisibleTypeLimited)
196196
if ctx.Doer.IsAdmin {
197197
vMode = append(vMode, api.VisibleTypePrivate)

routers/api/v1/repo/issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func SearchIssues(ctx *context.APIContext) {
149149
Actor: ctx.Doer,
150150
}
151151
if ctx.IsSigned {
152-
opts.Private = true
152+
opts.Private = !ctx.PublicOnly
153153
opts.AllLimited = true
154154
}
155155
if ctx.FormString("owner") != "" {

routers/api/v1/repo/repo.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ func Search(ctx *context.APIContext) {
129129
// "422":
130130
// "$ref": "#/responses/validationError"
131131

132+
private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
133+
if ctx.PublicOnly {
134+
private = false
135+
}
136+
132137
opts := &repo_model.SearchRepoOptions{
133138
ListOptions: utils.GetListOptions(ctx),
134139
Actor: ctx.Doer,
@@ -138,7 +143,7 @@ func Search(ctx *context.APIContext) {
138143
TeamID: ctx.FormInt64("team_id"),
139144
TopicOnly: ctx.FormBool("topic"),
140145
Collaborate: optional.None[bool](),
141-
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
146+
Private: private,
142147
Template: optional.None[bool](),
143148
StarredByID: ctx.FormInt64("starredBy"),
144149
IncludeDescription: ctx.FormBool("includeDesc"),

routers/api/v1/user/user.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
activities_model "code.gitea.io/gitea/models/activities"
1111
user_model "code.gitea.io/gitea/models/user"
12+
"code.gitea.io/gitea/modules/structs"
1213
"code.gitea.io/gitea/routers/api/v1/utils"
1314
"code.gitea.io/gitea/services/context"
1415
"code.gitea.io/gitea/services/convert"
@@ -67,12 +68,17 @@ func Search(ctx *context.APIContext) {
6768
maxResults = 1
6869
users = []*user_model.User{user_model.NewActionsUser()}
6970
default:
71+
var visible []structs.VisibleType
72+
if ctx.PublicOnly {
73+
visible = []structs.VisibleType{structs.VisibleTypePublic}
74+
}
7075
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
7176
Actor: ctx.Doer,
7277
Keyword: ctx.FormTrim("q"),
7378
UID: uid,
7479
Type: user_model.UserTypeIndividual,
7580
SearchByEmail: true,
81+
Visible: visible,
7682
ListOptions: listOptions,
7783
})
7884
if err != nil {

services/context/api.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ type APIContext struct {
3535

3636
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
3737

38-
Repo *Repository
39-
Org *APIOrganization
40-
Package *Package
38+
Repo *Repository
39+
Org *APIOrganization
40+
Package *Package
41+
PublicOnly bool // Whether the request is for a public endpoint
4142
}
4243

4344
func init() {

0 commit comments

Comments
 (0)