Skip to content

Commit ea5a8c7

Browse files
lunnyearl-warren
authored andcommitted
Fix bug when a token is given public only
Port of go-gitea/gitea#32204 (cherry picked from commit d6d3c96e6555fc91b3e2ef21f4d8d7475564bb3e) Conflicts: routers/api/v1/api.go services/context/api.go trivial context conflicts (cherry picked from commit a052d2b) Conflicts: routers/api/v1/user/user.go trivial context conflict (search by email is not in v9.0)
1 parent 0496e72 commit ea5a8c7

File tree

11 files changed

+172
-51
lines changed

11 files changed

+172
-51
lines changed

models/user/user.go

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

424+
func (u *User) IsUser() bool {
425+
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
426+
}
427+
424428
// IsBot returns whether or not the user is of type bot
425429
func (u *User) IsBot() bool {
426430
return u.Type == UserTypeBot

routers/api/packages/api.go

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

routers/api/v1/api.go

Lines changed: 82 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,62 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext)
274274
}
275275
}
276276

277+
func checkTokenPublicOnly() func(ctx *context.APIContext) {
278+
return func(ctx *context.APIContext) {
279+
if !ctx.PublicOnly {
280+
return
281+
}
282+
283+
requiredScopeCategories, ok := ctx.Data["requiredScopeCategories"].([]auth_model.AccessTokenScopeCategory)
284+
if !ok || len(requiredScopeCategories) == 0 {
285+
return
286+
}
287+
288+
// public Only permission check
289+
switch {
290+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
291+
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
292+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
293+
return
294+
}
295+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
296+
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
297+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public issues")
298+
return
299+
}
300+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
301+
if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
302+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
303+
return
304+
}
305+
if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
306+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
307+
return
308+
}
309+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
310+
if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
311+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users")
312+
return
313+
}
314+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
315+
if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
316+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub")
317+
return
318+
}
319+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
320+
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
321+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public notifications")
322+
return
323+
}
324+
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
325+
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
326+
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
327+
return
328+
}
329+
}
330+
}
331+
}
332+
277333
// if a token is being used for auth, we check that it contains the required scope
278334
// if a token is not being used, reqToken will enforce other sign in methods
279335
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
@@ -289,9 +345,6 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
289345
return
290346
}
291347

292-
ctx.Data["ApiTokenScopePublicRepoOnly"] = false
293-
ctx.Data["ApiTokenScopePublicOrgOnly"] = false
294-
295348
// use the http method to determine the access level
296349
requiredScopeLevel := auth_model.Read
297350
if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
@@ -300,29 +353,28 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
300353

301354
// get the required scope for the given access level and category
302355
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
303-
304-
// check if scope only applies to public resources
305-
publicOnly, err := scope.PublicOnly()
356+
allow, err := scope.HasScope(requiredScopes...)
306357
if err != nil {
307-
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
358+
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
308359
return
309360
}
310361

311-
// this context is used by the middleware in the specific route
312-
ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository)
313-
ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization)
314-
315-
allow, err := scope.HasScope(requiredScopes...)
316-
if err != nil {
317-
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
362+
if !allow {
363+
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
318364
return
319365
}
320366

321-
if allow {
367+
ctx.Data["requiredScopeCategories"] = requiredScopeCategories
368+
369+
// check if scope only applies to public resources
370+
publicOnly, err := scope.PublicOnly()
371+
if err != nil {
372+
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
322373
return
323374
}
324375

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

@@ -334,25 +386,6 @@ func reqToken() func(ctx *context.APIContext) {
334386
return
335387
}
336388

337-
if true == ctx.Data["IsApiToken"] {
338-
publicRepo, pubRepoExists := ctx.Data["ApiTokenScopePublicRepoOnly"]
339-
publicOrg, pubOrgExists := ctx.Data["ApiTokenScopePublicOrgOnly"]
340-
341-
if pubRepoExists && publicRepo.(bool) &&
342-
ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
343-
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
344-
return
345-
}
346-
347-
if pubOrgExists && publicOrg.(bool) &&
348-
ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
349-
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
350-
return
351-
}
352-
353-
return
354-
}
355-
356389
if ctx.IsSigned {
357390
return
358391
}
@@ -800,11 +833,11 @@ func Routes() *web.Route {
800833
m.Group("/user/{username}", func() {
801834
m.Get("", activitypub.Person)
802835
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
803-
}, context.UserAssignmentAPI())
836+
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
804837
m.Group("/user-id/{user-id}", func() {
805838
m.Get("", activitypub.Person)
806839
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
807-
}, context.UserIDAssignmentAPI())
840+
}, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
808841
m.Group("/actor", func() {
809842
m.Get("", activitypub.Actor)
810843
m.Post("/inbox", activitypub.ActorInbox)
@@ -871,7 +904,7 @@ func Routes() *web.Route {
871904
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
872905

873906
m.Get("/activities/feeds", user.ListUserActivityFeeds)
874-
}, context.UserAssignmentAPI(), individualPermsChecker)
907+
}, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
875908
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
876909

