From 4e824a735ea9290f75f57d1211d1b7e8c4e9dda2 Mon Sep 17 00:00:00 2001
From: Giteabot <teabot@gitea.io>
Date: Sun, 1 Oct 2023 19:54:11 +0800
Subject: [PATCH] Allow get release download files and lfs files with oauth2
 token format (#26430) (#27379)

Backport #26430 by @lunny

Fix #26165
Fix #25257

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
---
 models/fixtures/attachment.yml                  | 13 +++++++++++++
 models/fixtures/release.yml                     | 14 ++++++++++++++
 routers/web/web.go                              |  8 +++-----
 services/auth/oauth2.go                         |  4 +++-
 .../user2/repo2.git/refs/tags/v1.1              |  1 +
 tests/integration/release_test.go               | 17 +++++++++++++++++
 tests/test_utils.go                             | 14 ++++++++++++++
 .../a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22    |  1 +
 8 files changed, 66 insertions(+), 6 deletions(-)
 create mode 100644 tests/gitea-repositories-meta/user2/repo2.git/refs/tags/v1.1
 create mode 100644 tests/testdata/data/attachments/a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22

diff --git a/models/fixtures/attachment.yml b/models/fixtures/attachment.yml
index 9ad43fa2b7..7882d8bff2 100644
--- a/models/fixtures/attachment.yml
+++ b/models/fixtures/attachment.yml
@@ -140,3 +140,16 @@
   download_count: 0
   size: 0
   created_unix: 946684800
+
+-
+  id: 12
+  uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22
+  repo_id: 2
+  issue_id: 0
+  release_id: 11
+  uploader_id: 2
+  comment_id: 0
+  name: README.md
+  download_count: 0
+  size: 0
+  created_unix: 946684800
diff --git a/models/fixtures/release.yml b/models/fixtures/release.yml
index 4ed7df440d..372a79509f 100644
--- a/models/fixtures/release.yml
+++ b/models/fixtures/release.yml
@@ -136,3 +136,17 @@
   is_prerelease: false
   is_tag: false
   created_unix: 946684803
+
+- id: 11
+  repo_id: 2
+  publisher_id: 2
+  tag_name: "v1.1"
+  lower_tag_name: "v1.1"
+  target: ""
+  title: "v1.1"
+  sha1: "205ac761f3326a7ebe416e8673760016450b5cec"
+  num_commits: 2
+  is_draft: false
+  is_prerelease: false
+  is_tag: false
+  created_unix: 946684803
diff --git a/routers/web/web.go b/routers/web/web.go
index 99862505b4..2154838726 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -978,9 +978,6 @@ func registerRoutes(m *web.Route) {
 		}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false))
 	}, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
 
-	// ***** Release Attachment Download without Signin
-	m.Get("/{username}/{reponame}/releases/download/{vTag}/{fileName}", ignSignIn, context.RepoAssignment, repo.MustBeNotEmpty, repo.RedirectDownload)
-
 	m.Group("/{username}/{reponame}", func() {
 		m.Group("/settings", func() {
 			m.Group("", func() {
@@ -1240,8 +1237,9 @@ func registerRoutes(m *web.Route) {
 			m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS)
 			m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom)
 		}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
-			repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag, true))
-		m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, reqRepoReleaseReader, repo.GetAttachment)
+			repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true))
+		m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment)
+		m.Get("/releases/download/{vTag}/{fileName}", repo.MustBeNotEmpty, repo.RedirectDownload)
 		m.Group("/releases", func() {
 			m.Get("/new", repo.NewRelease)
 			m.Post("/new", web.Bind(forms.NewReleaseForm{}), repo.NewReleasePost)
diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go
index 6572d661e8..38b705cc5b 100644
--- a/services/auth/oauth2.go
+++ b/services/auth/oauth2.go
@@ -125,7 +125,9 @@ func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store Dat
 // If verification is successful returns an existing user object.
 // Returns nil if verification fails.
 func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
-	if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) {
+	// These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs
+	if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) &&
+		!gitRawReleasePathRe.MatchString(req.URL.Path) {
 		return nil, nil
 	}
 
diff --git a/tests/gitea-repositories-meta/user2/repo2.git/refs/tags/v1.1 b/tests/gitea-repositories-meta/user2/repo2.git/refs/tags/v1.1
new file mode 100644
index 0000000000..334d09ca02
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/repo2.git/refs/tags/v1.1
@@ -0,0 +1 @@
+1032bbf17fbc0d9c95bb5418dabe8f8c99278700
diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go
index 8de761ea6c..42d0d00e78 100644
--- a/tests/integration/release_test.go
+++ b/tests/integration/release_test.go
@@ -239,3 +239,20 @@ func TestViewTagsList(t *testing.T) {
 
 	assert.EqualValues(t, []string{"v1.0", "delete-tag", "v1.1"}, tagNames)
 }
+
+func TestDownloadReleaseAttachment(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	tests.PrepareAttachmentsStorage(t)
+
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
+
+	url := repo.Link() + "/releases/download/v1.1/README.md"
+
+	req := NewRequest(t, "GET", url)
+	MakeRequest(t, req, http.StatusNotFound)
+
+	req = NewRequest(t, "GET", url)
+	session := loginUser(t, "user2")
+	session.MakeRequest(t, req, http.StatusOK)
+}
diff --git a/tests/test_utils.go b/tests/test_utils.go
index 089b4dce1c..50049e73f0 100644
--- a/tests/test_utils.go
+++ b/tests/test_utils.go
@@ -179,6 +179,20 @@ func InitTest(requireGitea bool) {
 	routers.InitWebInstalled(graceful.GetManager().HammerContext())
 }
 
+func PrepareAttachmentsStorage(t testing.TB) {
+	// prepare attachments directory and files
+	assert.NoError(t, storage.Clean(storage.Attachments))
+
+	s, err := storage.NewStorage(setting.LocalStorageType, &setting.Storage{
+		Path: filepath.Join(filepath.Dir(setting.AppPath), "tests", "testdata", "data", "attachments"),
+	})
+	assert.NoError(t, err)
+	assert.NoError(t, s.IterateObjects("", func(p string, obj storage.Object) error {
+		_, err = storage.Copy(storage.Attachments, p, s, p)
+		return err
+	}))
+}
+
 func PrepareTestEnv(t testing.TB, skip ...int) func() {
 	t.Helper()
 	ourSkip := 1
diff --git a/tests/testdata/data/attachments/a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22 b/tests/testdata/data/attachments/a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22
new file mode 100644
index 0000000000..96fc98807f
--- /dev/null
+++ b/tests/testdata/data/attachments/a/0/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22
@@ -0,0 +1 @@
+# This is a release README