mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-27 22:23:50 +03:00
Fix bug when a token is given public only
Port of https://github.com/go-gitea/gitea/pull/32204 (cherry picked from commit d6d3c96e6555fc91b3e2ef21f4d8d7475564bb3e) Conflicts: routers/api/v1/api.go services/context/api.go trivial context conflicts
This commit is contained in:
parent
67549e2d94
commit
a052d2b602
11 changed files with 174 additions and 53 deletions
|
@ -421,6 +421,10 @@ func (u *User) IsIndividual() bool {
|
||||||
return u.Type == UserTypeIndividual
|
return u.Type == UserTypeIndividual
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) IsUser() bool {
|
||||||
|
return u.Type == UserTypeIndividual || u.Type == UserTypeBot
|
||||||
|
}
|
||||||
|
|
||||||
// IsBot returns whether or not the user is of type bot
|
// IsBot returns whether or not the user is of type bot
|
||||||
func (u *User) IsBot() bool {
|
func (u *User) IsBot() bool {
|
||||||
return u.Type == UserTypeBot
|
return u.Type == UserTypeBot
|
||||||
|
|
|
@ -65,6 +65,20 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
|
||||||
ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
|
ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if publicOnly {
|
||||||
|
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
|
||||||
|
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -274,6 +274,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 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
|
// if a token is not being used, reqToken will enforce other sign in methods
|
||||||
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
|
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
|
||||||
|
@ -289,9 +345,6 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["ApiTokenScopePublicRepoOnly"] = false
|
|
||||||
ctx.Data["ApiTokenScopePublicOrgOnly"] = false
|
|
||||||
|
|
||||||
// use the http method to determine the access level
|
// use the http method to determine the access level
|
||||||
requiredScopeLevel := auth_model.Read
|
requiredScopeLevel := auth_model.Read
|
||||||
if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
|
if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
|
||||||
|
@ -300,6 +353,18 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||||
|
|
||||||
// get the required scope for the given access level and category
|
// get the required scope for the given access level and category
|
||||||
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
|
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
|
||||||
|
allow, err := scope.HasScope(requiredScopes...)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allow {
|
||||||
|
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["requiredScopeCategories"] = requiredScopeCategories
|
||||||
|
|
||||||
// check if scope only applies to public resources
|
// check if scope only applies to public resources
|
||||||
publicOnly, err := scope.PublicOnly()
|
publicOnly, err := scope.PublicOnly()
|
||||||
|
@ -308,21 +373,8 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// this context is used by the middleware in the specific route
|
// assign to true so that those searching should only filter public repositories/users/organizations
|
||||||
ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository)
|
ctx.PublicOnly = publicOnly
|
||||||
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())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if allow {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,25 +386,6 @@ func reqToken() func(ctx *context.APIContext) {
|
||||||
return
|
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 {
|
if ctx.IsSigned {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -800,11 +833,11 @@ func Routes() *web.Route {
|
||||||
m.Group("/user/{username}", func() {
|
m.Group("/user/{username}", func() {
|
||||||
m.Get("", activitypub.Person)
|
m.Get("", activitypub.Person)
|
||||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||||
}, context.UserAssignmentAPI())
|
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||||
m.Group("/user-id/{user-id}", func() {
|
m.Group("/user-id/{user-id}", func() {
|
||||||
m.Get("", activitypub.Person)
|
m.Get("", activitypub.Person)
|
||||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||||
}, context.UserIDAssignmentAPI())
|
}, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
|
||||||
m.Group("/actor", func() {
|
m.Group("/actor", func() {
|
||||||
m.Get("", activitypub.Actor)
|
m.Get("", activitypub.Actor)
|
||||||
m.Post("/inbox", activitypub.ActorInbox)
|
m.Post("/inbox", activitypub.ActorInbox)
|
||||||
|
@ -871,7 +904,7 @@ func Routes() *web.Route {
|
||||||
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
|
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
|
||||||
|
|
||||||
m.Get("/activities/feeds", user.ListUserActivityFeeds)
|
m.Get("/activities/feeds", user.ListUserActivityFeeds)
|
||||||
}, context.UserAssignmentAPI(), individualPermsChecker)
|
}, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker)
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
|
||||||
|
|
||||||
// Users (requires user scope)
|
// Users (requires user scope)
|
||||||
|
@ -891,7 +924,7 @@ func Routes() *web.Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Get("/subscriptions", user.GetWatchedRepos)
|
m.Get("/subscriptions", user.GetWatchedRepos)
|
||||||
}, context.UserAssignmentAPI())
|
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
||||||
|
|
||||||
// Users (requires user scope)
|
// Users (requires user scope)
|
||||||
|
@ -988,7 +1021,7 @@ func Routes() *web.Route {
|
||||||
m.Get("", user.IsStarring)
|
m.Get("", user.IsStarring)
|
||||||
m.Put("", user.Star)
|
m.Put("", user.Star)
|
||||||
m.Delete("", user.Unstar)
|
m.Delete("", user.Unstar)
|
||||||
}, repoAssignment())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||||
}
|
}
|
||||||
m.Get("/times", repo.ListMyTrackedTimes)
|
m.Get("/times", repo.ListMyTrackedTimes)
|
||||||
|
@ -1019,12 +1052,14 @@ func Routes() *web.Route {
|
||||||
|
|
||||||
// Repositories (requires repo scope, org scope)
|
// Repositories (requires repo scope, org scope)
|
||||||
m.Post("/org/{org}/repos",
|
m.Post("/org/{org}/repos",
|
||||||
|
// FIXME: we need org in context
|
||||||
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
|
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
|
||||||
reqToken(),
|
reqToken(),
|
||||||
bind(api.CreateRepoOption{}),
|
bind(api.CreateRepoOption{}),
|
||||||
repo.CreateOrgRepoDeprecated)
|
repo.CreateOrgRepoDeprecated)
|
||||||
|
|
||||||
// requires repo scope
|
// 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)
|
m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID)
|
||||||
|
|
||||||
// Repos (requires repo scope)
|
// Repos (requires repo scope)
|
||||||
|
@ -1305,7 +1340,7 @@ func Routes() *web.Route {
|
||||||
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
|
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
|
||||||
m.Delete("", repo.DeleteAvatar)
|
m.Delete("", repo.DeleteAvatar)
|
||||||
}, reqAdmin(), reqToken())
|
}, reqAdmin(), reqToken())
|
||||||
}, repoAssignment())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||||
|
|
||||||
// Notifications (requires notifications scope)
|
// Notifications (requires notifications scope)
|
||||||
|
@ -1314,7 +1349,7 @@ func Routes() *web.Route {
|
||||||
m.Combo("/notifications", reqToken()).
|
m.Combo("/notifications", reqToken()).
|
||||||
Get(notify.ListRepoNotifications).
|
Get(notify.ListRepoNotifications).
|
||||||
Put(notify.ReadRepoNotifications)
|
Put(notify.ReadRepoNotifications)
|
||||||
}, repoAssignment())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
|
||||||
|
|
||||||
// Issue (requires issue scope)
|
// Issue (requires issue scope)
|
||||||
|
@ -1428,7 +1463,7 @@ func Routes() *web.Route {
|
||||||
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
|
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
|
||||||
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
|
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
|
||||||
})
|
})
|
||||||
}, repoAssignment())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
|
||||||
|
|
||||||
// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs
|
// 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 {
|
||||||
m.Get("/files", reqToken(), packages.ListPackageFiles)
|
m.Get("/files", reqToken(), packages.ListPackageFiles)
|
||||||
})
|
})
|
||||||
m.Get("/", reqToken(), packages.ListPackages)
|
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
|
// Organizations
|
||||||
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
|
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
|
||||||
m.Group("/users/{username}/orgs", func() {
|
m.Group("/users/{username}/orgs", func() {
|
||||||
m.Get("", reqToken(), org.ListUserOrgs)
|
m.Get("", reqToken(), org.ListUserOrgs)
|
||||||
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
|
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.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
|
||||||
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
|
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
|
||||||
m.Group("/orgs/{org}", func() {
|
m.Group("/orgs/{org}", func() {
|
||||||
|
@ -1513,7 +1548,7 @@ func Routes() *web.Route {
|
||||||
m.Put("/unblock/{username}", org.UnblockUser)
|
m.Put("/unblock/{username}", org.UnblockUser)
|
||||||
}, context.UserAssignmentAPI())
|
}, context.UserAssignmentAPI())
|
||||||
}, reqToken(), reqOrgOwnership())
|
}, reqToken(), reqOrgOwnership())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly())
|
||||||
m.Group("/teams/{teamid}", func() {
|
m.Group("/teams/{teamid}", func() {
|
||||||
m.Combo("").Get(reqToken(), org.GetTeam).
|
m.Combo("").Get(reqToken(), org.GetTeam).
|
||||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
|
Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
|
||||||
|
@ -1533,7 +1568,7 @@ func Routes() *web.Route {
|
||||||
Get(reqToken(), org.GetTeamRepo)
|
Get(reqToken(), org.GetTeamRepo)
|
||||||
})
|
})
|
||||||
m.Get("/activities/feeds", org.ListTeamActivityFeeds)
|
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("/admin", func() {
|
||||||
m.Group("/cron", func() {
|
m.Group("/cron", func() {
|
||||||
|
|
|
@ -192,7 +192,7 @@ func GetAll(ctx *context.APIContext) {
|
||||||
// "$ref": "#/responses/OrganizationList"
|
// "$ref": "#/responses/OrganizationList"
|
||||||
|
|
||||||
vMode := []api.VisibleType{api.VisibleTypePublic}
|
vMode := []api.VisibleType{api.VisibleTypePublic}
|
||||||
if ctx.IsSigned {
|
if ctx.IsSigned && !ctx.PublicOnly {
|
||||||
vMode = append(vMode, api.VisibleTypeLimited)
|
vMode = append(vMode, api.VisibleTypeLimited)
|
||||||
if ctx.Doer.IsAdmin {
|
if ctx.Doer.IsAdmin {
|
||||||
vMode = append(vMode, api.VisibleTypePrivate)
|
vMode = append(vMode, api.VisibleTypePrivate)
|
||||||
|
|
|
@ -149,7 +149,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
}
|
}
|
||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
opts.Private = true
|
opts.Private = !ctx.PublicOnly
|
||||||
opts.AllLimited = true
|
opts.AllLimited = true
|
||||||
}
|
}
|
||||||
if ctx.FormString("owner") != "" {
|
if ctx.FormString("owner") != "" {
|
||||||
|
|
|
@ -130,6 +130,11 @@ func Search(ctx *context.APIContext) {
|
||||||
// "422":
|
// "422":
|
||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
|
private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
|
||||||
|
if ctx.PublicOnly {
|
||||||
|
private = false
|
||||||
|
}
|
||||||
|
|
||||||
opts := &repo_model.SearchRepoOptions{
|
opts := &repo_model.SearchRepoOptions{
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
|
@ -139,7 +144,7 @@ func Search(ctx *context.APIContext) {
|
||||||
TeamID: ctx.FormInt64("team_id"),
|
TeamID: ctx.FormInt64("team_id"),
|
||||||
TopicOnly: ctx.FormBool("topic"),
|
TopicOnly: ctx.FormBool("topic"),
|
||||||
Collaborate: optional.None[bool](),
|
Collaborate: optional.None[bool](),
|
||||||
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
|
Private: private,
|
||||||
Template: optional.None[bool](),
|
Template: optional.None[bool](),
|
||||||
StarredByID: ctx.FormInt64("starredBy"),
|
StarredByID: ctx.FormInt64("starredBy"),
|
||||||
IncludeDescription: ctx.FormBool("includeDesc"),
|
IncludeDescription: ctx.FormBool("includeDesc"),
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/routers/api/v1/utils"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
@ -68,12 +69,17 @@ func Search(ctx *context.APIContext) {
|
||||||
maxResults = 1
|
maxResults = 1
|
||||||
users = []*user_model.User{user_model.NewActionsUser()}
|
users = []*user_model.User{user_model.NewActionsUser()}
|
||||||
default:
|
default:
|
||||||
|
var visible []structs.VisibleType
|
||||||
|
if ctx.PublicOnly {
|
||||||
|
visible = []structs.VisibleType{structs.VisibleTypePublic}
|
||||||
|
}
|
||||||
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Keyword: ctx.FormTrim("q"),
|
Keyword: ctx.FormTrim("q"),
|
||||||
UID: uid,
|
UID: uid,
|
||||||
Type: user_model.UserTypeIndividual,
|
Type: user_model.UserTypeIndividual,
|
||||||
SearchByEmail: true,
|
SearchByEmail: true,
|
||||||
|
Visible: visible,
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -45,6 +45,7 @@ type APIContext struct {
|
||||||
Package *Package
|
Package *Package
|
||||||
QuotaGroup *quota_model.Group
|
QuotaGroup *quota_model.Group
|
||||||
QuotaRule *quota_model.Rule
|
QuotaRule *quota_model.Rule
|
||||||
|
PublicOnly bool // Whether the request is for a public endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -76,6 +76,34 @@ func TestAPIListIssues(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIListIssuesPublicOnly(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
owner1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo1.OwnerID})
|
||||||
|
|
||||||
|
session := loginUser(t, owner1.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner1.Name, repo1.Name))
|
||||||
|
link.RawQuery = url.Values{"state": {"all"}}.Encode()
|
||||||
|
req := NewRequest(t, "GET", link.String()).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
owner2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID})
|
||||||
|
|
||||||
|
session = loginUser(t, owner2.Name)
|
||||||
|
token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
|
||||||
|
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner2.Name, repo2.Name))
|
||||||
|
link.RawQuery = url.Values{"state": {"all"}}.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAPICreateIssue(t *testing.T) {
|
func TestAPICreateIssue(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
const body, title = "apiTestBody", "apiTestTitle"
|
const body, title = "apiTestBody", "apiTestTitle"
|
||||||
|
@ -404,6 +432,12 @@ func TestAPISearchIssues(t *testing.T) {
|
||||||
DecodeJSON(t, resp, &apiIssues)
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
assert.Len(t, apiIssues, expectedIssueCount)
|
assert.Len(t, apiIssues, expectedIssueCount)
|
||||||
|
|
||||||
|
publicOnlyToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 15) // 15 public issues
|
||||||
|
|
||||||
since := "2000-01-01T00:50:01+00:00" // 946687801
|
since := "2000-01-01T00:50:01+00:00" // 946687801
|
||||||
before := time.Unix(999307200, 0).Format(time.RFC3339)
|
before := time.Unix(999307200, 0).Format(time.RFC3339)
|
||||||
query.Add("since", since)
|
query.Add("since", since)
|
||||||
|
|
|
@ -29,9 +29,13 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
session := loginUser(t, user1.LowerName)
|
session := loginUser(t, user1.LowerName)
|
||||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
|
||||||
|
|
||||||
|
// public only token should be forbidden
|
||||||
|
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository)
|
||||||
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||||
|
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
bs, err := io.ReadAll(resp.Body)
|
bs, err := io.ReadAll(resp.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -43,6 +47,8 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||||
assert.EqualValues(t, "master", branches[1].Name)
|
assert.EqualValues(t, "master", branches[1].Name)
|
||||||
|
|
||||||
link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name))
|
link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name))
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||||
|
|
||||||
resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK)
|
resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
bs, err = io.ReadAll(resp.Body)
|
bs, err = io.ReadAll(resp.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -50,6 +56,8 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||||
require.NoError(t, json.Unmarshal(bs, &branch))
|
require.NoError(t, json.Unmarshal(bs, &branch))
|
||||||
assert.EqualValues(t, "test_branch", branch.Name)
|
assert.EqualValues(t, "test_branch", branch.Name)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||||
|
|
||||||
req := NewRequest(t, "POST", link.String()).AddTokenAuth(token)
|
req := NewRequest(t, "POST", link.String()).AddTokenAuth(token)
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`))
|
req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`))
|
||||||
|
@ -74,6 +82,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||||
|
|
||||||
link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name))
|
link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name))
|
||||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound)
|
MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound)
|
||||||
|
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||||
|
|
||||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent)
|
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -40,6 +40,19 @@ func TestAPIUserSearchLoggedIn(t *testing.T) {
|
||||||
assert.Contains(t, user.UserName, query)
|
assert.Contains(t, user.UserName, query)
|
||||||
assert.NotEmpty(t, user.Email)
|
assert.NotEmpty(t, user.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publicToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query).
|
||||||
|
AddTokenAuth(publicToken)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
results = SearchResults{}
|
||||||
|
DecodeJSON(t, resp, &results)
|
||||||
|
assert.NotEmpty(t, results.Data)
|
||||||
|
for _, user := range results.Data {
|
||||||
|
assert.Contains(t, user.UserName, query)
|
||||||
|
assert.NotEmpty(t, user.Email)
|
||||||
|
assert.True(t, user.Visibility == "public")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIUserSearchNotLoggedIn(t *testing.T) {
|
func TestAPIUserSearchNotLoggedIn(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue