Replace CSRF cookie with CrossOriginProtection (#36183)

Removes the CSRF cookie in favor of
[`CrossOriginProtection`](https://pkg.go.dev/net/http#CrossOriginProtection)
which relies purely on HTTP headers.

Fixes: https://github.com/go-gitea/gitea/issues/11188
Fixes: https://github.com/go-gitea/gitea/issues/30333
Helps: https://github.com/go-gitea/gitea/issues/35107

TODOs:

- [x] Fix tests
- [ ] Ideally add tests to validates the protection

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwind
2025-12-25 11:33:34 +01:00
committed by GitHub
parent eddf875992
commit 42d294941c
207 changed files with 178 additions and 1196 deletions
+8 -39
View File
@@ -130,7 +130,6 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content
link, exists := htmlDoc.doc.Find("form.ui.form").Attr("action")
assert.True(t, exists, "The template has changed")
req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"title": title,
"content": content,
})
@@ -150,15 +149,12 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content
}
func testIssueDelete(t *testing.T, session *TestSession, issueURL string) {
req := NewRequestWithValues(t, "POST", path.Join(issueURL, "delete"), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
})
req := NewRequest(t, "POST", path.Join(issueURL, "delete"))
session.MakeRequest(t, req, http.StatusSeeOther)
}
func testIssueAssign(t *testing.T, session *TestSession, repoLink string, issueID, assigneeID int64) {
req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/assignee?issue_ids=%d", issueID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"id": strconv.FormatInt(assigneeID, 10),
"action": "", // empty action means assign
})
@@ -176,7 +172,6 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content,
commentCount := htmlDoc.doc.Find(".comment-list .comment .render-content").Length()
req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"content": content,
"status": status,
})
@@ -200,8 +195,7 @@ func testIssueAddComment(t *testing.T, session *TestSession, issueURL, content,
func testIssueChangeMilestone(t *testing.T, session *TestSession, repoLink string, issueID, milestoneID int64) {
req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/milestone?issue_ids=%d", issueID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"id": strconv.FormatInt(milestoneID, 10),
"id": strconv.FormatInt(milestoneID, 10),
})
resp := session.MakeRequest(t, req, http.StatusOK)
assert.Equal(t, `{"ok":true}`, strings.TrimSpace(resp.Body.String()))
@@ -219,21 +213,18 @@ func TestEditIssue(t *testing.T) {
issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
req := NewRequestWithValues(t, "POST", issueURL+"/content", map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"content": "modified content",
"context": fmt.Sprintf("/%s/%s", "user2", "repo1"),
})
session.MakeRequest(t, req, http.StatusOK)
req = NewRequestWithValues(t, "POST", issueURL+"/content", map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"content": "modified content",
"context": fmt.Sprintf("/%s/%s", "user2", "repo1"),
})
session.MakeRequest(t, req, http.StatusBadRequest)
req = NewRequestWithValues(t, "POST", issueURL+"/content", map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"content": "modified content",
"content_version": "1",
"context": fmt.Sprintf("/%s/%s", "user2", "repo1"),
@@ -267,13 +258,9 @@ func TestIssueCommentDelete(t *testing.T) {
assert.Equal(t, comment1, comment.Content)
// Using the ID of a comment that does not belong to the repository must fail
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user5", "repo4", commentID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
})
req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user5", "repo4", commentID))
session.MakeRequest(t, req, http.StatusNotFound)
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user2", "repo1", commentID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
})
req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user2", "repo1", commentID))
session.MakeRequest(t, req, http.StatusOK)
unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: commentID})
}
@@ -292,13 +279,11 @@ func TestIssueCommentUpdate(t *testing.T) {
// Using the ID of a comment that does not belong to the repository must fail
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user5", "repo4", commentID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"content": modifiedContent,
})
session.MakeRequest(t, req, http.StatusNotFound)
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"content": modifiedContent,
})
session.MakeRequest(t, req, http.StatusOK)
@@ -320,7 +305,6 @@ func TestIssueCommentUpdateSimultaneously(t *testing.T) {
modifiedContent := comment.Content + "MODIFIED"
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"content": modifiedContent,
})
session.MakeRequest(t, req, http.StatusOK)
@@ -328,13 +312,11 @@ func TestIssueCommentUpdateSimultaneously(t *testing.T) {
modifiedContent = comment.Content + "2"
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"content": modifiedContent,
})
session.MakeRequest(t, req, http.StatusBadRequest)
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
"_csrf": GetUserCSRFToken(t, session),
"content": modifiedContent,
"content_version": "1",
})
@@ -350,22 +332,15 @@ func TestIssueReaction(t *testing.T) {
session := loginUser(t, "user2")
issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
req := NewRequest(t, "GET", issueURL)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{
"_csrf": htmlDoc.GetCSRF(),
req := NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{
"content": "8ball",
})
session.MakeRequest(t, req, http.StatusInternalServerError)
req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/react"), map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"content": "eyes",
})
session.MakeRequest(t, req, http.StatusOK)
req = NewRequestWithValues(t, "POST", path.Join(issueURL, "/reactions/unreact"), map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"content": "eyes",
})
session.MakeRequest(t, req, http.StatusOK)
@@ -459,14 +434,8 @@ func testIssueWithBean(t *testing.T, user string, repoID int64, title, content s
func testIssueChangeInfo(t *testing.T, user, issueURL, info, value string) {
session := loginUser(t, user)
req := NewRequest(t, "GET", issueURL)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
req = NewRequestWithValues(t, "POST", path.Join(issueURL, info), map[string]string{
"_csrf": htmlDoc.GetCSRF(),
info: value,
req := NewRequestWithValues(t, "POST", path.Join(issueURL, info), map[string]string{
info: value,
})
_ = session.MakeRequest(t, req, http.StatusOK)
}
@@ -700,7 +669,7 @@ func TestUpdateIssueDeadline(t *testing.T) {
assert.Equal(t, api.StateOpen, issueBefore.State())
session := loginUser(t, owner.Name)
urlStr := fmt.Sprintf("%s/%s/issues/%d/deadline?_csrf=%s", owner.Name, repoBefore.Name, issueBefore.Index, GetUserCSRFToken(t, session))
urlStr := fmt.Sprintf("%s/%s/issues/%d/deadline", owner.Name, repoBefore.Name, issueBefore.Index)
req := NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": "2022-04-06"})
session.MakeRequest(t, req, http.StatusOK)