Skip to content

Commit 64c616d

Browse files
committed
fix
1 parent c69cbb7 commit 64c616d

9 files changed

Lines changed: 57 additions & 192 deletions

File tree

custom/conf/app.example.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,11 @@ ENABLED = true
592592
;; * https://github.com/git-ecosystem/git-credential-manager
593593
;; * https://gitea.com/gitea/tea
594594
;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager, tea
595+
;;
596+
;; By default, OAuth2 applications can only use "http" and "https" as their redirect URI schemes.
597+
;; If you need to use other schemes (e.g. for desktop applications), you can specify them here as a comma-separated list.
598+
;; For example: set "my-scheme, com.example.app" to support "my-scheme://..." and "com.example.app://..." redirect URIs.
599+
;CUSTOM_SCHEMES =
595600

596601
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
597602
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

modules/setting/oauth2.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ var OAuth2 = struct {
9999
JWTClaimIssuer string `ini:"JWT_CLAIM_ISSUER"`
100100
MaxTokenLength int
101101
DefaultApplications []string
102+
CustomSchemes []string
102103
}{
103104
Enabled: true,
104105
AccessTokenExpirationTime: 3600,

modules/validation/binding.go

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"code.gitea.io/gitea/modules/git"
1414
"code.gitea.io/gitea/modules/glob"
1515
"code.gitea.io/gitea/modules/json"
16-
"code.gitea.io/gitea/modules/util"
1716

1817
"gitea.com/go-chi/binding"
1918
)
@@ -51,7 +50,6 @@ func (j jsonProvider) NewEncoder(writer io.Writer) binding.JSONEncoder {
5150
func AddBindingRules() {
5251
binding.JSONProvider = jsonProvider{}
5352
addGitRefNameBindingRule()
54-
addValidURLListBindingRule()
5553
addValidURLBindingRule()
5654
addValidSiteURLBindingRule()
5755
addGlobPatternRule()
@@ -80,33 +78,6 @@ func addGitRefNameBindingRule() {
8078
})
8179
}
8280

