mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
- fix: prevent git write to wiki repo from unauthorized user via git HTTP - fix: prevent LFS authorization token from being used for read/write access after user's access is restricted from Forgejo - fix: prevent scoped API access (OAuth tokens, Access tokens) from accessing resources beyond their permitted scope via non-API endpoints (e.g. /user/repo/raw/...) - fix: implementing missing OAuth validation checks, improve protections against race conditions - fix: prevent OAuth redirect URI spoofing via non-ascii case collision - fix: strengthen Actions Artifact V4 signature algorithm against spoofing attacks - fix: update Go toolchain to 1.25.10 Co-authored-by: Derzsi Dániel <daniel@tohka.us> Co-authored-by: jvoisin <julien.voisin@dustri.org> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12495
149 lines
5.8 KiB
Go
149 lines
5.8 KiB
Go
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
|
|
auth_model "forgejo.org/models/auth"
|
|
"forgejo.org/modules/setting"
|
|
"forgejo.org/tests"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestDownloadByID(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
session := loginUser(t, "user2")
|
|
|
|
// Request raw blob
|
|
req := NewRequest(t, "GET", "/user2/repo1/raw/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String())
|
|
}
|
|
|
|
func TestDownloadByIDForSVGUsesSecureHeaders(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
|
|
// Request raw blob
|
|
req := NewRequest(t, "GET", "/user2/repo2/raw/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
assert.Equal(t, "default-src 'none'; style-src 'unsafe-inline'; sandbox", resp.Header().Get("Content-Security-Policy"))
|
|
assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type"))
|
|
assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options"))
|
|
}
|
|
|
|
func TestDownloadByIDMedia(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
|
|
// Request raw blob
|
|
req := NewRequest(t, "GET", "/user2/repo1/media/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String())
|
|
}
|
|
|
|
func TestDownloadByIDMediaForSVGUsesSecureHeaders(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
|
|
// Request raw blob
|
|
req := NewRequest(t, "GET", "/user2/repo2/media/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
assert.Equal(t, "default-src 'none'; style-src 'unsafe-inline'; sandbox", resp.Header().Get("Content-Security-Policy"))
|
|
assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type"))
|
|
assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options"))
|
|
}
|
|
|
|
func TestDownloadRawTextFileWithoutMimeTypeMapping(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
|
|
req := NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type"))
|
|
}
|
|
|
|
func TestDownloadRawTextFileWithMimeTypeMapping(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
setting.MimeTypeMap.Map[".xml"] = "text/xml"
|
|
setting.MimeTypeMap.Enabled = true
|
|
|
|
session := loginUser(t, "user2")
|
|
|
|
req := NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
assert.Equal(t, "text/xml; charset=utf-8", resp.Header().Get("Content-Type"))
|
|
|
|
delete(setting.MimeTypeMap.Map, ".xml")
|
|
setting.MimeTypeMap.Enabled = false
|
|
}
|
|
|
|
// Access under `/raw` is permitted for API tokens. Those API tokens then need to have the read:repository scope to
|
|
// permit access, though. The below series of tests covers the middleware combinations on the entire `/user/repo/raw/*`
|
|
// URL tree as they use a common middleware implementation.
|
|
func TestDownloadAccessViaAPITokens(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
t.Run("no read:repository scope", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
allToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadMisc)
|
|
|
|
t.Run("denied public repo1", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
req := NewRequest(t, "GET", "/user2/repo1/raw/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f").AddTokenAuth(allToken)
|
|
MakeRequest(t, req, http.StatusForbidden)
|
|
})
|
|
t.Run("denied private repo2", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
req := NewRequest(t, "GET", "/user2/repo2/raw/blob/1032bbf17fbc0d9c95bb5418dabe8f8c99278700").AddTokenAuth(allToken)
|
|
MakeRequest(t, req, http.StatusForbidden)
|
|
})
|
|
t.Run("denied private repo16", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
req := NewRequest(t, "GET", "/user2/repo16/raw/blob/69554a64c1e6030f051e5c3f94bfbd773cd6a324").AddTokenAuth(allToken)
|
|
MakeRequest(t, req, http.StatusForbidden)
|
|
})
|
|
})
|
|
|
|
t.Run("all access token", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
allToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
|
|
|
t.Run("allowed public repo1", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
req := NewRequest(t, "GET", "/user2/repo1/raw/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f").AddTokenAuth(allToken)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String())
|
|
})
|
|
t.Run("allowed private repo2", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
req := NewRequest(t, "GET", "/user2/repo2/raw/blob/1032bbf17fbc0d9c95bb5418dabe8f8c99278700").AddTokenAuth(allToken)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, "tree ba1aed4e2ea2443d76cec241b96be4ec990852ec\nparent 205ac761f3326a7ebe416e8673760016450b5cec\nauthor Jimmy Praet <jimmy.praet@telenet.be> 1624996449 +0200\ncommitter Jimmy Praet <jimmy.praet@telenet.be> 1624996449 +0200\n\nAdd test.xml\n", resp.Body.String())
|
|
})
|
|
t.Run("allowed private repo16", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
req := NewRequest(t, "GET", "/user2/repo16/raw/blob/69554a64c1e6030f051e5c3f94bfbd773cd6a324").AddTokenAuth(allToken)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, "tree 24f83a471f77579fea57bac7255d6e64e70fce1c\nparent 27566bd5738fc8b4e3fef3c5e72cce608537bd95\nauthor User2 <user2@example.com> 1502042309 +0200\ncommitter User2 <user2@example.com> 1502042309 +0200\n\nnot signed commit\n", resp.Body.String())
|
|
})
|
|
})
|
|
}
|