[SHARED] 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 bf679b24dd)
(cherry picked from commit 1963085dd9)
(cherry picked from commit fb94095d19)
(cherry picked from commit e1d1e46afe)
(cherry picked from commit 93993029e4)
(cherry picked from commit df3b058179)
(cherry picked from commit 8ccc6b9cba)
(cherry picked from commit 9fbe28fca3)
(cherry picked from commit 4ef2be6dc7)

https://codeberg.org/forgejo/forgejo/pulls/1873
  Moved test from repo_test.go to forgejo_confirmation_repo_test.go to
  avoid conflicts.
This commit is contained in:
Gusted 2023-09-13 00:53:03 +02:00 committed by Earl Warren
parent d8af308820
commit 83cae67aa3
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
4 changed files with 163 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

@ -0,0 +1,151 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"net/http/httptest"
"testing"
gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
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.")
})
})
}

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)
} }