Skip to content

Commit bcf6d06

Browse files
committed
enforce public-only scope for access token
1 parent a989404 commit bcf6d06

File tree

7 files changed

+259
-20
lines changed

7 files changed

+259
-20
lines changed

options/locale/locale_en-US.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -893,8 +893,8 @@ access_token_deletion_confirm_action = Delete
893893
access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. This cannot be undone. Continue?
894894
delete_token_success = The token has been deleted. Applications using it no longer have access to your account.
895895
repo_and_org_access = Repository and Organization Access
896-
permissions_public_only = Public only
897-
permissions_access_all = All (public, private, and limited)
896+
permissions_public_only = Public only (Grant access only to public and exclude private and limited organizations and repositories)
897+
permissions_access_all = All (Grant access to public, private, and limited organizations and repositories)
898898
select_permissions = Select permissions
899899
permission_not_set = Not set
900900
permission_no_access = No Access

routers/api/v1/api.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ import (
9292
"code.gitea.io/gitea/routers/api/v1/repo"
9393
"code.gitea.io/gitea/routers/api/v1/settings"
9494
"code.gitea.io/gitea/routers/api/v1/user"
95+
"code.gitea.io/gitea/routers/api/v1/utils"
9596
"code.gitea.io/gitea/routers/common"
9697
"code.gitea.io/gitea/services/actions"
9798
"code.gitea.io/gitea/services/auth"
@@ -184,6 +185,10 @@ func repoAssignment() func(ctx *context.APIContext) {
184185
}
185186
return
186187
}
188+
if repo.IsPrivate && utils.PublicOnlyToken(ctx, "ApiTokenScopePublicRepoOnly") {
189+
ctx.NotFound()
190+
return
191+
}
187192

188193
repo.Owner = owner
189194
ctx.Repo.Repository = repo
@@ -954,9 +959,9 @@ func Routes() *web.Router {
954959
m.Get("/{target}", user.CheckFollowing)
955960
})
956961

957-
m.Get("/starred", user.GetStarredRepos)
962+
m.Get("/starred", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), user.GetStarredRepos)
958963

959-
m.Get("/subscriptions", user.GetWatchedRepos)
964+
m.Get("/subscriptions", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), user.GetWatchedRepos)
960965
}, context.UserAssignmentAPI())
961966
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
962967

