mirror of
https://github.com/go-gitea/gitea.git
synced 2025-02-02 15:09:33 -05:00
Add basic auth support to rss/atom feeds (#33371)
Allows RSS readers to access private feeds using their basic auth capabilities. Not all clients feature the ability to add cookies or headers. fixes #32458 Tested with miniflux no credentials: ![image](https://github.com/user-attachments/assets/8c3369f2-1cf6-4ce3-ac6e-84447e454928) basic auth entered: ![image](https://github.com/user-attachments/assets/c93ff22c-1429-4a80-898f-91d9f35c7c61) ![image](https://github.com/user-attachments/assets/60d83afd-9dde-4973-a440-ff8138799e87) --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
26b51aa032
commit
c79adf00b8
@ -26,13 +26,17 @@ type globalVarsStruct struct {
|
|||||||
gitRawOrAttachPathRe *regexp.Regexp
|
gitRawOrAttachPathRe *regexp.Regexp
|
||||||
lfsPathRe *regexp.Regexp
|
lfsPathRe *regexp.Regexp
|
||||||
archivePathRe *regexp.Regexp
|
archivePathRe *regexp.Regexp
|
||||||
|
feedPathRe *regexp.Regexp
|
||||||
|
feedRefPathRe *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||||
return &globalVarsStruct{
|
return &globalVarsStruct{
|
||||||
gitRawOrAttachPathRe: regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/)|(?:attachments/))`),
|
gitRawOrAttachPathRe: regexp.MustCompile(`^/[-.\w]+/[-.\w]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/)|(?:attachments/))`),
|
||||||
lfsPathRe: regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`),
|
lfsPathRe: regexp.MustCompile(`^/[-.\w]+/[-.\w]+/info/lfs/`),
|
||||||
archivePathRe: regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/archive/`),
|
archivePathRe: regexp.MustCompile(`^/[-.\w]+/[-.\w]+/archive/`),
|
||||||
|
feedPathRe: regexp.MustCompile(`^/[-.\w]+(/[-.\w]+)?\.(rss|atom)$`), // "/owner.rss" or "/owner/repo.atom"
|
||||||
|
feedRefPathRe: regexp.MustCompile(`^/[-.\w]+/[-.\w]+/(rss|atom)/`), // "/owner/repo/rss/branch/..."
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -61,6 +65,16 @@ func (a *authPathDetector) isAttachmentDownload() bool {
|
|||||||
return strings.HasPrefix(a.req.URL.Path, "/attachments/") && a.req.Method == "GET"
|
return strings.HasPrefix(a.req.URL.Path, "/attachments/") && a.req.Method == "GET"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *authPathDetector) isFeedRequest(req *http.Request) bool {
|
||||||
|
if !setting.Other.EnableFeed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if req.Method != "GET" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return a.vars.feedPathRe.MatchString(req.URL.Path) || a.vars.feedRefPathRe.MatchString(req.URL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
// isContainerPath checks if the request targets the container endpoint
|
// isContainerPath checks if the request targets the container endpoint
|
||||||
func (a *authPathDetector) isContainerPath() bool {
|
func (a *authPathDetector) isContainerPath() bool {
|
||||||
return strings.HasPrefix(a.req.URL.Path, "/v2/")
|
return strings.HasPrefix(a.req.URL.Path, "/v2/")
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -92,6 +93,19 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
|
|||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer test.MockVariableValue(&setting.LFS.StartServer)()
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.path, func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil)
|
||||||
|
setting.LFS.StartServer = false
|
||||||
|
assert.Equal(t, tt.want, newAuthPathDetector(req).isGitRawOrAttachOrLFSPath())
|
||||||
|
|
||||||
|
setting.LFS.StartServer = true
|
||||||
|
assert.Equal(t, tt.want, newAuthPathDetector(req).isGitRawOrAttachOrLFSPath())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
lfsTests := []string{
|
lfsTests := []string{
|
||||||
"/owner/repo/info/lfs/",
|
"/owner/repo/info/lfs/",
|
||||||
"/owner/repo/info/lfs/objects/batch",
|
"/owner/repo/info/lfs/objects/batch",
|
||||||
@ -103,19 +117,6 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
|
|||||||
"/owner/repo/info/lfs/locks/verify",
|
"/owner/repo/info/lfs/locks/verify",
|
||||||
"/owner/repo/info/lfs/locks/123/unlock",
|
"/owner/repo/info/lfs/locks/123/unlock",
|
||||||
}
|
}
|
||||||
|
|
||||||
origLFSStartServer := setting.LFS.StartServer
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.path, func(t *testing.T) {
|
|
||||||
req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil)
|
|
||||||
setting.LFS.StartServer = false
|
|
||||||
assert.Equal(t, tt.want, newAuthPathDetector(req).isGitRawOrAttachOrLFSPath())
|
|
||||||
|
|
||||||
setting.LFS.StartServer = true
|
|
||||||
assert.Equal(t, tt.want, newAuthPathDetector(req).isGitRawOrAttachOrLFSPath())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, tt := range lfsTests {
|
for _, tt := range lfsTests {
|
||||||
t.Run(tt, func(t *testing.T) {
|
t.Run(tt, func(t *testing.T) {
|
||||||
req, _ := http.NewRequest("POST", tt, nil)
|
req, _ := http.NewRequest("POST", tt, nil)
|
||||||
@ -128,5 +129,27 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
|
|||||||
assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer)
|
assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setting.LFS.StartServer = origLFSStartServer
|
}
|
||||||
|
|
||||||
|
func Test_isFeedRequest(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
want bool
|
||||||
|
path string
|
||||||
|
}{
|
||||||
|
{true, "/user.rss"},
|
||||||
|
{true, "/user/repo.atom"},
|
||||||
|
{false, "/user/repo"},
|
||||||
|
{false, "/use/repo/file.rss"},
|
||||||
|
|
||||||
|
{true, "/org/repo/rss/branch/xxx"},
|
||||||
|
{true, "/org/repo/atom/tag/xxx"},
|
||||||
|
{false, "/org/repo/branch/main/rss/any"},
|
||||||
|
{false, "/org/atom/any"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.path, func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("GET", "http://localhost"+tt.path, nil)
|
||||||
|
assert.Equal(t, tt.want, newAuthPathDetector(req).isFeedRequest(req))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,9 +47,10 @@ func (b *Basic) Name() string {
|
|||||||
// name/token on successful validation.
|
// name/token on successful validation.
|
||||||
// Returns nil if header is empty or validation fails.
|
// Returns nil if header is empty or validation fails.
|
||||||
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
||||||
// Basic authentication should only fire on API, Download or on Git or LFSPaths
|
// Basic authentication should only fire on API, Feed, Download or on Git or LFSPaths
|
||||||
|
// Not all feed (rss/atom) clients feature the ability to add cookies or headers, so we need to allow basic auth for feeds
|
||||||
detector := newAuthPathDetector(req)
|
detector := newAuthPathDetector(req)
|
||||||
if !detector.isAPIPath() && !detector.isContainerPath() && !detector.isAttachmentDownload() && !detector.isGitRawOrAttachOrLFSPath() {
|
if !detector.isAPIPath() && !detector.isFeedRequest(req) && !detector.isContainerPath() && !detector.isAttachmentDownload() && !detector.isGitRawOrAttachOrLFSPath() {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user