From bdf3be53b0b8005effb3bca8d9cc8dba7d35261c Mon Sep 17 00:00:00 2001
From: KN4CK3R <admin@oldschoolhack.me>
Date: Fri, 23 Sep 2022 05:25:53 +0200
Subject: [PATCH] Respect `REQUIRE_SIGNIN_VIEW` for packages (#20873) (#21232)

Backport of #20873

When REQUIRE_SIGNIN_VIEW = true, even with public repositories, you can only see them after you login. The packages should not be accessed without login.

Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
---
 integrations/api_packages_generic_test.go | 13 +++
 modules/context/package.go                | 98 +++++++++++++----------
 2 files changed, 70 insertions(+), 41 deletions(-)

diff --git a/integrations/api_packages_generic_test.go b/integrations/api_packages_generic_test.go
index adaf99e98..fb1ee9d28 100644
--- a/integrations/api_packages_generic_test.go
+++ b/integrations/api_packages_generic_test.go
@@ -14,6 +14,7 @@ import (
 	"code.gitea.io/gitea/models/packages"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/setting"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -79,6 +80,18 @@ func TestPackageGeneric(t *testing.T) {
 		assert.Equal(t, int64(1), pvs[0].DownloadCount)
 	})
 
+	t.Run("RequireSignInView", func(t *testing.T) {
+		defer PrintCurrentTest(t)()
+
+		setting.Service.RequireSignInView = true
+		defer func() {
+			setting.Service.RequireSignInView = false
+		}()
+
+		req := NewRequest(t, "GET", url)
+		MakeRequest(t, req, http.StatusUnauthorized)
+	})
+
 	t.Run("Delete", func(t *testing.T) {
 		defer PrintCurrentTest(t)()
 
diff --git a/modules/context/package.go b/modules/context/package.go
index 92a97831d..89a8c4466 100644
--- a/modules/context/package.go
+++ b/modules/context/package.go
@@ -13,6 +13,7 @@ import (
 	"code.gitea.io/gitea/models/perm"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
 )
 
@@ -52,47 +53,11 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
 		Owner: ctx.ContextUser,
 	}
 
-	if ctx.Package.Owner.IsOrganization() {
-		org := organization.OrgFromUser(ctx.Package.Owner)
-
-		// 1. Get user max authorize level for the org (may be none, if user is not member of the org)
-		if ctx.Doer != nil {
-			var err error
-			ctx.Package.AccessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
-			if err != nil {
-				errCb(http.StatusInternalServerError, "GetOrgUserMaxAuthorizeLevel", err)
-				return
-			}
-			// If access mode is less than write check every team for more permissions
-			if ctx.Package.AccessMode < perm.AccessModeWrite {
-				teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID)
-				if err != nil {
-					errCb(http.StatusInternalServerError, "GetUserOrgTeams", err)
-					return
-				}
-				for _, t := range teams {
-					perm := t.UnitAccessModeCtx(ctx, unit.TypePackages)
-					if ctx.Package.AccessMode < perm {
-						ctx.Package.AccessMode = perm
-					}
-				}
-			}
-		}
-		// 2. If authorize level is none, check if org is visible to user
-		if ctx.Package.AccessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) {
-			ctx.Package.AccessMode = perm.AccessModeRead
-		}
-	} else {
-		if ctx.Doer != nil && !ctx.Doer.IsGhost() {
-			// 1. Check if user is package owner
-			if ctx.Doer.ID == ctx.Package.Owner.ID {
-				ctx.Package.AccessMode = perm.AccessModeOwner
-			} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic || ctx.Package.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited
-				ctx.Package.AccessMode = perm.AccessModeRead
-			}
-		} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public
-			ctx.Package.AccessMode = perm.AccessModeRead
-		}
+	var err error
+	ctx.Package.AccessMode, err = determineAccessMode(ctx)
+	if err != nil {
+		errCb(http.StatusInternalServerError, "determineAccessMode", err)
+		return
 	}
 
 	packageType := ctx.Params("type")
@@ -117,6 +82,57 @@ func packageAssignment(ctx *Context, errCb func(int, string, interface{})) {
 	}
 }
 
+func determineAccessMode(ctx *Context) (perm.AccessMode, error) {
+	accessMode := perm.AccessModeNone
+
+	if setting.Service.RequireSignInView && ctx.Doer == nil {
+		return accessMode, nil
+	}
+
+	if ctx.Package.Owner.IsOrganization() {
+		org := organization.OrgFromUser(ctx.Package.Owner)
+
+		// 1. Get user max authorize level for the org (may be none, if user is not member of the org)
+		if ctx.Doer != nil {
+			var err error
+			accessMode, err = org.GetOrgUserMaxAuthorizeLevel(ctx.Doer.ID)
+			if err != nil {
+				return accessMode, err
+			}
+			// If access mode is less than write check every team for more permissions
+			if accessMode < perm.AccessModeWrite {
+				teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID)
+				if err != nil {
+					return accessMode, err
+				}
+				for _, t := range teams {
+					perm := t.UnitAccessModeCtx(ctx, unit.TypePackages)
+					if accessMode < perm {
+						accessMode = perm
+					}
+				}
+			}
+		}
+		// 2. If authorize level is none, check if org is visible to user
+		if accessMode == perm.AccessModeNone && organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) {
+			accessMode = perm.AccessModeRead
+		}
+	} else {
+		if ctx.Doer != nil && !ctx.Doer.IsGhost() {
+			// 1. Check if user is package owner
+			if ctx.Doer.ID == ctx.Package.Owner.ID {
+				accessMode = perm.AccessModeOwner
+			} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic || ctx.Package.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited
+				accessMode = perm.AccessModeRead
+			}
+		} else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public
+			accessMode = perm.AccessModeRead
+		}
+	}
+
+	return accessMode, nil
+}
+
 // PackageContexter initializes a package context for a request.
 func PackageContexter() func(next http.Handler) http.Handler {
 	return func(next http.Handler) http.Handler {