[GITEA] Make confirmation clearer for dangerous actions

- Currently the confirmation for dangerous actions such as transferring
the repository or deleting it only requires the user to ~~copy paste~~
type the repository name.
- This can be problematic when the user has a fork or another repository
with the same name as an organization's repository, and the confirmation
doesn't make clear that it could be deleting the wrong repository. While
it's mentioned in the dialog, it's better to be on the safe side and
also add the owner's name to be an element that has to be typed for
these dangerous actions.
- Added integration tests.

(cherry picked from commit bf679b24dd23c9ed586b9439e293bbd27cc89232)
(cherry picked from commit 1963085dd9d1521b7a4aa8558d409bd1a9f2e1da)
(cherry picked from commit fb94095d1992c3e47f03e0fccc98a90707a5271b)
(cherry picked from commit e1d1e46afee6891becdb6ccd027fc66843b56db9)
(cherry picked from commit 64e38b3363b77271a3155acfafc7c8f4753c441e)
(cherry picked from commit 0c2a78fa4803d91377d639e6f31a5d2f593b0778)
(cherry picked from commit e8aa66f1dd5067c563a8da9fc0322101b87436d2)
(cherry picked from commit 55b5aa023939c95aa69f6da9b5e3dddf7e27f822)
(cherry picked from commit a448744b7bf7c663c148b315b5ac2485da466624)
This commit is contained in:
Gusted 2023-09-13 00:53:03 +02:00 committed by Earl Warren
parent 605f3a21a4
commit 12ef8a7937
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
4 changed files with 150 additions and 11 deletions

View file

@ -681,7 +681,7 @@ func SettingsPost(ctx *context.Context) {
ctx.Error(http.StatusNotFound) ctx.Error(http.StatusNotFound)
return return
} }
if repo.Name != form.RepoName { if repo.FullName() != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return return
} }
@ -712,7 +712,7 @@ func SettingsPost(ctx *context.Context) {
ctx.ServerError("Convert Fork", err) ctx.ServerError("Convert Fork", err)
return return
} }
if repo.Name != form.RepoName { if repo.FullName() != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return return
} }
@ -745,7 +745,7 @@ func SettingsPost(ctx *context.Context) {
ctx.Error(http.StatusNotFound) ctx.Error(http.StatusNotFound)
return return
} }
if repo.Name != form.RepoName { if repo.FullName() != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return return
} }
@ -827,7 +827,7 @@ func SettingsPost(ctx *context.Context) {
ctx.Error(http.StatusNotFound) ctx.Error(http.StatusNotFound)
return return
} }
if repo.Name != form.RepoName { if repo.FullName() != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return return
} }
@ -851,7 +851,7 @@ func SettingsPost(ctx *context.Context) {
ctx.Error(http.StatusNotFound) ctx.Error(http.StatusNotFound)
return return
} }
if repo.Name != form.RepoName { if repo.FullName() != form.RepoName {
ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil)
return return
} }

View file