@@ -1477,13 +1482,13 @@ func Routes() *web.Router {
14771482
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
14781483
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI())
14791484
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
1480-
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
1485+
m.Get("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), org.GetAll)
14811486
m.Group("/orgs/{org}", func() {
14821487
m.Combo("").Get(org.Get).
14831488
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
14841489
Delete(reqToken(), reqOrgOwnership(), org.Delete)
1485-
m.Combo("/repos").Get(user.ListOrgRepos).
1486-
Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
1490+
m.Combo("/repos").Get(tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), user.ListOrgRepos).
1491+
Post(reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
14871492
m.Group("/members", func() {
14881493
m.Get("", reqToken(), org.ListMembers)
14891494
m.Combo("/{username}").Get(reqToken(), org.IsMember).
@@ -1551,7 +1556,7 @@ func Routes() *web.Router {
15511556
Put(reqToken(), org.AddTeamRepository).
15521557
Delete(reqToken(), org.RemoveTeamRepository).
15531558
Get(reqToken(), org.GetTeamRepo)
1554-
})
1559+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository, auth_model.AccessTokenScopeCategoryRepository))
15551560
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
15561561
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership())
15571562

@@ -1571,23 +1576,25 @@ func Routes() *web.Router {
15711576
m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
15721577
m.Delete("/{id}", admin.DeleteUserPublicKey)
15731578
})
1574-
m.Get("/orgs", org.ListUserOrgs)
1575-
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
1576-
m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
1579+
m.Get("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), org.ListUserOrgs)
1580+
m.Get("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), org.ListUserOrgs)
1581+
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), bind(api.CreateOrgOption{}), admin.CreateOrg)
1582+
m.Post("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), bind(api.CreateRepoOption{}), admin.CreateRepo)
15771583
m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser)
15781584
m.Get("/badges", admin.ListUserBadges)
15791585
m.Post("/badges", bind(api.UserBadgeOption{}), admin.AddUserBadges)
15801586
m.Delete("/badges", bind(api.UserBadgeOption{}), admin.DeleteUserBadges)
15811587
}, context.UserAssignmentAPI())
1582-
})
1588+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
1589+
15831590
m.Group("/emails", func() {
15841591
m.Get("", admin.GetAllEmails)
15851592
m.Get("/search", admin.SearchEmail)
15861593
})
15871594
m.Group("/unadopted", func() {
15881595
m.Get("", admin.ListUnadoptedRepositories)
1589-
m.Post("/{username}/{reponame}", admin.AdoptRepository)
1590-
m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository)
1596+
m.Post("/{username}/{reponame}", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), admin.AdoptRepository)
1597+
m.Delete("/{username}/{reponame}", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), admin.DeleteUnadoptedRepository)
15911598
})
15921599
m.Group("/hooks", func() {
15931600
m.Combo("").Get(admin.ListHooks).

routers/api/v1/org/org.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import (
2626
func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
2727
listOptions := utils.GetListOptions(ctx)
2828
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == u.ID)
29+
if utils.PublicOnlyToken(ctx, "ApiTokenScopePublicOrgOnly") {
30+
showPrivate = false
31+
}
2932

3033
opts := organization.FindOrgOptions{
3134
ListOptions: listOptions,
@@ -191,10 +194,12 @@ func GetAll(ctx *context.APIContext) {
191194
// "$ref": "#/responses/OrganizationList"
192195

193196
vMode := []api.VisibleType{api.VisibleTypePublic}
194-
if ctx.IsSigned {
195-
vMode = append(vMode, api.VisibleTypeLimited)
196-
if ctx.Doer.IsAdmin {
197-
vMode = append(vMode, api.VisibleTypePrivate)
197+
if !utils.PublicOnlyToken(ctx, "ApiTokenScopePublicOrgOnly") {
198+
if ctx.IsSigned {
199+
vMode = append(vMode, api.VisibleTypeLimited)
200+
if ctx.Doer.IsAdmin {
201+
vMode = append(vMode, api.VisibleTypePrivate)
202+
}
198203
}
199204
}
200205

@@ -304,6 +309,11 @@ func Get(ctx *context.APIContext) {
304309
return
305310
}
306311

312+
if !ctx.Org.Organization.Visibility.IsPublic() && utils.PublicOnlyToken(ctx, "ApiTokenScopePublicOrgOnly") {
313+
ctx.NotFound()
314+
return
315+
}
316+
307317
org := convert.ToOrganization(ctx, ctx.Org.Organization)
308318

309319
// Don't show Mail, when User is not logged in

routers/api/v1/repo/repo.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ func Search(ctx *context.APIContext) {
197197
}
198198
}
199199

200+
if utils.PublicOnlyToken(ctx, "ApiTokenScopePublicRepoOnly") {
201+
opts.Private = false
202+
opts.IsPrivate = optional.Some(false)
203+
}
204+
200205
var err error
201206
repos, count, err := repo_model.SearchRepository(ctx, opts)
202207
if err != nil {
@@ -589,6 +594,12 @@ func GetByID(ctx *context.APIContext) {
589594
ctx.NotFound()
590595
return
591596
}
597+
598+
if repo.IsPrivate && utils.PublicOnlyToken(ctx, "ApiTokenScopePublicRepoOnly") {
599+
ctx.NotFound()
600+
return
601+
}
602+
592603
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
593604
}
594605

routers/api/v1/user/repo.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ func ListUserRepos(ctx *context.APIContext) {
8080
// "$ref": "#/responses/notFound"
8181

8282
private := ctx.IsSigned
83+
if utils.PublicOnlyToken(ctx, "ApiTokenScopePublicRepoOnly") {
84+
private = false
85+
}
8386
listUserRepos(ctx, ctx.ContextUser, private)
8487
}
8588

@@ -111,6 +114,10 @@ func ListMyRepos(ctx *context.APIContext) {
111114
IncludeDescription: true,
112115
}
113116

117+
if utils.PublicOnlyToken(ctx, "ApiTokenScopePublicRepoOnly") {
118+
opts.Private = false
119+
}
120+
114121
var err error
115122
repos, count, err := repo_model.SearchRepository(ctx, opts)
116123
if err != nil {
@@ -162,6 +169,9 @@ func ListOrgRepos(ctx *context.APIContext) {
162169
// "$ref": "#/responses/RepositoryList"
163170
// "404":
164171
// "$ref": "#/responses/notFound"
165-
166-
listUserRepos(ctx, ctx.Org.Organization.AsUser(), ctx.IsSigned)
172+
private := ctx.IsSigned
173+
if utils.PublicOnlyToken(ctx, "ApiTokenScopePublicRepoOnly") {
174+
private = false
175+
}
176+
listUserRepos(ctx, ctx.Org.Organization.AsUser(), private)
167177
}

routers/api/v1/utils/token.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package utils
5+
6+
import "code.gitea.io/gitea/services/context"
7+
8+
// check if api token contains `public-only` scope
9+
func PublicOnlyToken(ctx *context.APIContext, scopeKey string) bool {
10+
publicScope, _ := ctx.Data[scopeKey].(bool)
11+
return publicScope
12+
}

0 commit comments

Comments
 (0)