diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index 64685e4cc24..99914a7bc24 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -85,7 +85,7 @@ func (opts FindGPGKeyOptions) ToConds() builder.Cond { cond = cond.And(builder.Eq{"primary_key_id": ""}) } - if opts.OwnerID > 0 { + if opts.OwnerID != 0 { cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) } if opts.KeyID != "" { diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index a84ba7736ca..3d628f8d10b 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -224,7 +224,7 @@ func (opts FindPublicKeyOptions) ToOrders() string { func (opts FindPublicKeyOptions) ToConds() builder.Cond { cond := builder.NewCond() - if opts.OwnerID > 0 { + if opts.OwnerID != 0 { cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) } if opts.Fingerprint != "" { diff --git a/models/organization/org_list.go b/models/organization/org_list.go index 9f9ea69c7c7..93b566be5a3 100644 --- a/models/organization/org_list.go +++ b/models/organization/org_list.go @@ -70,7 +70,7 @@ func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder { func (opts FindOrgOptions) ToConds() builder.Cond { var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization} - if opts.UserID > 0 { + if opts.UserID != 0 { cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludeVisibility == structs.VisibleTypePrivate))) } // public=0, limited=1, private=2 diff --git a/models/project/project.go b/models/project/project.go index 97c1bee27a7..ee916bcbc78 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -225,7 +225,7 @@ func (opts SearchOptions) ToConds() builder.Cond { if opts.Type > 0 { cond = cond.And(builder.Eq{"type": opts.Type}) } - if opts.OwnerID > 0 { + if opts.OwnerID != 0 { cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) } diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 5d4284534d0..516fedd472f 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -373,8 +373,7 @@ func SearchRepositoryCondition(opts SearchRepoOptions) builder.Cond { cond := builder.NewCond() if opts.Private { - if opts.Actor != nil && !opts.Actor.IsAdmin && opts.Actor.ID != opts.OwnerID { - // OK we're in the context of a User + if opts.Actor != nil && !opts.Actor.IsAdmin { cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid)) } } else { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 924917f251f..1fccceddc30 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -317,8 +317,14 @@ func rejectPublicOnly() func(ctx *context.APIContext) { } } -func contextAuthenticatedUser() func(ctx *context.APIContext) { +func authenticatedUserAsContextUser(allowSystemUser bool) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { + isIndividualOrBot := ctx.Doer.IsIndividual() || ctx.Doer.IsTypeBot() + isSystem := ctx.Doer.ID < 0 + if !isIndividualOrBot || isSystem && !allowSystemUser { + ctx.APIError(http.StatusForbidden, "this endpoint is only available for individual user or bot accounts") + return + } ctx.ContextUser = ctx.Doer } } @@ -1162,8 +1168,11 @@ func Routes() *web.Router { }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) // Users (requires user scope) + contextRealDoerUser := authenticatedUserAsContextUser(false) + contextRealOrSystemUser := authenticatedUserAsContextUser(true) + m.Get("/user", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), contextRealOrSystemUser, checkTokenPublicOnly(), user.GetAuthenticatedUser) m.Group("/user", func() { - m.Get("", user.GetAuthenticatedUser) + m.Get("/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs) m.Group("/settings", func() { m.Get("", user.GetUserSettings) m.Patch("", bind(api.UserSettingsOptions{}), user.UpdateUserSettings) @@ -1283,7 +1292,7 @@ func Routes() *web.Router { m.Delete("", user.UnblockUser) }, context.UserAssignmentAPI(), checkTokenPublicOnly()) }, rejectPublicOnly()) - }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), contextAuthenticatedUser(), checkTokenPublicOnly()) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), contextRealDoerUser, checkTokenPublicOnly()) // Repositories (requires repo scope, org scope) m.Post("/org/{org}/repos", @@ -1780,7 +1789,6 @@ func Routes() *web.Router { }, reqToken(), 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), checkTokenPublicOnly(), org.ListMyOrgs) m.Group("/users/{username}/orgs", func() { m.Get("", reqToken(), org.ListUserOrgs) m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions) diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go index b9dbde73565..6bcd860e0e2 100644 --- a/routers/api/v1/user/repo.go +++ b/routers/api/v1/user/repo.go @@ -129,6 +129,7 @@ func ListMyRepos(ctx *context.APIContext) { permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer) if err != nil { ctx.APIErrorInternal(err) + return } results[i] = convert.ToRepo(ctx, repo, permission) } diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go index 688574140b2..d1d9c9d6e1d 100644 --- a/routers/web/feed/profile.go +++ b/routers/web/feed/profile.go @@ -7,11 +7,13 @@ import ( "time" activities_model "gitea.dev/models/activities" + auth_model "gitea.dev/models/auth" "gitea.dev/models/organization" "gitea.dev/models/renderhelper" user_model "gitea.dev/models/user" "gitea.dev/modules/markup/markdown" "gitea.dev/modules/setting" + "gitea.dev/modules/util" "gitea.dev/services/context" feed_service "gitea.dev/services/feed" @@ -37,6 +39,10 @@ func showUserFeed(ctx *context.Context, formatType string) { ctx.NotFound(nil) return } + context.CheckTokenScopes(ctx, nil, util.Iif(ctx.ContextUser.IsOrganization(), auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser)) + if ctx.Written() { + return + } includePrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) if ctx.IsSigned && isOrganisation && !includePrivate { diff --git a/routers/web/web.go b/routers/web/web.go index c1a14038afc..54d87e6c332 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -897,9 +897,19 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { }, adminReq, ctxDataSet(reqctx.ContextData{"EnableOAuth2": setting.OAuth2.Enabled, "EnablePackages": setting.Packages.Enabled})) // ***** END: Admin ***** + // tokens only authenticate feeds, the other variants render data outside any token scope + allowBasicForFeeds := types.PreMiddlewareProvider(func(next http.Handler) http.Handler { + nextWithBasic := webAuth.AllowBasic(next) + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if isFeed, _ := feed.GetFeedType(req.URL.Path, req); isFeed { + nextWithBasic.ServeHTTP(w, req) + } else { + next.ServeHTTP(w, req) + } + }) + }) m.Group("", func() { - // it handles "username.rss" in the handler, so allow basic auth as other rss/atom routes - m.Get("/{username}", webAuth.AllowBasic, user.UsernameSubRoute) + m.Get("/{username}", allowBasicForFeeds, user.UsernameSubRoute) m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), webAuth.AllowBasic, webAuth.AllowOAuth2, repo.GetAttachment) }, optSignIn) diff --git a/tests/integration/api_actions_permission_test.go b/tests/integration/api_actions_permission_test.go index 90908662966..65413ea0677 100644 --- a/tests/integration/api_actions_permission_test.go +++ b/tests/integration/api_actions_permission_test.go @@ -24,6 +24,17 @@ func testActionUserSignIn(t *testing.T) { assert.Equal(t, "gitea-actions", u.UserName) } +func testActionUserOwnsNothing(t *testing.T) { + const token = "8061e833a55f6fc0157c98b883e91fcfeeb1a71a" + for _, link := range []string{"/api/v1/user/repos", "/api/v1/user/orgs", "/api/v1/user/actions/runs"} { + MakeRequest(t, NewRequest(t, "GET", link).AddTokenAuth(token), http.StatusForbidden) + } + for _, link := range []string{"/api/v1/users/gitea-actions/orgs", "/api/v1/users/gitea-actions/keys"} { + assert.JSONEq(t, "[]", MakeRequest(t, NewRequest(t, "GET", link).AddTokenAuth(token), http.StatusOK).Body.String(), link) + } + assert.NotContains(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/repos/search?uid=-2").AddTokenAuth(token), http.StatusOK).Body.String(), `"private":true`) +} + func testActionUserAccessPublicRepo(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/raw/README.md"). AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") @@ -48,6 +59,7 @@ func TestActionUserAccessPermission(t *testing.T) { defer tests.PrepareTestEnv(t)() t.Run("ActionUserSignIn", testActionUserSignIn) + t.Run("ActionUserOwnsNothing", testActionUserOwnsNothing) t.Run("ActionUserAccessPublicRepo", testActionUserAccessPublicRepo) t.Run("ActionUserNoAccessOtherPrivateRepo", testActionUserNoAccessOtherPrivateRepo) } diff --git a/tests/integration/feed_user_test.go b/tests/integration/feed_user_test.go index 48a88c4ae06..2ee3de0ced4 100644 --- a/tests/integration/feed_user_test.go +++ b/tests/integration/feed_user_test.go @@ -6,6 +6,7 @@ package integration import ( "encoding/xml" "net/http" + "strings" "testing" auth_model "gitea.dev/models/auth" @@ -31,103 +32,38 @@ type RSS struct { } func TestFeedUser(t *testing.T) { - t.Run("User", func(t *testing.T) { - t.Run("Atom", func(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/user2.atom") - resp := MakeRequest(t, req, http.StatusOK) - - data := resp.Body.String() - assert.Contains(t, data, `