83-
func addValidURLListBindingRule() {
84-
// URL validation rule
85-
binding.AddRule(&binding.Rule{
86-
IsMatch: func(rule string) bool {
87-
return rule == "ValidUrlList"
88-
},
89-
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
90-
str := fmt.Sprintf("%v", val)
91-
if len(str) == 0 {
92-
errs.Add([]string{name}, binding.ERR_URL, "Url")
93-
return false, errs
94-
}
95-
96-
ok := true
97-
urls := util.SplitTrimSpace(str, "\n")
98-
for _, u := range urls {
99-
if !IsValidURL(u) {
100-
ok = false
101-
errs.Add([]string{name}, binding.ERR_URL, u)
102-
}
103-
}
104-
105-
return ok, errs
106-
},
107-
})
108-
}
109-
11081
func addValidURLBindingRule() {
11182
// URL validation rule
11283
binding.AddRule(&binding.Rule{

modules/validation/binding_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ type (
2727
TestForm struct {
2828
BranchName string `form:"BranchName" binding:"GitRefName"`
2929
URL string `form:"ValidUrl" binding:"ValidUrl"`
30-
URLs string `form:"ValidUrls" binding:"ValidUrlList"`
3130
GlobPattern string `form:"GlobPattern" binding:"GlobPattern"`
3231
RegexPattern string `form:"RegexPattern" binding:"RegexPattern"`
3332
}

modules/validation/validurllist_test.go

Lines changed: 0 additions & 157 deletions
This file was deleted.

modules/web/middleware/binding.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,15 @@ func GetInclude(field reflect.StructField) string {
7878
return getRuleBody(field, "Include(")
7979
}
8080

81-
// Validate validate
81+
func ReportValidationError(errs binding.Errors, data map[string]any, fieldName, classification, errorMsg string) {
82+
data["HasError"] = true
83+
data["ErrorMsg"] = fieldName + ": " + errorMsg
84+
if fieldName != "" && fieldName != "-" {
85+
data["Err_"+fieldName] = true
86+
}
87+
errs.Add([]string{fieldName}, classification, errorMsg)
88+
}
89+
8290
func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Locale) binding.Errors {
8391
if errs.Len() == 0 {
8492
return errs

routers/api/v1/user/app.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"code.gitea.io/gitea/routers/api/v1/utils"
1919
"code.gitea.io/gitea/services/context"
2020
"code.gitea.io/gitea/services/convert"
21+
"code.gitea.io/gitea/services/forms"
2122
)
2223

2324
// ListAccessTokens list all the access tokens
@@ -228,7 +229,10 @@ func CreateOauth2Application(ctx *context.APIContext) {
228229
// "$ref": "#/responses/error"
229230

230231
data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions)
231-
232+
if invalidURI := forms.DetectInvalidOAuth2ApplicationRedirectURI(data.RedirectURIs); invalidURI != "" {
233+
ctx.APIError(http.StatusBadRequest, "invalid redirect URI: "+invalidURI)
234+
return
235+
}
232236
app, err := auth_model.CreateOAuth2Application(ctx, auth_model.CreateOAuth2ApplicationOptions{
233237
Name: data.Name,
234238
UserID: ctx.Doer.ID,
@@ -382,11 +386,17 @@ func UpdateOauth2Application(ctx *context.APIContext) {
382386
// responses:
383387
// "200":
384388
// "$ref": "#/responses/OAuth2Application"
389+
// "400":
390+
// "$ref": "#/responses/error"
385391
// "404":
386392
// "$ref": "#/responses/notFound"
387393
appID := ctx.PathParamInt64("id")
388394

389395
data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions)
396+
if invalidURI := forms.DetectInvalidOAuth2ApplicationRedirectURI(data.RedirectURIs); invalidURI != "" {
397+
ctx.APIError(http.StatusBadRequest, "invalid redirect URI: "+invalidURI)
398+
return
399+
}
390400

391401
app, err := auth_model.UpdateOAuth2Application(ctx, auth_model.UpdateOAuth2ApplicationOptions{
392402
Name: data.Name,

services/forms/user_form.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ package forms
77
import (
88
"mime/multipart"
99
"net/http"
10+
"strings"
1011

1112
user_model "code.gitea.io/gitea/models/user"
13+
"code.gitea.io/gitea/modules/setting"
1214
"code.gitea.io/gitea/modules/structs"
15+
"code.gitea.io/gitea/modules/util"
1316
"code.gitea.io/gitea/modules/web/middleware"
1417
"code.gitea.io/gitea/services/context"
1518

@@ -356,14 +359,31 @@ func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) bi
356359
// EditOAuth2ApplicationForm form for editing oauth2 applications
357360
type EditOAuth2ApplicationForm struct {
358361
Name string `binding:"Required;MaxSize(255)" form:"application_name"`
359-
RedirectURIs string `binding:"Required;ValidUrlList" form:"redirect_uris"`
362+
RedirectURIs string `binding:"Required" form:"redirect_uris"`
360363
ConfidentialClient bool `form:"confidential_client"`
361364
SkipSecondaryAuthorization bool `form:"skip_secondary_authorization"`
362365
}
363366

367+
func DetectInvalidOAuth2ApplicationRedirectURI(uris []string) (invalidURL string) {
368+
for _, u := range uris {
369+
scheme, _, ok := strings.Cut(u, ":")
370+
valid := ok && (strings.EqualFold(scheme, "http") ||
371+
strings.EqualFold(scheme, "https") ||
372+
util.SliceContainsString(setting.OAuth2.CustomSchemes, scheme, true))
373+
if !valid {
374+
return u
375+
}
376+
}
377+
return ""
378+
}
379+
364380
// Validate validates the fields
365381
func (f *EditOAuth2ApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
366382
ctx := context.GetValidateContext(req)
383+
invalidURI := DetectInvalidOAuth2ApplicationRedirectURI(util.SplitTrimSpace(f.RedirectURIs, "\n"))
384+
if invalidURI != "" {
385+
middleware.ReportValidationError(errs, ctx.Data, "RedirectURIs", binding.ERR_URL, ctx.Locale.TrString("form.url_error", invalidURI))
386+
}
367387
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
368388
}
369389

tests/integration/user_settings_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"code.gitea.io/gitea/modules/container"
1212
"code.gitea.io/gitea/modules/setting"
13+
"code.gitea.io/gitea/modules/test"
1314
"code.gitea.io/gitea/tests"
1415

1516
"github.com/stretchr/testify/assert"
@@ -311,18 +312,25 @@ func TestUserSettingsApplications(t *testing.T) {
311312
resp := session.MakeRequest(t, req, http.StatusOK)
312313
doc := NewHTMLParser(t, resp.Body)
313314
msg := strings.TrimSpace(doc.Find(".ui.message.flash-message").Text())
314-
assert.Equal(t, `form.RedirectURIs"ftp://127.0.0.1" is not a valid URL.`, msg)
315+
assert.Equal(t, `RedirectURIs: "ftp://127.0.0.1" is not a valid URL.`, msg)
315316
})
316317

317318
t.Run("OK", func(t *testing.T) {
318319
defer tests.PrintCurrentTest(t)()
319-
320+
defer test.MockVariableValue(&setting.OAuth2.CustomSchemes, []string{"my-app"})()
320321
req := NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/2", map[string]string{
321322
"application_name": "Test native app",
322323
"redirect_uris": "http://127.0.0.1",
323324
"confidential_client": "false",
324325
})
325326
session.MakeRequest(t, req, http.StatusSeeOther)
327+
328+
req = NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/2", map[string]string{
329+
"application_name": "Test native app",
330+
"redirect_uris": "my-app://127.0.0.1",
331+
"confidential_client": "false",
332+
})
333+
session.MakeRequest(t, req, http.StatusSeeOther)
326334
})
327335
})
328336
})

0 commit comments

Comments
 (0)