From 0e283c5485bf94bf4baf0972a694ebb7b83ee991 Mon Sep 17 00:00:00 2001 From: AverageHelper Date: Mon, 8 Jun 2026 15:17:51 +0200 Subject: [PATCH] feat: apply service.VALID_SITE_URL_SCHEMES to apply to repository and organization profiles (#12962) Turns out this was a one-line fix for each affected field (change the binding from `ValidUrl` to `ValidSiteUrl`), but the tests are rather verbose. The tests are, however, each a simple flow of Create Thing > Try HTTP Website > Try Different Website (notice failure) > Try Different Website With New Config (notice success). I wrote this PR by adding failing tests first, then making the change, for each affected field. Not sure if this should be "feat:" or "fix:" tbh. I figured "fix:" for this PR since IMO the expected behavior is for `VALID_SITE_URL_SCHEMES` to apply in each of these cases, not only for user profiles via the UI form. (Later changed to "feat:" at @limiting-factor's suggestion, based on the observation that this change extends documented behavior.) This PR deals with the server-side validation only. #12991 covers client-side validation (deriving a `pattern` attribute from `VALID_SITE_URL_SCHEMES`, etc.) Closes #5519 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12962 Reviewed-by: limiting-factor --- custom/conf/app.example.ini | 2 +- modules/structs/admin_user.go | 2 +- modules/structs/org.go | 4 +- modules/structs/user.go | 2 +- services/forms/admin.go | 2 +- services/forms/org.go | 2 +- services/forms/repo_form.go | 2 +- tests/integration/admin_user_test.go | 61 ++++++++++++ tests/integration/api_admin_test.go | 61 ++++++++++++ tests/integration/api_org_test.go | 119 ++++++++++++++++++++++++ tests/integration/org_settings_test.go | 51 ++++++++++ tests/integration/repo_settings_test.go | 52 +++++++++++ tests/integration/user_settings_test.go | 43 +++++++++ tests/integration/user_test.go | 59 ++++++++++++ 14 files changed, 454 insertions(+), 8 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index ecd64495b9..0a6c80f0a4 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -915,7 +915,7 @@ LEVEL = Info ;; ;; Minimum amount of time a user must exist before comments are kept when the user is deleted. ;USER_DELETE_WITH_COMMENTS_MAX_TIME = 0 -;; Valid site url schemes for user profiles +;; Valid site url schemes for user, organization, or repository profiles ;VALID_SITE_URL_SCHEMES=http,https ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go index a7bd5643e9..d1daede280 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -37,7 +37,7 @@ type EditUserOption struct { FullName *string `json:"full_name" binding:"MaxSize(100)"` Password string `json:"password" binding:"MaxSize(255)"` MustChangePassword *bool `json:"must_change_password"` - Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"` + Website *string `json:"website" binding:"OmitEmpty;ValidSiteUrl;MaxSize(255)"` Location *string `json:"location" binding:"MaxSize(50)"` Pronouns *string `json:"pronouns" binding:"MaxSize(50)"` Description *string `json:"description" binding:"MaxSize(255)"` diff --git a/modules/structs/org.go b/modules/structs/org.go index a0b050326b..c5080a165e 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -38,7 +38,7 @@ type CreateOrgOption struct { FullName string `json:"full_name" binding:"MaxSize(100)"` Email string `json:"email" binding:"MaxSize(255)"` Description string `json:"description" binding:"MaxSize(255)"` - Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` + Website string `json:"website" binding:"ValidSiteUrl;MaxSize(255)"` Location string `json:"location" binding:"MaxSize(50)"` // possible values are `public` (default), `limited` or `private` // enum: ["public", "limited", "private"] @@ -53,7 +53,7 @@ type EditOrgOption struct { FullName string `json:"full_name" binding:"MaxSize(100)"` Email *string `json:"email" binding:"MaxSize(255)"` Description string `json:"description" binding:"MaxSize(255)"` - Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` + Website string `json:"website" binding:"ValidSiteUrl;MaxSize(255)"` Location string `json:"location" binding:"MaxSize(50)"` // possible values are `public`, `limited` or `private` // enum: ["public", "limited", "private"] diff --git a/modules/structs/user.go b/modules/structs/user.go index e0767071d0..b4f3d67a11 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -92,7 +92,7 @@ type UserSettings struct { // swagger:model type UserSettingsOptions struct { FullName *string `json:"full_name" binding:"MaxSize(100)"` - Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"` + Website *string `json:"website" binding:"OmitEmpty;ValidSiteUrl;MaxSize(255)"` Description *string `json:"description" binding:"MaxSize(255)"` Location *string `json:"location" binding:"MaxSize(50)"` Pronouns *string `json:"pronouns" binding:"MaxSize(50)"` diff --git a/services/forms/admin.go b/services/forms/admin.go index dc2cc9c909..d2dd16c732 100644 --- a/services/forms/admin.go +++ b/services/forms/admin.go @@ -39,7 +39,7 @@ type AdminEditUserForm struct { FullName string `binding:"MaxSize(100)"` Email string `binding:"Required;EmailForAdmin;MaxSize(254)"` Password string `binding:"MaxSize(255)"` - Website string `binding:"ValidUrl;MaxSize(255)"` + Website string `binding:"ValidSiteUrl;MaxSize(255)"` Location string `binding:"MaxSize(50)"` Language string `binding:"MaxSize(5)"` Pronouns string `binding:"MaxSize(50)"` diff --git a/services/forms/org.go b/services/forms/org.go index a6e4e72c4a..cb32016c3d 100644 --- a/services/forms/org.go +++ b/services/forms/org.go @@ -40,7 +40,7 @@ type UpdateOrgSettingForm struct { FullName string `binding:"MaxSize(100)"` Email string `binding:"MaxSize(255)"` Description string `binding:"MaxSize(255)"` - Website string `binding:"ValidUrl;MaxSize(255)"` + Website string `binding:"ValidSiteUrl;MaxSize(255)"` Location string `binding:"MaxSize(50)"` Visibility structs.VisibleType MaxRepoCreation int diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 4d2c592eb1..72c03cb193 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -130,7 +130,7 @@ func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, err type RepoSettingForm struct { RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` Description string `binding:"MaxSize(2048)"` - Website string `binding:"ValidUrl;MaxSize(1024)"` + Website string `binding:"ValidSiteUrl;MaxSize(1024)"` FollowingRepos string Interval string MirrorAddress string diff --git a/tests/integration/admin_user_test.go b/tests/integration/admin_user_test.go index 611a0e89c6..9350168c8f 100644 --- a/tests/integration/admin_user_test.go +++ b/tests/integration/admin_user_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "strconv" + "strings" "testing" "time" @@ -15,9 +16,12 @@ import ( issues_model "forgejo.org/models/issues" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" + "forgejo.org/modules/setting" api "forgejo.org/modules/structs" + "forgejo.org/modules/test" "forgejo.org/modules/timeutil" "forgejo.org/tests" + "forgejo.org/tests/forgery" "github.com/stretchr/testify/assert" ) @@ -128,6 +132,63 @@ func TestAdminEditUserHideEmail(t *testing.T) { htmlDoc.AssertElement(t, `input[name="hide_email"][checked]`, true) } +func TestAdminEditUserWebsite(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + user := forgery.CreateUser(t, nil) + urlStr := fmt.Sprintf("/admin/users/%d/edit", user.ID) + + t.Run("an HTTPS website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should work + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "user_name": user.Name, + "login_name": user.LoginName, + "login_type": "0-0", + "email": user.Email, + "website": "https://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assertHasFlashMessages(t, resp, "success") + }) + + t.Run("an H3 website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should not work + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "user_name": user.Name, + "login_name": user.LoginName, + "login_type": "0-0", + "email": user.Email, + "website": "h3://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + flash := doc.Find("#flash-message").Text() + assert.Equal(t, `Website"Url" is not a valid URL.`, strings.TrimSpace(flash)) + }) + + t.Run("an H3 website under custom schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)() + setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3") + + // changing website should work + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "user_name": user.Name, + "login_name": user.LoginName, + "login_type": "0-0", + "email": user.Email, + "website": "h3://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assertHasFlashMessages(t, resp, "success") + }) +} + func testSuccessfulEdit(t *testing.T, formData user_model.User) { makeRequest(t, formData, http.StatusSeeOther) } diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index e3cab38d9d..4fc4bfabd5 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -16,7 +16,9 @@ import ( "forgejo.org/modules/json" "forgejo.org/modules/setting" api "forgejo.org/modules/structs" + "forgejo.org/modules/test" "forgejo.org/tests" + "forgejo.org/tests/forgery" "github.com/gobwas/glob" "github.com/stretchr/testify/assert" @@ -512,6 +514,65 @@ func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) { MakeRequest(t, req, http.StatusOK) } +func TestAPIUser_Website(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) + user := forgery.CreateUser(t, nil) + urlStr := fmt.Sprintf("/api/v1/admin/users/%s", user.Name) + + t.Run("an HTTPS website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should work + website := "https://codeberg.org" + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditUserOption{ + Website: &website, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiUser api.User + DecodeJSON(t, resp, &apiUser) + + assert.Equal(t, website, apiUser.Website) + }) + + t.Run("an H3 website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should not work + website := "h3://codeberg.org" + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditUserOption{ + Website: &website, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusUnprocessableEntity) + + var apiErr api.APIError + DecodeJSON(t, resp, &apiErr) + + assert.Equal(t, "[Website]: Url", apiErr.Message) + }) + + t.Run("an H3 website under custom schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)() + setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3") + + // changing website should work + website := "h3://codeberg.org" + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditUserOption{ + Website: &website, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiUser api.User + DecodeJSON(t, resp, &apiUser) + + assert.Equal(t, website, apiUser.Website) + }) +} + func TestAPIAdminListUserEmails(t *testing.T) { defer tests.PrepareTestEnv(t)() diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index 98532f6659..4791ccba93 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -100,6 +100,67 @@ func TestAPIOrgCreate(t *testing.T) { assert.Equal(t, "user1", users[0].UserName) } +func TestAPIOrgCreateWithWebsite(t *testing.T) { + defer tests.PrepareTestEnv(t)() + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) + + t.Run("an HTTPS website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // setting website should work + org := api.CreateOrgOption{ + UserName: "user1_org", + FullName: "User1's organization", + Website: "https://codeberg.org", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) + + assert.Equal(t, org.Website, apiOrg.Website) + }) + + t.Run("an H3 website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // setting website should not work + org := api.CreateOrgOption{ + UserName: "user1_org_2", + FullName: "User1's second organization", + Website: "h3://codeberg.org", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusUnprocessableEntity) + + var apiErr api.APIError + DecodeJSON(t, resp, &apiErr) + + assert.Equal(t, "[Website]: Url", apiErr.Message) + }) + + t.Run("an H3 website under custom schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)() + setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3") + + // setting website should work + org := api.CreateOrgOption{ + UserName: "user1_org_2", + FullName: "User1's second organization", + Website: "h3://codeberg.org", + } + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &org).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) + + assert.Equal(t, org.Website, apiOrg.Website) + }) +} + func TestAPIOrgRename(t *testing.T) { defer tests.PrepareTestEnv(t)() token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) @@ -150,6 +211,64 @@ func TestAPIOrgEdit(t *testing.T) { assert.Equal(t, org.Visibility, apiOrg.Visibility) } +func TestAPIOrgEditWebsite(t *testing.T) { + defer tests.PrepareTestEnv(t)() + const orgName = "org3" + urlStr := fmt.Sprintf("/api/v1/orgs/%s", orgName) + session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + + t.Run("an HTTPS website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should work + org := api.EditOrgOption{ + Website: "https://codeberg.org", + } + req := NewRequestWithJSON(t, "PATCH", urlStr, &org).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) + + assert.Equal(t, org.Website, apiOrg.Website) + }) + + t.Run("an H3 website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should not work + org := api.EditOrgOption{ + Website: "h3://codeberg.org", + } + req := NewRequestWithJSON(t, "PATCH", urlStr, &org).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusUnprocessableEntity) + + var apiErr api.APIError + DecodeJSON(t, resp, &apiErr) + + assert.Equal(t, "[Website]: Url", apiErr.Message) + }) + + t.Run("an H3 website under custom schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)() + setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3") + + // changing website should work + org := api.EditOrgOption{ + Website: "h3://codeberg.org", + } + req := NewRequestWithJSON(t, "PATCH", urlStr, &org).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) + + assert.Equal(t, org.Website, apiOrg.Website) + }) +} + func TestAPIOrgEditBadVisibility(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") diff --git a/tests/integration/org_settings_test.go b/tests/integration/org_settings_test.go index 5442cdbbcc..95b736b095 100644 --- a/tests/integration/org_settings_test.go +++ b/tests/integration/org_settings_test.go @@ -6,10 +6,13 @@ package integration import ( "fmt" "net/http" + "strings" "testing" auth_model "forgejo.org/models/auth" + "forgejo.org/modules/setting" api "forgejo.org/modules/structs" + "forgejo.org/modules/test" "forgejo.org/tests" "github.com/stretchr/testify/assert" @@ -94,3 +97,51 @@ func TestOrgSettingsChangeEmail(t *testing.T) { assert.Empty(t, org.Email) }) } + +func TestOrgSettingsUpdateWebsite(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + const orgName = "org3" + urlStr := fmt.Sprintf("/org/%s/settings", orgName) + session := loginUser(t, "user1") + + t.Run("an HTTPS website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should work + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "name": orgName, + "website": "https://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assertHasFlashMessages(t, resp, "success") + }) + + t.Run("an H3 website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should not work + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "name": orgName, + "website": "h3://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + flash := doc.Find("#flash-message").Text() + assert.Equal(t, `Website"Url" is not a valid URL.`, strings.TrimSpace(flash)) + }) + + t.Run("an H3 website under custom schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)() + setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3") + + // changing website should work + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "name": orgName, + "website": "h3://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assertHasFlashMessages(t, resp, "success") + }) +} diff --git a/tests/integration/repo_settings_test.go b/tests/integration/repo_settings_test.go index 96304f07ae..aa3faaa54c 100644 --- a/tests/integration/repo_settings_test.go +++ b/tests/integration/repo_settings_test.go @@ -6,6 +6,7 @@ package integration import ( "fmt" "net/http" + "strings" "testing" "forgejo.org/models/db" @@ -16,6 +17,7 @@ import ( "forgejo.org/models/unittest" "forgejo.org/modules/optional" "forgejo.org/modules/setting" + "forgejo.org/modules/test" app_context "forgejo.org/services/context" repo_service "forgejo.org/services/repository" user_service "forgejo.org/services/user" @@ -36,6 +38,56 @@ func TestRepoSettingsUnits(t *testing.T) { session.MakeRequest(t, req, http.StatusOK) } +func TestRepoSettingsUpdateWebsite(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo := forgery.CreateRepository(t, nil, nil) + session := loginUser(t, repo.Owner.Name) + urlStr := fmt.Sprintf("%s/settings", repo.Link()) + + t.Run("an HTTPS website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should work + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "action": "update", + "repo_name": repo.Name, + "website": "https://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assertHasFlashMessages(t, resp, "success") + }) + + t.Run("an H3 website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should not work + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "action": "update", + "repo_name": repo.Name, + "website": "h3://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + flash := doc.Find("#flash-message").Text() + assert.Equal(t, `Website"Url" is not a valid URL.`, strings.TrimSpace(flash)) + }) + + t.Run("an H3 website under custom schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)() + setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3") + + // changing website should work + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "action": "update", + "repo_name": repo.Name, + "website": "h3://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assertHasFlashMessages(t, resp, "success") + }) +} + func TestRepoSettingsAdminOptions(t *testing.T) { defer tests.PrepareTestEnv(t)() diff --git a/tests/integration/user_settings_test.go b/tests/integration/user_settings_test.go index c16607a8fb..401f048cff 100644 --- a/tests/integration/user_settings_test.go +++ b/tests/integration/user_settings_test.go @@ -5,6 +5,7 @@ package integration import ( "net/http" + "strings" "testing" "forgejo.org/models/auth" @@ -133,6 +134,48 @@ func TestUserSettingsDelete(t *testing.T) { }) } +func TestUserSettingsUpdateWebsite(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + + t.Run("an HTTPS website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should work + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "website": "https://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assertHasFlashMessages(t, resp, "success") + }) + + t.Run("an H3 website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should not work + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "website": "h3://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + flash := doc.Find("#flash-message").Text() + assert.Equal(t, `Website"Url" is not a valid URL.`, strings.TrimSpace(flash)) + }) + + t.Run("an H3 website under custom schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)() + setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3") + + // changing website should work + req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{ + "website": "h3://codeberg.org", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assertHasFlashMessages(t, resp, "success") + }) +} + func TestUserRename(t *testing.T) { defer unittest.OverrideFixtures("tests/integration/fixtures/TestUserRename")() defer tests.PrepareTestEnv(t)() diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go index 13ce197eee..05604e3e6f 100644 --- a/tests/integration/user_test.go +++ b/tests/integration/user_test.go @@ -742,6 +742,65 @@ func TestUserPronouns(t *testing.T) { }) } +func TestUserEditWebsite(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user := forgery.CreateUser(t, nil) + urlStr := "/api/v1/user/settings" + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) + + t.Run("an HTTPS website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should work + website := "https://codeberg.org" + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.UserSettingsOptions{ + Website: &website, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiUser api.UserSettings + DecodeJSON(t, resp, &apiUser) + + assert.Equal(t, website, apiUser.Website) + }) + + t.Run("an H3 website under default schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // changing website should not work + website := "h3://codeberg.org" + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.UserSettingsOptions{ + Website: &website, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusUnprocessableEntity) + + var apiErr api.APIError + DecodeJSON(t, resp, &apiErr) + + assert.Equal(t, "[Website]: Url", apiErr.Message) + }) + + t.Run("an H3 website under custom schemes", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockProtect(&setting.Service.ValidSiteURLSchemes)() + setting.Service.ValidSiteURLSchemes = append(setting.Service.ValidSiteURLSchemes, "h3") + + // changing website should work + website := "h3://codeberg.org" + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.UserSettingsOptions{ + Website: &website, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiUser api.UserSettings + DecodeJSON(t, resp, &apiUser) + + assert.Equal(t, website, apiUser.Website) + }) +} + func TestUserTOTPMail(t *testing.T) { defer tests.PrepareTestEnv(t)()