mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-03-25 12:33:03 -04:00
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Replace the anti-CSRF token with a [cross origin protection by Go](https://go.dev/doc/go1.25#nethttppkgnethttp) that uses a stateless way of verifying if a request was cross origin or not. This allows is to remove al lot of code and replace it with a few lines of code and we no longer have to hand roll this protection. The new protection uses indicators by the browser itself that indicate if the request is cross-origin, thus we no longer have to take care of ensuring the generated CSRF token is passed back to the server any request by the the browser will have send this indicator. Resolves forgejo/forgejo#3538 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9830 Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org> Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org> Co-authored-by: Gusted <postmaster@gusted.xyz> Co-committed-by: Gusted <postmaster@gusted.xyz>
481 lines
15 KiB
Go
481 lines
15 KiB
Go
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"path"
|
|
"testing"
|
|
|
|
repo_model "forgejo.org/models/repo"
|
|
"forgejo.org/models/unittest"
|
|
user_model "forgejo.org/models/user"
|
|
"forgejo.org/modules/git"
|
|
"forgejo.org/modules/json"
|
|
"forgejo.org/modules/translation"
|
|
app_context "forgejo.org/services/context"
|
|
"forgejo.org/tests"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCreateFileOnProtectedBranch(t *testing.T) {
|
|
onApplicationRun(t, func(t *testing.T, u *url.URL) {
|
|
session := loginUser(t, "user2")
|
|
|
|
// Change master branch to protected
|
|
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/edit", map[string]string{
|
|
"rule_name": "master",
|
|
"enable_push": "true",
|
|
})
|
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
|
// Check if master branch has been locked successfully
|
|
flashCookie := session.GetCookie(app_context.CookieNameFlash)
|
|
assert.NotNil(t, flashCookie)
|
|
assert.Equal(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522master%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
|
|
|
|
// Request editor page
|
|
req = NewRequest(t, "GET", "/user2/repo1/_new/master/")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
doc := NewHTMLParser(t, resp.Body)
|
|
lastCommit := doc.GetInputValueByName("last_commit")
|
|
assert.NotEmpty(t, lastCommit)
|
|
|
|
// Save new file to master branch
|
|
req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{
|
|
"last_commit": lastCommit,
|
|
"tree_path": "test.txt",
|
|
"content": "Content",
|
|
"commit_choice": "direct",
|
|
"commit_mail_id": "3",
|
|
})
|
|
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
// Check body for error message
|
|
assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch "master".")
|
|
|
|
// remove the protected branch
|
|
|
|
// Change master branch to protected
|
|
req = NewRequestWithValues(t, "POST", "/user2/repo1/settings/branches/1/delete", map[string]string{})
|
|
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
res := make(map[string]string)
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&res))
|
|
assert.Equal(t, "/user2/repo1/settings/branches", res["redirect"])
|
|
|
|
// Check if master branch has been locked successfully
|
|
flashCookie = session.GetCookie(app_context.CookieNameFlash)
|
|
assert.NotNil(t, flashCookie)
|
|
assert.Equal(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25221%2522%2Bfailed.", flashCookie.Value)
|
|
})
|
|
}
|
|
|
|
func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath, newContent string) *httptest.ResponseRecorder {
|
|
// Get to the 'edit this file' page
|
|
req := NewRequest(t, "GET", path.Join(user, repo, "_edit", branch, filePath))
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
lastCommit := htmlDoc.GetInputValueByName("last_commit")
|
|
assert.NotEmpty(t, lastCommit)
|
|
|
|
// Submit the edits
|
|
req = NewRequestWithValues(t, "POST", path.Join(user, repo, "_edit", branch, filePath),
|
|
map[string]string{
|
|
"last_commit": lastCommit,
|
|
"tree_path": filePath,
|
|
"content": newContent,
|
|
"commit_choice": "direct",
|
|
"commit_mail_id": "-1",
|
|
},
|
|
)
|
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
|
|
|
// Verify the change
|
|
req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", branch, filePath))
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, newContent, resp.Body.String())
|
|
|
|
return resp
|
|
}
|
|
|
|
func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, branch, targetBranch, filePath, newContent string) *httptest.ResponseRecorder {
|
|
// Get to the 'edit this file' page
|
|
req := NewRequest(t, "GET", path.Join(user, repo, "_edit", branch, filePath))
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
lastCommit := htmlDoc.GetInputValueByName("last_commit")
|
|
assert.NotEmpty(t, lastCommit)
|
|
|
|
// Submit the edits
|
|
req = NewRequestWithValues(t, "POST", path.Join(user, repo, "_edit", branch, filePath),
|
|
map[string]string{
|
|
"last_commit": lastCommit,
|
|
"tree_path": filePath,
|
|
"content": newContent,
|
|
"commit_choice": "commit-to-new-branch",
|
|
"new_branch_name": targetBranch,
|
|
"commit_mail_id": "-1",
|
|
},
|
|
)
|
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
|
|
|
// Verify the change
|
|
req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", targetBranch, filePath))
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
assert.Equal(t, newContent, resp.Body.String())
|
|
|
|
return resp
|
|
}
|
|
|
|
func TestEditFile(t *testing.T) {
|
|
onApplicationRun(t, func(t *testing.T, u *url.URL) {
|
|
session := loginUser(t, "user2")
|
|
testEditFile(t, session, "user2", "repo1", "master", "README.md", "Hello, World (Edited)\n")
|
|
})
|
|
}
|
|
|
|
func TestEditFileToNewBranch(t *testing.T) {
|
|
onApplicationRun(t, func(t *testing.T, u *url.URL) {
|
|
session := loginUser(t, "user2")
|
|
testEditFileToNewBranch(t, session, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n")
|
|
})
|
|
}
|
|
|
|
func TestEditorAddTranslation(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
session := loginUser(t, "user2")
|
|
req := NewRequest(t, "GET", "/user2/repo1/_new/master")
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
|
|
placeholder, ok := htmlDoc.Find("input[name='commit_summary']").Attr("placeholder")
|
|
assert.True(t, ok)
|
|
assert.Equal(t, `Add "<filename>"`, placeholder)
|
|
}
|
|
|
|
func TestCommitMail(t *testing.T) {
|
|
onApplicationRun(t, func(t *testing.T, _ *url.URL) {
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
// Require that the user has KeepEmailPrivate enabled, because it needs
|
|
// to be tested that even with this setting enabled, it will use the
|
|
// provided mail and not revert to the placeholder one.
|
|
assert.True(t, user.KeepEmailPrivate)
|
|
|
|
inactivatedMail := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 35, UID: user.ID})
|
|
assert.False(t, inactivatedMail.IsActivated)
|
|
|
|
otherEmail := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 1, IsActivated: true})
|
|
assert.NotEqual(t, otherEmail.UID, user.ID)
|
|
|
|
primaryEmail := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 3, UID: user.ID, IsActivated: true})
|
|
|
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
gitRepo, _ := git.OpenRepository(git.DefaultContext, repo1.RepoPath())
|
|
defer gitRepo.Close()
|
|
|
|
session := loginUser(t, user.Name)
|
|
|
|
getLastCommit := func(t *testing.T, link string, skipLastCommit bool) string {
|
|
t.Helper()
|
|
|
|
req := NewRequest(t, "GET", link)
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
lastCommit := htmlDoc.GetInputValueByName("last_commit")
|
|
if !skipLastCommit {
|
|
assert.NotEmpty(t, lastCommit)
|
|
}
|
|
|
|
return lastCommit
|
|
}
|
|
|
|
type caseOpts struct {
|
|
link string
|
|
fileName string
|
|
base map[string]string
|
|
skipLastCommit bool
|
|
}
|
|
|
|
// Base2 should have different content, so we can test two 'correct' operations
|
|
// without the second becoming a noop because no content was changed. If needed,
|
|
// link2 can point to a new file that's used with base2.
|
|
assertCase := func(t *testing.T, case1, case2 caseOpts) {
|
|
t.Helper()
|
|
|
|
t.Run("Not activated", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
lastCommit := getLastCommit(t, case1.link, case1.skipLastCommit)
|
|
baseCopy := case1.base
|
|
baseCopy["last_commit"] = lastCommit
|
|
baseCopy["commit_mail_id"] = fmt.Sprintf("%d", inactivatedMail.ID)
|
|
|
|
req := NewRequestWithValues(t, "POST", case1.link, baseCopy)
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
assert.Contains(t,
|
|
htmlDoc.doc.Find(".ui.negative.message").Text(),
|
|
translation.NewLocale("en-US").Tr("repo.editor.invalid_commit_mail"),
|
|
)
|
|
})
|
|
|
|
t.Run("Not belong to user", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
lastCommit := getLastCommit(t, case1.link, case1.skipLastCommit)
|
|
baseCopy := case1.base
|
|
baseCopy["last_commit"] = lastCommit
|
|
baseCopy["commit_mail_id"] = fmt.Sprintf("%d", otherEmail.ID)
|
|
|
|
req := NewRequestWithValues(t, "POST", case1.link, baseCopy)
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
assert.Contains(t,
|
|
htmlDoc.doc.Find(".ui.negative.message").Text(),
|
|
translation.NewLocale("en-US").Tr("repo.editor.invalid_commit_mail"),
|
|
)
|
|
})
|
|
|
|
t.Run("Placeholder mail", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
lastCommit := getLastCommit(t, case1.link, case1.skipLastCommit)
|
|
baseCopy := case1.base
|
|
baseCopy["last_commit"] = lastCommit
|
|
baseCopy["commit_mail_id"] = "-1"
|
|
|
|
req := NewRequestWithValues(t, "POST", case1.link, baseCopy)
|
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
|
if !case2.skipLastCommit {
|
|
newlastCommit := getLastCommit(t, case1.link, false)
|
|
assert.NotEqual(t, newlastCommit, lastCommit)
|
|
}
|
|
|
|
commit, err := gitRepo.GetCommitByPath(case1.fileName)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "user2", commit.Author.Name)
|
|
assert.Equal(t, "user2@noreply.example.org", commit.Author.Email)
|
|
assert.Equal(t, "user2", commit.Committer.Name)
|
|
assert.Equal(t, "user2@noreply.example.org", commit.Committer.Email)
|
|
})
|
|
|
|
t.Run("Normal", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
lastCommit := getLastCommit(t, case2.link, case2.skipLastCommit)
|
|
baseCopy := case2.base
|
|
baseCopy["last_commit"] = lastCommit
|
|
baseCopy["commit_mail_id"] = fmt.Sprintf("%d", primaryEmail.ID)
|
|
|
|
req := NewRequestWithValues(t, "POST", case2.link, baseCopy)
|
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
|
if !case2.skipLastCommit {
|
|
newlastCommit := getLastCommit(t, case2.link, false)
|
|
assert.NotEqual(t, newlastCommit, lastCommit)
|
|
}
|
|
|
|
commit, err := gitRepo.GetCommitByPath(case2.fileName)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "user2", commit.Author.Name)
|
|
assert.Equal(t, primaryEmail.Email, commit.Author.Email)
|
|
assert.Equal(t, "user2", commit.Committer.Name)
|
|
assert.Equal(t, primaryEmail.Email, commit.Committer.Email)
|
|
})
|
|
}
|
|
|
|
t.Run("New", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
assertCase(t, caseOpts{
|
|
fileName: "new_file",
|
|
link: "user2/repo1/_new/master",
|
|
base: map[string]string{
|
|
"tree_path": "new_file",
|
|
"content": "new_content",
|
|
"commit_choice": "direct",
|
|
},
|
|
}, caseOpts{
|
|
fileName: "new_file_2",
|
|
link: "user2/repo1/_new/master",
|
|
base: map[string]string{
|
|
"tree_path": "new_file_2",
|
|
"content": "new_content",
|
|
"commit_choice": "direct",
|
|
},
|
|
},
|
|
)
|
|
})
|
|
|
|
t.Run("Edit", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
assertCase(t, caseOpts{
|
|
fileName: "README.md",
|
|
link: "user2/repo1/_edit/master/README.md",
|
|
base: map[string]string{
|
|
"tree_path": "README.md",
|
|
"content": "Edit content",
|
|
"commit_choice": "direct",
|
|
},
|
|
}, caseOpts{
|
|
fileName: "README.md",
|
|
link: "user2/repo1/_edit/master/README.md",
|
|
base: map[string]string{
|
|
"tree_path": "README.md",
|
|
"content": "Other content",
|
|
"commit_choice": "direct",
|
|
},
|
|
},
|
|
)
|
|
})
|
|
|
|
t.Run("Delete", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
assertCase(t, caseOpts{
|
|
fileName: "new_file",
|
|
link: "user2/repo1/_delete/master/new_file",
|
|
base: map[string]string{
|
|
"commit_choice": "direct",
|
|
},
|
|
}, caseOpts{
|
|
fileName: "new_file_2",
|
|
link: "user2/repo1/_delete/master/new_file_2",
|
|
base: map[string]string{
|
|
"commit_choice": "direct",
|
|
},
|
|
},
|
|
)
|
|
})
|
|
|
|
t.Run("Upload", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
// Upload two separate times, so we have two different 'uploads' that can
|
|
// be used independently of each other.
|
|
uploadFile := func(t *testing.T, name, content string) string {
|
|
t.Helper()
|
|
|
|
body := &bytes.Buffer{}
|
|
mpForm := multipart.NewWriter(body)
|
|
|
|
file, err := mpForm.CreateFormFile("file", name)
|
|
require.NoError(t, err)
|
|
|
|
io.Copy(file, bytes.NewBufferString(content))
|
|
require.NoError(t, mpForm.Close())
|
|
|
|
req := NewRequestWithBody(t, "POST", "/user2/repo1/upload-file", body)
|
|
req.Header.Add("Content-Type", mpForm.FormDataContentType())
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
respMap := map[string]string{}
|
|
DecodeJSON(t, resp, &respMap)
|
|
return respMap["uuid"]
|
|
}
|
|
|
|
file1UUID := uploadFile(t, "upload_file_1", "Uploaded a file!")
|
|
file2UUID := uploadFile(t, "upload_file_2", "Uploaded another file!")
|
|
file1UUIDFullpathKey := fmt.Sprintf("files_fullpath[%s]", file1UUID)
|
|
file2UUIDFullpathKey := fmt.Sprintf("files_fullpath[%s]", file2UUID)
|
|
|
|
assertCase(t, caseOpts{
|
|
fileName: "upload_file_1",
|
|
link: "user2/repo1/_upload/master",
|
|
skipLastCommit: true,
|
|
base: map[string]string{
|
|
"commit_choice": "direct",
|
|
"files": file1UUID,
|
|
file1UUIDFullpathKey: "upload_file_1",
|
|
},
|
|
}, caseOpts{
|
|
fileName: "upload_file_2",
|
|
link: "user2/repo1/_upload/master",
|
|
skipLastCommit: true,
|
|
base: map[string]string{
|
|
"commit_choice": "direct",
|
|
"files": file2UUID,
|
|
file2UUIDFullpathKey: "upload_file_2",
|
|
},
|
|
},
|
|
)
|
|
})
|
|
|
|
t.Run("Apply patch", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
assertCase(t, caseOpts{
|
|
fileName: "diff-file-1.txt",
|
|
link: "user2/repo1/_diffpatch/master",
|
|
base: map[string]string{
|
|
"tree_path": "patch",
|
|
"commit_choice": "direct",
|
|
"content": `diff --git a/diff-file-1.txt b/diff-file-1.txt
|
|
new file mode 100644
|
|
index 0000000000..50fcd26d6c
|
|
--- /dev/null
|
|
+++ b/diff-file-1.txt
|
|
@@ -0,0 +1 @@
|
|
+File 1
|
|
`,
|
|
},
|
|
}, caseOpts{
|
|
fileName: "diff-file-2.txt",
|
|
link: "user2/repo1/_diffpatch/master",
|
|
base: map[string]string{
|
|
"tree_path": "patch",
|
|
"commit_choice": "direct",
|
|
"content": `diff --git a/diff-file-2.txt b/diff-file-2.txt
|
|
new file mode 100644
|
|
index 0000000000..4475433e27
|
|
--- /dev/null
|
|
+++ b/diff-file-2.txt
|
|
@@ -0,0 +1 @@
|
|
+File 2
|
|
`,
|
|
},
|
|
})
|
|
})
|
|
|
|
t.Run("Cherry pick", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
commitID1, err := gitRepo.GetCommitByPath("diff-file-1.txt")
|
|
require.NoError(t, err)
|
|
commitID2, err := gitRepo.GetCommitByPath("diff-file-2.txt")
|
|
require.NoError(t, err)
|
|
|
|
assertCase(t, caseOpts{
|
|
fileName: "diff-file-1.txt",
|
|
link: "user2/repo1/_cherrypick/" + commitID1.ID.String() + "/master",
|
|
base: map[string]string{
|
|
"commit_choice": "direct",
|
|
"revert": "true",
|
|
},
|
|
}, caseOpts{
|
|
fileName: "diff-file-2.txt",
|
|
link: "user2/repo1/_cherrypick/" + commitID2.ID.String() + "/master",
|
|
base: map[string]string{
|
|
"commit_choice": "direct",
|
|
"revert": "true",
|
|
},
|
|
})
|
|
})
|
|
})
|
|
}
|