877910
// Users (requires user scope)
@@ -891,7 +924,7 @@ func Routes() *web.Route {
891924
}
892925

893926
m.Get("/subscriptions", user.GetWatchedRepos)
894-
}, context.UserAssignmentAPI())
927+
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
895928
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
896929

897930
// Users (requires user scope)
@@ -988,7 +1021,7 @@ func Routes() *web.Route {
9881021
m.Get("", user.IsStarring)
9891022
m.Put("", user.Star)
9901023
m.Delete("", user.Unstar)
991-
}, repoAssignment())
1024+
}, repoAssignment(), checkTokenPublicOnly())
9921025
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
9931026
}
9941027
m.Get("/times", repo.ListMyTrackedTimes)
@@ -1019,12 +1052,14 @@ func Routes() *web.Route {
10191052

10201053
// Repositories (requires repo scope, org scope)
10211054
m.Post("/org/{org}/repos",
1055+
// FIXME: we need org in context
10221056
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
10231057
reqToken(),
10241058
bind(api.CreateRepoOption{}),
10251059
repo.CreateOrgRepoDeprecated)
10261060

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

10301065
// Repos (requires repo scope)
@@ -1305,7 +1340,7 @@ func Routes() *web.Route {
13051340
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
13061341
m.Delete("", repo.DeleteAvatar)
13071342
}, reqAdmin(), reqToken())
1308-
}, repoAssignment())
1343+
}, repoAssignment(), checkTokenPublicOnly())
13091344
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
13101345

13111346
// Notifications (requires notifications scope)
@@ -1314,7 +1349,7 @@ func Routes() *web.Route {
13141349
m.Combo("/notifications", reqToken()).
13151350
Get(notify.ListRepoNotifications).
13161351
Put(notify.ReadRepoNotifications)
1317-
}, repoAssignment())
1352+
}, repoAssignment(), checkTokenPublicOnly())
13181353
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
13191354

13201355
// Issue (requires issue scope)
@@ -1428,7 +1463,7 @@ func Routes() *web.Route {
14281463
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
14291464
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
14301465
})
1431-
}, repoAssignment())
1466+
}, repoAssignment(), checkTokenPublicOnly())
14321467
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
14331468

14341469
// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
@@ -1439,14 +1474,14 @@ func Routes() *web.Route {
14391474
m.Get("/files", reqToken(), packages.ListPackageFiles)
14401475
})
14411476
m.Get("/", reqToken(), packages.ListPackages)
1442-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
1477+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
14431478

14441479
// Organizations
14451480
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
14461481
m.Group("/users/{username}/orgs", func() {
14471482
m.Get("", reqToken(), org.ListUserOrgs)
14481483
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
1449-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI())
1484+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI(), checkTokenPublicOnly())
14501485
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
14511486
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
14521487
m.Group("/orgs/{org}", func() {
@@ -1513,7 +1548,7 @@ func Routes() *web.Route {
15131548
m.Put("/unblock/{username}", org.UnblockUser)
15141549
}, context.UserAssignmentAPI())
15151550
}, reqToken(), reqOrgOwnership())
1516-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
1551+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
15171552
m.Group("/teams/{teamid}", func() {
15181553
m.Combo("").Get(reqToken(), org.GetTeam).
15191554
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
@@ -1533,7 +1568,7 @@ func Routes() *web.Route {
15331568
Get(reqToken(), org.GetTeamRepo)
15341569
})
15351570
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
1536-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership())
1571+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly())
15371572

15381573
m.Group("/admin", func() {
15391574
m.Group("/cron", func() {

routers/api/v1/org/org.go

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

194194
vMode := []api.VisibleType{api.VisibleTypePublic}
195-
if ctx.IsSigned {
195+
if ctx.IsSigned && !ctx.PublicOnly {
196196
vMode = append(vMode, api.VisibleTypeLimited)
197197
if ctx.Doer.IsAdmin {
198198
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
@@ -130,6 +130,11 @@ func Search(ctx *context.APIContext) {
130130
// "422":
131131
// "$ref": "#/responses/validationError"
132132

133+
private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
134+
if ctx.PublicOnly {
135+
private = false
136+
}
137+
133138
opts := &repo_model.SearchRepoOptions{
134139
ListOptions: utils.GetListOptions(ctx),
135140
Actor: ctx.Doer,
@@ -139,7 +144,7 @@ func Search(ctx *context.APIContext) {
139144
TeamID: ctx.FormInt64("team_id"),
140145
TopicOnly: ctx.FormBool("topic"),
141146
Collaborate: optional.None[bool](),
142-
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
147+
Private: private,
143148
Template: optional.None[bool](),
144149
StarredByID: ctx.FormInt64("starredBy"),
145150
IncludeDescription: ctx.FormBool("includeDesc"),

routers/api/v1/user/user.go

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

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

services/context/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type APIContext struct {
4545
Package *Package
4646
QuotaGroup *quota_model.Group
4747
QuotaRule *quota_model.Rule
48+
PublicOnly bool // Whether the request is for a public endpoint
4849
}
4950

5051
func init() {

0 commit comments

Comments
 (0)