From 717d0f5934826e0ddfdce176cf3cab303cd74fbb Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Tue, 12 Dec 2023 13:01:17 +0800
Subject: [PATCH] Do some missing checks (#28423)

---
 routers/api/v1/api.go             | 20 +++++++++++++++++++-
 routers/web/web.go                | 22 ++++++++++++++++++++--
 tests/integration/project_test.go | 23 +++++++++++++++++++++++
 3 files changed, 62 insertions(+), 3 deletions(-)
 create mode 100644 tests/integration/project_test.go

diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 13c6762b54..0e437bb92e 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -790,6 +790,24 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
 	}
 }
 
+func individualPermsChecker(ctx *context.APIContext) {
+	// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
+	if ctx.ContextUser.IsIndividual() {
+		switch {
+		case ctx.ContextUser.Visibility == api.VisibleTypePrivate:
+			if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
+				ctx.NotFound("Visit Project", nil)
+				return
+			}
+		case ctx.ContextUser.Visibility == api.VisibleTypeLimited:
+			if ctx.Doer == nil {
+				ctx.NotFound("Visit Project", nil)
+				return
+			}
+		}
+	}
+}
+
 // check for and warn against deprecated authentication options
 func checkDeprecatedAuthMethods(ctx *context.APIContext) {
 	if ctx.FormString("token") != "" || ctx.FormString("access_token") != "" {
@@ -899,7 +917,7 @@ func Routes() *web.Route {
 				}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
 
 				m.Get("/activities/feeds", user.ListUserActivityFeeds)
-			}, context_service.UserAssignmentAPI())
+			}, context_service.UserAssignmentAPI(), individualPermsChecker)
 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
 
 		// Users (requires user scope)
diff --git a/routers/web/web.go b/routers/web/web.go
index da7360f1b8..db0588056b 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -796,6 +796,24 @@ func registerRoutes(m *web.Route) {
 		}
 	}
 
+	individualPermsChecker := func(ctx *context.Context) {
+		// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
+		if ctx.ContextUser.IsIndividual() {
+			switch {
+			case ctx.ContextUser.Visibility == structs.VisibleTypePrivate:
+				if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
+					ctx.NotFound("Visit Project", nil)
+					return
+				}
+			case ctx.ContextUser.Visibility == structs.VisibleTypeLimited:
+				if ctx.Doer == nil {
+					ctx.NotFound("Visit Project", nil)
+					return
+				}
+			}
+		}
+	}
+
 	// ***** START: Organization *****
 	m.Group("/org", func() {
 		m.Group("/{org}", func() {
@@ -976,11 +994,11 @@ func registerRoutes(m *web.Route) {
 					return
 				}
 			})
-		})
+		}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), individualPermsChecker)
 
 		m.Group("", func() {
 			m.Get("/code", user.CodeSearch)
-		}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false))
+		}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false), individualPermsChecker)
 	}, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
 
 	m.Group("/{username}/{reponame}", func() {
diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go
new file mode 100644
index 0000000000..45061c5b24
--- /dev/null
+++ b/tests/integration/project_test.go
@@ -0,0 +1,23 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+	"net/http"
+	"testing"
+
+	"code.gitea.io/gitea/tests"
+)
+
+func TestPrivateRepoProject(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	// not logged in user
+	req := NewRequest(t, "GET", "/user31/-/projects")
+	MakeRequest(t, req, http.StatusNotFound)
+
+	sess := loginUser(t, "user1")
+	req = NewRequest(t, "GET", "/user31/-/projects")
+	sess.MakeRequest(t, req, http.StatusOK)
+}