aboutsummaryrefslogtreecommitdiffstats
path: root/routers/api/v1
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2024-10-08 17:51:09 +0800
committerGitHub <noreply@github.com>2024-10-08 12:51:09 +0300
commitd6d3c96e6555fc91b3e2ef21f4d8d7475564bb3e (patch)
tree710dd914452b0c11b39aa6cbc9f431302af0b1f9 /routers/api/v1
parentd3ada91ea41b2f2eb58d637ab4c0a2dde07f20ce (diff)
downloadgitea-d6d3c96e6555fc91b3e2ef21f4d8d7475564bb3e.tar.gz
gitea-d6d3c96e6555fc91b3e2ef21f4d8d7475564bb3e.zip
Fix bug when a token is given public only (#32204)
Diffstat (limited to 'routers/api/v1')
-rw-r--r--routers/api/v1/api.go131
-rw-r--r--routers/api/v1/org/org.go2
-rw-r--r--routers/api/v1/repo/issue.go2
-rw-r--r--routers/api/v1/repo/repo.go7
-rw-r--r--routers/api/v1/user/user.go6
5 files changed, 97 insertions, 51 deletions
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 5aa8ad44e5..883e694e44 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -235,6 +235,62 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext)
}
}
+func checkTokenPublicOnly() func(ctx *context.APIContext) {
+ return func(ctx *context.APIContext) {
+ if !ctx.PublicOnly {
+ return
+ }
+
+ requiredScopeCategories, ok := ctx.Data["requiredScopeCategories"].([]auth_model.AccessTokenScopeCategory)
+ if !ok || len(requiredScopeCategories) == 0 {
+ return
+ }
+
+ // public Only permission check
+ switch {
+ case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
+ if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
+ ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
+ return
+ }
+ case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
+ if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
+ ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public issues")
+ return
+ }
+ case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
+ if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
+ ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
+ return
+ }
+ if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
+ ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
+ return
+ }
+ case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
+ if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
+ ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users")
+ return
+ }
+ case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
+ if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
+ ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub")
+ return
+ }
+ case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
+ if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
+ ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public notifications")
+ return
+ }
+ case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
+ if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
+ ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
+ return
+ }
+ }
+ }
+}
+
// if a token is being used for auth, we check that it contains the required scope
// if a token is not being used, reqToken will enforce other sign in methods
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
@@ -250,9 +306,6 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
return
}
- ctx.Data["ApiTokenScopePublicRepoOnly"] = false
- ctx.Data["ApiTokenScopePublicOrgOnly"] = false
-
// use the http method to determine the access level
requiredScopeLevel := auth_model.Read
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
// get the required scope for the given access level and category
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
-
- // check if scope only applies to public resources
- publicOnly, err := scope.PublicOnly()
+ allow, err := scope.HasScope(requiredScopes...)
if err != nil {
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
+ ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
return
}
- // this context is used by the middleware in the specific route
- ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository)
- ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization)
-
- allow, err := scope.HasScope(requiredScopes...)
- if err != nil {
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
+ if !allow {
+ ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
return
}
- if allow {
+ ctx.Data["requiredScopeCategories"] = requiredScopeCategories
+
+ // check if scope only applies to public resources
+ publicOnly, err := scope.PublicOnly()
+ if err != nil {
+ ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
return
}
- ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
+ // assign to true so that those searching should only filter public repositories/users/organizations
+ ctx.PublicOnly = publicOnly
}
}
@@ -295,25 +347,6 @@ func reqToken() func(ctx *context.APIContext) {
return
}
- if true == ctx.Data["IsApiToken"] {
- publicRepo, pubRepoExists := ctx.Data["ApiTokenScopePublicRepoOnly"]
- publicOrg, pubOrgExists := ctx.Data["ApiTokenScopePublicOrgOnly"]
-
- if pubRepoExists && publicRepo.(bool) &&
- ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
- return
- }
-
- if pubOrgExists && publicOrg.(bool) &&
- ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
- ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs")
- return
- }
-
- return
- }
-
if ctx.IsSigned {
return
}
@@ -879,11 +912,11 @@ func Routes() *web.Router {
m.Group("/user/{username}", func() {
m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
- }, context.UserAssignmentAPI())
+ }, context.UserAssignmentAPI(), checkTokenPublicOnly())
m.Group("/user-id/{user-id}", func() {
m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
- }, context.UserIDAssignmentAPI())
+ }, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
}
@@ -939,7 +972,7 @@ func Routes() *web.Router {
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
m.Get("/activities/feeds", user.ListUserActivityFeeds)
- }, context.UserAssignmentAPI(), individualPermsChecker)
+ }, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
// Users (requires user scope)
@@ -957,7 +990,7 @@ func Routes() *web.Router {
m.Get("/starred", user.GetStarredRepos)
m.Get("/subscriptions", user.GetWatchedRepos)
- }, context.UserAssignmentAPI())
+ }, context.UserAssignmentAPI(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Users (requires user scope)
@@ -1044,7 +1077,7 @@ func Routes() *web.Router {
m.Get("", user.IsStarring)
m.Put("", user.Star)
m.Delete("", user.Unstar)
- }, repoAssignment())
+ }, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
m.Get("/times", repo.ListMyTrackedTimes)
m.Get("/stopwatches", repo.GetStopwatches)
@@ -1069,18 +1102,20 @@ func Routes() *web.Router {
m.Get("", user.CheckUserBlock)
m.Put("", user.BlockUser)
m.Delete("", user.UnblockUser)
- }, context.UserAssignmentAPI())
+ }, context.UserAssignmentAPI(), checkTokenPublicOnly())
})
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Repositories (requires repo scope, org scope)
m.Post("/org/{org}/repos",
+ // FIXME: we need org in context
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
reqToken(),
bind(api.CreateRepoOption{}),
repo.CreateOrgRepoDeprecated)
// requires repo scope
+ // FIXME: Don't expose repository id outside of the system
m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID)
// Repos (requires repo scope)
@@ -1334,7 +1369,7 @@ func Routes() *web.Router {
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
m.Delete("", repo.DeleteAvatar)
}, reqAdmin(), reqToken())
- }, repoAssignment())
+ }, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
// Notifications (requires notifications scope)
@@ -1343,7 +1378,7 @@ func Routes() *web.Router {
m.Combo("/notifications", reqToken()).
Get(notify.ListRepoNotifications).
Put(notify.ReadRepoNotifications)
- }, repoAssignment())
+ }, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
// Issue (requires issue scope)
@@ -1457,7 +1492,7 @@ func Routes() *web.Router {
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
})
- }, repoAssignment())
+ }, repoAssignment(), checkTokenPublicOnly())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
// 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 {
m.Get("/files", reqToken(), packages.ListPackageFiles)
})
m.Get("/", reqToken(), packages.ListPackages)
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
+ }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
// Organizations
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
m.Group("/users/{username}/orgs", func() {
m.Get("", reqToken(), org.ListUserOrgs)
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI())
+ }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI(), checkTokenPublicOnly())
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
m.Group("/orgs/{org}", func() {
@@ -1533,7 +1568,7 @@ func Routes() *web.Router {
m.Delete("", org.UnblockUser)
})
}, reqToken(), reqOrgOwnership())
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
+ }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
m.Group("/teams/{teamid}", func() {
m.Combo("").Get(reqToken(), org.GetTeam).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
@@ -1553,7 +1588,7 @@ func Routes() *web.Router {
Get(reqToken(), org.GetTeamRepo)
})
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
- }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership())
+ }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly())
m.Group("/admin", func() {
m.Group("/cron", func() {
diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go
index e848d95181..9e58746272 100644
--- a/routers/api/v1/org/org.go
+++ b/routers/api/v1/org/org.go
@@ -191,7 +191,7 @@ func GetAll(ctx *context.APIContext) {
// "$ref": "#/responses/OrganizationList"
vMode := []api.VisibleType{api.VisibleTypePublic}
- if ctx.IsSigned {
+ if ctx.IsSigned && !ctx.PublicOnly {
vMode = append(vMode, api.VisibleTypeLimited)
if ctx.Doer.IsAdmin {
vMode = append(vMode, api.VisibleTypePrivate)
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index c1218440e5..d8c39b0f69 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -149,7 +149,7 @@ func SearchIssues(ctx *context.APIContext) {
Actor: ctx.Doer,
}
if ctx.IsSigned {
- opts.Private = true
+ opts.Private = !ctx.PublicOnly
opts.AllLimited = true
}
if ctx.FormString("owner") != "" {
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 6c1a94ee16..4638e2ba5c 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -129,6 +129,11 @@ func Search(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
+ private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
+ if ctx.PublicOnly {
+ private = false
+ }
+
opts := &repo_model.SearchRepoOptions{
ListOptions: utils.GetListOptions(ctx),
Actor: ctx.Doer,
@@ -138,7 +143,7 @@ func Search(ctx *context.APIContext) {
TeamID: ctx.FormInt64("team_id"),
TopicOnly: ctx.FormBool("topic"),
Collaborate: optional.None[bool](),
- Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
+ Private: private,
Template: optional.None[bool](),
StarredByID: ctx.FormInt64("starredBy"),
IncludeDescription: ctx.FormBool("includeDesc"),
diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go
index 2c277a18c7..a9011427fb 100644
--- a/routers/api/v1/user/user.go
+++ b/routers/api/v1/user/user.go
@@ -9,6 +9,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
@@ -67,12 +68,17 @@ func Search(ctx *context.APIContext) {
maxResults = 1
users = []*user_model.User{user_model.NewActionsUser()}
default:
+ var visible []structs.VisibleType
+ if ctx.PublicOnly {
+ visible = []structs.VisibleType{structs.VisibleTypePublic}
+ }
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
UID: uid,
Type: user_model.UserTypeIndividual,
SearchByEmail: true,
+ Visible: visible,
ListOptions: listOptions,
})
if err != nil {