@ -816,7 +816,7 @@
<div class="field"> <div class="field">
<label> <label>
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}} {{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="text red">{{.Repository.Name}}</span> <span class="text red">{{.Repository.FullName}}</span>
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">
@ -847,7 +847,7 @@
<div class="field"> <div class="field">
<label> <label>
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}} {{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="text red">{{.Repository.Name}}</span> <span class="text red">{{.Repository.FullName}}</span>
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">
@ -879,7 +879,7 @@
<div class="field"> <div class="field">
<label> <label>
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}} {{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="text red">{{.Repository.Name}}</span> <span class="text red">{{.Repository.FullName}}</span>
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">
@ -917,7 +917,7 @@
<div class="field"> <div class="field">
<label> <label>
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}} {{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="text red">{{.Repository.Name}}</span> <span class="text red">{{.Repository.FullName}}</span>
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">
@ -949,7 +949,7 @@
<div class="field"> <div class="field">
<label> <label>
{{ctx.Locale.Tr "repo.settings.transfer_form_title"}} {{ctx.Locale.Tr "repo.settings.transfer_form_title"}}
<span class="text red">{{.Repository.Name}}</span> <span class="text red">{{.Repository.FullName}}</span>
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">

View file

@ -4,6 +4,7 @@
package integration package integration
import ( import (
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -129,7 +130,7 @@ func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoNam
req = NewRequestWithValues(t, "POST", relURL+"?action=delete", map[string]string{ req = NewRequestWithValues(t, "POST", relURL+"?action=delete", map[string]string{
"_csrf": htmlDoc.GetCSRF(), "_csrf": htmlDoc.GetCSRF(),
"repo_name": repoName, "repo_name": fmt.Sprintf("%s/%s", ownerName, repoName),
}) })
session.MakeRequest(t, req, http.StatusSeeOther) session.MakeRequest(t, req, http.StatusSeeOther)
} }

View file

@ -6,12 +6,15 @@ package integration
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest"
"path" "path"
"strings" "strings"
"testing" "testing"
"time" "time"
gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
@ -448,3 +451,138 @@ func TestGeneratedSourceLink(t *testing.T) {
assert.Equal(t, "/user27/repo49/src/commit/aacbdfe9e1c4b47f60abe81849045fa4e96f1d75/test/test.txt", dataURL) assert.Equal(t, "/user27/repo49/src/commit/aacbdfe9e1c4b47f60abe81849045fa4e96f1d75/test/test.txt", dataURL)
}) })
} }
func TestDangerZoneConfirmation(t *testing.T) {
defer tests.PrepareTestEnv(t)()
mustInvalidRepoName := func(resp *httptest.ResponseRecorder) {
t.Helper()
htmlDoc := NewHTMLParser(t, resp.Body)
assert.Contains(t,
htmlDoc.doc.Find(".ui.negative.message").Text(),
translation.NewLocale("en-US").Tr("form.enterred_invalid_repo_name"),
)
}
t.Run("Transfer ownership", func(t *testing.T) {
session := loginUser(t, "user2")
t.Run("Fail", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user2/repo1/settings"),
"action": "transfer",
"repo_name": "repo1",
"new_owner_name": "user1",
})
resp := session.MakeRequest(t, req, http.StatusOK)
mustInvalidRepoName(resp)
})
t.Run("Pass", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user2/repo1/settings"),
"action": "transfer",
"repo_name": "user2/repo1",
"new_owner_name": "user1",
})
session.MakeRequest(t, req, http.StatusSeeOther)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
assert.NotNil(t, flashCookie)
assert.EqualValues(t, flashCookie.Value, "success%3DThis%2Brepository%2Bhas%2Bbeen%2Bmarked%2Bfor%2Btransfer%2Band%2Bawaits%2Bconfirmation%2Bfrom%2B%2522User%2BOne%2522")
})
})
t.Run("Convert fork", func(t *testing.T) {
session := loginUser(t, "user20")
t.Run("Fail", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithValues(t, "POST", "/user20/big_test_public_fork_7/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user20/big_test_public_fork_7/settings"),
"action": "convert_fork",
"repo_name": "big_test_public_fork_7",
})
resp := session.MakeRequest(t, req, http.StatusOK)
mustInvalidRepoName(resp)
})
t.Run("Pass", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithValues(t, "POST", "/user20/big_test_public_fork_7/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user20/big_test_public_fork_7/settings"),
"action": "convert_fork",
"repo_name": "user20/big_test_public_fork_7",
})
session.MakeRequest(t, req, http.StatusSeeOther)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
assert.NotNil(t, flashCookie)
assert.EqualValues(t, flashCookie.Value, "success%3DThe%2Bfork%2Bhas%2Bbeen%2Bconverted%2Binto%2Ba%2Bregular%2Brepository.")
})
})
t.Run("Delete wiki", func(t *testing.T) {
session := loginUser(t, "user2")
t.Run("Fail", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user2/repo1/settings"),
"action": "delete-wiki",
"repo_name": "repo1",
})
resp := session.MakeRequest(t, req, http.StatusOK)
mustInvalidRepoName(resp)
})
t.Run("Pass", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user2/repo1/settings"),
"action": "delete-wiki",
"repo_name": "user2/repo1",
})
session.MakeRequest(t, req, http.StatusSeeOther)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
assert.NotNil(t, flashCookie)
assert.EqualValues(t, flashCookie.Value, "success%3DThe%2Brepository%2Bwiki%2Bdata%2Bhas%2Bbeen%2Bdeleted.")
})
})
t.Run("Delete", func(t *testing.T) {
session := loginUser(t, "user2")
t.Run("Fail", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user2/repo1/settings"),
"action": "delete",
"repo_name": "repo1",
})
resp := session.MakeRequest(t, req, http.StatusOK)
mustInvalidRepoName(resp)
})
t.Run("Pass", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequestWithValues(t, "POST", "/user2/repo1/settings", map[string]string{
"_csrf": GetCSRF(t, session, "/user2/repo1/settings"),
"action": "delete",
"repo_name": "user2/repo1",
})
session.MakeRequest(t, req, http.StatusSeeOther)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
assert.NotNil(t, flashCookie)
assert.EqualValues(t, flashCookie.Value, "success%3DThe%2Brepository%2Bhas%2Bbeen%2Bdeleted.")
})
})
}