Refactor context flash msg and global variables (#33375)

1. add `GetSiteCookieFlashMessage` to help to parse flash message
2. clarify `handleRepoHomeFeed` logic
3. remove unnecessary global variables, use `sync.OnceValue` instead
4. add some tests for `IsUsableUsername` and `IsUsableRepoName`
pull/33392/head^2
wxiaoguang 6 days ago committed by GitHub
parent 6a516a0d14
commit 2c1ff8701a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      models/db/name.go
  2. 27
      models/repo/repo.go
  3. 1
      models/repo/repo_test.go
  4. 8
      models/user/user.go
  5. 15
      models/user/user_test.go
  6. 56
      modules/validation/glob_pattern_test.go
  7. 27
      modules/validation/helpers.go
  8. 419
      modules/validation/refname_test.go
  9. 56
      modules/validation/regex_pattern_test.go
  10. 158
      modules/validation/validurl_test.go
  11. 238
      modules/validation/validurllist_test.go
  12. 25
      modules/web/middleware/flash.go
  13. 14
      modules/web/middleware/request.go
  14. 8
      routers/web/feed/convert.go
  15. 2
      routers/web/feed/render.go
  16. 28
      routers/web/repo/view_home.go
  17. 2
      routers/web/web.go
  18. 65
      services/auth/auth.go
  19. 10
      services/auth/auth_test.go
  20. 4
      services/auth/basic.go
  21. 16
      services/auth/oauth2.go
  22. 4
      services/auth/reverseproxy.go
  23. 9
      services/auth/sspi.go
  24. 14
      services/context/context.go
  25. 11
      tests/integration/editor_test.go
  26. 6
      tests/integration/git_general_test.go
  27. 22
      tests/integration/integration_test.go
  28. 11
      tests/integration/mirror_push_test.go
  29. 11
      tests/integration/rename_branch_test.go
  30. 2
      tests/integration/signin_test.go

@ -11,8 +11,6 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
var ErrNameEmpty = util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument}
// ErrNameReserved represents a "reserved name" error. // ErrNameReserved represents a "reserved name" error.
type ErrNameReserved struct { type ErrNameReserved struct {
Name string Name string
@ -79,7 +77,7 @@ func (err ErrNameCharsNotAllowed) Unwrap() error {
func IsUsableName(reservedNames, reservedPatterns []string, name string) error { func IsUsableName(reservedNames, reservedPatterns []string, name string) error {
name = strings.TrimSpace(strings.ToLower(name)) name = strings.TrimSpace(strings.ToLower(name))
if utf8.RuneCountInString(name) == 0 { if utf8.RuneCountInString(name) == 0 {
return ErrNameEmpty return util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument}
} }
for i := range reservedNames { for i := range reservedNames {

@ -14,6 +14,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"sync"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
@ -61,20 +62,30 @@ func (err ErrRepoIsArchived) Error() string {
return fmt.Sprintf("%s is archived", err.Repo.LogString()) return fmt.Sprintf("%s is archived", err.Repo.LogString())
} }
var ( type globalVarsStruct struct {
validRepoNamePattern = regexp.MustCompile(`[-.\w]+`) validRepoNamePattern *regexp.Regexp
invalidRepoNamePattern = regexp.MustCompile(`[.]{2,}`) invalidRepoNamePattern *regexp.Regexp
reservedRepoNames = []string{".", "..", "-"} reservedRepoNames []string
reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"} reservedRepoPatterns []string
) }
var globalVars = sync.OnceValue(func() *globalVarsStruct {
return &globalVarsStruct{
validRepoNamePattern: regexp.MustCompile(`[-.\w]+`),
invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
reservedRepoNames: []string{".", "..", "-"},
reservedRepoPatterns: []string{"*.git", "*.wiki", "*.rss", "*.atom"},
}
})
// IsUsableRepoName returns true when name is usable // IsUsableRepoName returns true when name is usable
func IsUsableRepoName(name string) error { func IsUsableRepoName(name string) error {
if !validRepoNamePattern.MatchString(name) || invalidRepoNamePattern.MatchString(name) { vars := globalVars()
if !vars.validRepoNamePattern.MatchString(name) || vars.invalidRepoNamePattern.MatchString(name) {
// Note: usually this error is normally caught up earlier in the UI // Note: usually this error is normally caught up earlier in the UI
return db.ErrNameCharsNotAllowed{Name: name} return db.ErrNameCharsNotAllowed{Name: name}
} }
return db.IsUsableName(reservedRepoNames, reservedRepoPatterns, name) return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoPatterns, name)
} }
// TrustModelType defines the types of trust model for this repository // TrustModelType defines the types of trust model for this repository

@ -219,4 +219,5 @@ func TestIsUsableRepoName(t *testing.T) {
assert.Error(t, IsUsableRepoName("the..repo")) assert.Error(t, IsUsableRepoName("the..repo"))
assert.Error(t, IsUsableRepoName("foo.wiki")) assert.Error(t, IsUsableRepoName("foo.wiki"))
assert.Error(t, IsUsableRepoName("foo.git")) assert.Error(t, IsUsableRepoName("foo.git"))
assert.Error(t, IsUsableRepoName("foo.RSS"))
} }

@ -502,10 +502,10 @@ func (u *User) IsMailable() bool {
return u.IsActive return u.IsActive
} }
// IsUserExist checks if given user name exist, // IsUserExist checks if given username exist,
// the user name should be noncased unique. // the username should be non-cased unique.
// If uid is presented, then check will rule out that one, // If uid is presented, then check will rule out that one,
// it is used when update a user name in settings page. // it is used when update a username in settings page.
func IsUserExist(ctx context.Context, uid int64, name string) (bool, error) { func IsUserExist(ctx context.Context, uid int64, name string) (bool, error) {
if len(name) == 0 { if len(name) == 0 {
return false, nil return false, nil
@ -515,7 +515,7 @@ func IsUserExist(ctx context.Context, uid int64, name string) (bool, error) {
Get(&User{LowerName: strings.ToLower(name)}) Get(&User{LowerName: strings.ToLower(name)})
} }
// Note: As of the beginning of 2022, it is recommended to use at least // SaltByteLength as of the beginning of 2022, it is recommended to use at least
// 64 bits of salt, but NIST is already recommending to use to 128 bits. // 64 bits of salt, but NIST is already recommending to use to 128 bits.
// (16 bytes = 16 * 8 = 128 bits) // (16 bytes = 16 * 8 = 128 bits)
const SaltByteLength = 16 const SaltByteLength = 16

@ -25,6 +25,21 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestIsUsableUsername(t *testing.T) {
assert.NoError(t, user_model.IsUsableUsername("a"))
assert.NoError(t, user_model.IsUsableUsername("foo.wiki"))
assert.NoError(t, user_model.IsUsableUsername("foo.git"))
assert.Error(t, user_model.IsUsableUsername("a--b"))
assert.Error(t, user_model.IsUsableUsername("-1_."))
assert.Error(t, user_model.IsUsableUsername(".profile"))
assert.Error(t, user_model.IsUsableUsername("-"))
assert.Error(t, user_model.IsUsableUsername("🌞"))
assert.Error(t, user_model.IsUsableUsername("the..repo"))
assert.Error(t, user_model.IsUsableUsername("foo.RSS"))
assert.Error(t, user_model.IsUsableUsername("foo.PnG"))
}
func TestOAuth2Application_LoadUser(t *testing.T) { func TestOAuth2Application_LoadUser(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
app := unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ID: 1}) app := unittest.AssertExistsAndLoadBean(t, &auth.OAuth2Application{ID: 1})

@ -19,39 +19,39 @@ func getGlobPatternErrorString(pattern string) string {
return "" return ""
} }
var globValidationTestCases = []validationTestCase{ func Test_GlobPatternValidation(t *testing.T) {
{ AddBindingRules()
description: "Empty glob pattern",
data: TestForm{
GlobPattern: "",
},
expectedErrors: binding.Errors{},
},
{
description: "Valid glob",
data: TestForm{
GlobPattern: "{master,release*}",
},
expectedErrors: binding.Errors{},
},
{ globValidationTestCases := []validationTestCase{
description: "Invalid glob", {
data: TestForm{ description: "Empty glob pattern",
GlobPattern: "[a-", data: TestForm{
GlobPattern: "",
},
expectedErrors: binding.Errors{},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Valid glob",
FieldNames: []string{"GlobPattern"}, data: TestForm{
Classification: ErrGlobPattern, GlobPattern: "{master,release*}",
Message: getGlobPatternErrorString("[a-"),
}, },
expectedErrors: binding.Errors{},
}, },
},
}
func Test_GlobPatternValidation(t *testing.T) { {
AddBindingRules() description: "Invalid glob",
data: TestForm{
GlobPattern: "[a-",
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"GlobPattern"},
Classification: ErrGlobPattern,
Message: getGlobPatternErrorString("[a-"),
},
},
},
}
for _, testCase := range globValidationTestCases { for _, testCase := range globValidationTestCases {
t.Run(testCase.description, func(t *testing.T) { t.Run(testCase.description, func(t *testing.T) {

@ -8,13 +8,26 @@ import (
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
"sync"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/gobwas/glob" "github.com/gobwas/glob"
) )
var externalTrackerRegex = regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`) type globalVarsStruct struct {
externalTrackerRegex *regexp.Regexp
validUsernamePattern *regexp.Regexp
invalidUsernamePattern *regexp.Regexp
}
var globalVars = sync.OnceValue(func() *globalVarsStruct {
return &globalVarsStruct{
externalTrackerRegex: regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`),
validUsernamePattern: regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`),
invalidUsernamePattern: regexp.MustCompile(`[-._]{2,}|[-._]$`), // No consecutive or trailing non-alphanumeric chars
}
})
func isLoopbackIP(ip string) bool { func isLoopbackIP(ip string) bool {
return net.ParseIP(ip).IsLoopback() return net.ParseIP(ip).IsLoopback()
@ -105,9 +118,9 @@ func IsValidExternalTrackerURLFormat(uri string) bool {
if !IsValidExternalURL(uri) { if !IsValidExternalURL(uri) {
return false return false
} }
vars := globalVars()
// check for typoed variables like /{index/ or /[repo} // check for typoed variables like /{index/ or /[repo}
for _, match := range externalTrackerRegex.FindAllStringSubmatch(uri, -1) { for _, match := range vars.externalTrackerRegex.FindAllStringSubmatch(uri, -1) {
if (match[1] == "{" || match[2] == "}") && (match[1] != "{" || match[2] != "}") { if (match[1] == "{" || match[2] == "}") && (match[1] != "{" || match[2] != "}") {
return false return false
} }
@ -116,14 +129,10 @@ func IsValidExternalTrackerURLFormat(uri string) bool {
return true return true
} }
var (
validUsernamePattern = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) // No consecutive or trailing non-alphanumeric chars
)
// IsValidUsername checks if username is valid // IsValidUsername checks if username is valid
func IsValidUsername(name string) bool { func IsValidUsername(name string) bool {
// It is difficult to find a single pattern that is both readable and effective, // It is difficult to find a single pattern that is both readable and effective,
// but it's easier to use positive and negative checks. // but it's easier to use positive and negative checks.
return validUsernamePattern.MatchString(name) && !invalidUsernamePattern.MatchString(name) vars := globalVars()
return vars.validUsernamePattern.MatchString(name) && !vars.invalidUsernamePattern.MatchString(name)
} }

@ -9,253 +9,252 @@ import (
"gitea.com/go-chi/binding" "gitea.com/go-chi/binding"
) )
var gitRefNameValidationTestCases = []validationTestCase{ func Test_GitRefNameValidation(t *testing.T) {
{ AddBindingRules()
description: "Reference name contains only characters", gitRefNameValidationTestCases := []validationTestCase{
data: TestForm{ {
BranchName: "test", description: "Reference name contains only characters",
}, data: TestForm{
expectedErrors: binding.Errors{}, BranchName: "test",
},
{
description: "Reference name contains single slash",
data: TestForm{
BranchName: "feature/test",
},
expectedErrors: binding.Errors{},
},
{
description: "Reference name has allowed special characters",
data: TestForm{
BranchName: "debian/1%1.6.0-2",
},
expectedErrors: binding.Errors{},
},
{
description: "Reference name contains backslash",
data: TestForm{
BranchName: "feature\\test",
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
}, },
expectedErrors: binding.Errors{},
}, },
}, {
{ description: "Reference name contains single slash",
description: "Reference name starts with dot", data: TestForm{
data: TestForm{ BranchName: "feature/test",
BranchName: ".test",
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
}, },
expectedErrors: binding.Errors{},
}, },
}, {
{ description: "Reference name has allowed special characters",
description: "Reference name ends with dot", data: TestForm{
data: TestForm{ BranchName: "debian/1%1.6.0-2",
BranchName: "test.",
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
}, },
expectedErrors: binding.Errors{},
}, },
}, {
{ description: "Reference name contains backslash",
description: "Reference name starts with slash", data: TestForm{
data: TestForm{ BranchName: "feature\\test",
BranchName: "/test",
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
}, },
}, expectedErrors: binding.Errors{
}, binding.Error{
{ FieldNames: []string{"BranchName"},
description: "Reference name ends with slash", Classification: ErrGitRefName,
data: TestForm{ Message: "GitRefName",
BranchName: "test/", },
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
}, },
}, },
}, {
{ description: "Reference name starts with dot",
description: "Reference name ends with .lock", data: TestForm{
data: TestForm{ BranchName: ".test",
BranchName: "test.lock",
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
}, },
}, expectedErrors: binding.Errors{
}, binding.Error{
{ FieldNames: []string{"BranchName"},
description: "Reference name contains multiple consecutive dots", Classification: ErrGitRefName,
data: TestForm{ Message: "GitRefName",
BranchName: "te..st", },
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
}, },
}, },
}, {
{ description: "Reference name ends with dot",
description: "Reference name contains multiple consecutive slashes", data: TestForm{
data: TestForm{ BranchName: "test.",
BranchName: "te//st",
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
}, },
}, expectedErrors: binding.Errors{
}, binding.Error{
{ FieldNames: []string{"BranchName"},
description: "Reference name is single @", Classification: ErrGitRefName,
data: TestForm{ Message: "GitRefName",
BranchName: "@", },
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
}, },
}, },
}, {
{ description: "Reference name starts with slash",
description: "Reference name has @{", data: TestForm{
data: TestForm{ BranchName: "/test",
BranchName: "branch@{", },
}, expectedErrors: binding.Errors{
expectedErrors: binding.Errors{ binding.Error{
binding.Error{ FieldNames: []string{"BranchName"},
FieldNames: []string{"BranchName"}, Classification: ErrGitRefName,
Classification: ErrGitRefName, Message: "GitRefName",
Message: "GitRefName", },
}, },
}, },
}, {
{ description: "Reference name ends with slash",
description: "Reference name has unallowed special character ~", data: TestForm{
data: TestForm{ BranchName: "test/",
BranchName: "~debian/1%1.6.0-2", },
}, expectedErrors: binding.Errors{
expectedErrors: binding.Errors{ binding.Error{
binding.Error{ FieldNames: []string{"BranchName"},
FieldNames: []string{"BranchName"}, Classification: ErrGitRefName,
Classification: ErrGitRefName, Message: "GitRefName",
Message: "GitRefName", },
}, },
}, },
}, {
{ description: "Reference name ends with .lock",
description: "Reference name has unallowed special character *", data: TestForm{
data: TestForm{ BranchName: "test.lock",
BranchName: "*debian/1%1.6.0-2", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Reference name contains multiple consecutive dots",
FieldNames: []string{"BranchName"}, data: TestForm{
Classification: ErrGitRefName, BranchName: "te..st",
Message: "GitRefName", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
}, },
}, },
}, {
{ description: "Reference name contains multiple consecutive slashes",
description: "Reference name has unallowed special character ?", data: TestForm{
data: TestForm{ BranchName: "te//st",
BranchName: "?debian/1%1.6.0-2", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Reference name is single @",
FieldNames: []string{"BranchName"}, data: TestForm{
Classification: ErrGitRefName, BranchName: "@",
Message: "GitRefName", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
}, },
}, },
}, {
{ description: "Reference name has @{",
description: "Reference name has unallowed special character ^", data: TestForm{
data: TestForm{ BranchName: "branch@{",
BranchName: "^debian/1%1.6.0-2", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Reference name has unallowed special character ~",
FieldNames: []string{"BranchName"}, data: TestForm{
Classification: ErrGitRefName, BranchName: "~debian/1%1.6.0-2",
Message: "GitRefName", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
}, },
}, },
}, {
{ description: "Reference name has unallowed special character *",
description: "Reference name has unallowed special character :", data: TestForm{
data: TestForm{ BranchName: "*debian/1%1.6.0-2",
BranchName: "debian:jessie", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Reference name has unallowed special character ?",
FieldNames: []string{"BranchName"}, data: TestForm{
Classification: ErrGitRefName, BranchName: "?debian/1%1.6.0-2",
Message: "GitRefName", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
}, },
}, },
}, {
{ description: "Reference name has unallowed special character ^",
description: "Reference name has unallowed special character (whitespace)", data: TestForm{
data: TestForm{ BranchName: "^debian/1%1.6.0-2",
BranchName: "debian jessie", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Reference name has unallowed special character :",
FieldNames: []string{"BranchName"}, data: TestForm{
Classification: ErrGitRefName, BranchName: "debian:jessie",
Message: "GitRefName", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
}, },
}, },
}, {
{ description: "Reference name has unallowed special character (whitespace)",
description: "Reference name has unallowed special character [", data: TestForm{
data: TestForm{ BranchName: "debian jessie",
BranchName: "debian[jessie", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Reference name has unallowed special character [",
FieldNames: []string{"BranchName"}, data: TestForm{
Classification: ErrGitRefName, BranchName: "debian[jessie",
Message: "GitRefName", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"BranchName"},
Classification: ErrGitRefName,
Message: "GitRefName",
},
}, },
}, },
}, }
}
func Test_GitRefNameValidation(t *testing.T) {
AddBindingRules()
for _, testCase := range gitRefNameValidationTestCases { for _, testCase := range gitRefNameValidationTestCases {
t.Run(testCase.description, func(t *testing.T) { t.Run(testCase.description, func(t *testing.T) {

@ -17,39 +17,39 @@ func getRegexPatternErrorString(pattern string) string {
return "" return ""
} }
var regexValidationTestCases = []validationTestCase{ func Test_RegexPatternValidation(t *testing.T) {
{ AddBindingRules()
description: "Empty regex pattern",
data: TestForm{
RegexPattern: "",
},
expectedErrors: binding.Errors{},
},
{
description: "Valid regex",
data: TestForm{
RegexPattern: `(\d{1,3})+`,
},
expectedErrors: binding.Errors{},
},
{ regexValidationTestCases := []validationTestCase{
description: "Invalid regex", {
data: TestForm{ description: "Empty regex pattern",
RegexPattern: "[a-", data: TestForm{
RegexPattern: "",
},
expectedErrors: binding.Errors{},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Valid regex",
FieldNames: []string{"RegexPattern"}, data: TestForm{
Classification: ErrRegexPattern, RegexPattern: `(\d{1,3})+`,
Message: getRegexPatternErrorString("[a-"),
}, },
expectedErrors: binding.Errors{},
}, },
},
}
func Test_RegexPatternValidation(t *testing.T) { {
AddBindingRules() description: "Invalid regex",
data: TestForm{
RegexPattern: "[a-",
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"RegexPattern"},
Classification: ErrRegexPattern,
Message: getRegexPatternErrorString("[a-"),
},
},
},
}
for _, testCase := range regexValidationTestCases { for _, testCase := range regexValidationTestCases {
t.Run(testCase.description, func(t *testing.T) { t.Run(testCase.description, func(t *testing.T) {

@ -9,98 +9,98 @@ import (
"gitea.com/go-chi/binding" "gitea.com/go-chi/binding"
) )
var urlValidationTestCases = []validationTestCase{ func Test_ValidURLValidation(t *testing.T) {
{ AddBindingRules()
description: "Empty URL",
data: TestForm{ urlValidationTestCases := []validationTestCase{
URL: "", {
}, description: "Empty URL",
expectedErrors: binding.Errors{}, data: TestForm{
}, URL: "",
{ },
description: "URL without port", expectedErrors: binding.Errors{},
data: TestForm{
URL: "http://test.lan/",
},
expectedErrors: binding.Errors{},
},
{
description: "URL with port",
data: TestForm{
URL: "http://test.lan:3000/",
},
expectedErrors: binding.Errors{},
},
{
description: "URL with IPv6 address without port",
data: TestForm{
URL: "http://[::1]/",
},
expectedErrors: binding.Errors{},
},
{
description: "URL with IPv6 address with port",
data: TestForm{
URL: "http://[::1]:3000/",
}, },
expectedErrors: binding.Errors{}, {
}, description: "URL without port",
{ data: TestForm{
description: "Invalid URL", URL: "http://test.lan/",
data: TestForm{ },
URL: "http//test.lan/", expectedErrors: binding.Errors{},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "URL with port",
FieldNames: []string{"URL"}, data: TestForm{
Classification: binding.ERR_URL, URL: "http://test.lan:3000/",
Message: "Url",
}, },
expectedErrors: binding.Errors{},
}, },
}, {
{ description: "URL with IPv6 address without port",
description: "Invalid schema", data: TestForm{
data: TestForm{ URL: "http://[::1]/",
URL: "ftp://test.lan/", },
expectedErrors: binding.Errors{},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "URL with IPv6 address with port",
FieldNames: []string{"URL"}, data: TestForm{
Classification: binding.ERR_URL, URL: "http://[::1]:3000/",
Message: "Url",
}, },
expectedErrors: binding.Errors{},
}, },
}, {
{ description: "Invalid URL",
description: "Invalid port", data: TestForm{
data: TestForm{ URL: "http//test.lan/",
URL: "http://test.lan:3x4/", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URL"},
Classification: binding.ERR_URL,
Message: "Url",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Invalid schema",
FieldNames: []string{"URL"}, data: TestForm{
Classification: binding.ERR_URL, URL: "ftp://test.lan/",
Message: "Url", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URL"},
Classification: binding.ERR_URL,
Message: "Url",
},
}, },
}, },
}, {
{ description: "Invalid port",
description: "Invalid port with IPv6 address", data: TestForm{
data: TestForm{ URL: "http://test.lan:3x4/",
URL: "http://[::1]:3x4/", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URL"},
Classification: binding.ERR_URL,
Message: "Url",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Invalid port with IPv6 address",
FieldNames: []string{"URL"}, data: TestForm{
Classification: binding.ERR_URL, URL: "http://[::1]:3x4/",
Message: "Url", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URL"},
Classification: binding.ERR_URL,
Message: "Url",
},
}, },
}, },
}, }
}
func Test_ValidURLValidation(t *testing.T) {
AddBindingRules()
for _, testCase := range urlValidationTestCases { for _, testCase := range urlValidationTestCases {
t.Run(testCase.description, func(t *testing.T) { t.Run(testCase.description, func(t *testing.T) {

@ -9,145 +9,145 @@ import (
"gitea.com/go-chi/binding" "gitea.com/go-chi/binding"
) )
// This is a copy of all the URL tests cases, plus additional ones to func Test_ValidURLListValidation(t *testing.T) {
// account for multiple URLs AddBindingRules()
var urlListValidationTestCases = []validationTestCase{
{ // This is a copy of all the URL tests cases, plus additional ones to
description: "Empty URL", // account for multiple URLs
data: TestForm{ urlListValidationTestCases := []validationTestCase{
URLs: "", {
}, description: "Empty URL",
expectedErrors: binding.Errors{}, data: TestForm{
}, URLs: "",
{
description: "URL without port",
data: TestForm{
URLs: "http://test.lan/",
},
expectedErrors: binding.Errors{},
},
{
description: "URL with port",
data: TestForm{
URLs: "http://test.lan:3000/",
},
expectedErrors: binding.Errors{},
},
{
description: "URL with IPv6 address without port",
data: TestForm{
URLs: "http://[::1]/",
},
expectedErrors: binding.Errors{},
},
{
description: "URL with IPv6 address with port",
data: TestForm{
URLs: "http://[::1]:3000/",
},
expectedErrors: binding.Errors{},
},
{
description: "Invalid URL",
data: TestForm{
URLs: "http//test.lan/",
},
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URLs"},
Classification: binding.ERR_URL,
Message: "http//test.lan/",
}, },
expectedErrors: binding.Errors{},
}, },
}, {
{ description: "URL without port",
description: "Invalid schema", data: TestForm{
data: TestForm{ URLs: "http://test.lan/",
URLs: "ftp://test.lan/", },
expectedErrors: binding.Errors{},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "URL with port",
FieldNames: []string{"URLs"}, data: TestForm{
Classification: binding.ERR_URL, URLs: "http://test.lan:3000/",
Message: "ftp://test.lan/",
}, },
expectedErrors: binding.Errors{},
}, },
}, {
{ description: "URL with IPv6 address without port",
description: "Invalid port", data: TestForm{
data: TestForm{ URLs: "http://[::1]/",
URLs: "http://test.lan:3x4/", },
expectedErrors: binding.Errors{},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "URL with IPv6 address with port",
FieldNames: []string{"URLs"}, data: TestForm{
Classification: binding.ERR_URL, URLs: "http://[::1]:3000/",
Message: "http://test.lan:3x4/",
}, },
expectedErrors: binding.Errors{},
}, },
}, {
{ description: "Invalid URL",
description: "Invalid port with IPv6 address", data: TestForm{
data: TestForm{ URLs: "http//test.lan/",
URLs: "http://[::1]:3x4/", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URLs"},
Classification: binding.ERR_URL,
Message: "http//test.lan/",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Invalid schema",
FieldNames: []string{"URLs"}, data: TestForm{
Classification: binding.ERR_URL, URLs: "ftp://test.lan/",
Message: "http://[::1]:3x4/", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URLs"},
Classification: binding.ERR_URL,
Message: "ftp://test.lan/",
},
}, },
}, },
}, {
{ description: "Invalid port",
description: "Multi URLs", data: TestForm{
data: TestForm{ URLs: "http://test.lan:3x4/",
URLs: "http://test.lan:3000/\nhttp://test.local/", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URLs"},
Classification: binding.ERR_URL,
Message: "http://test.lan:3x4/",
},
},
}, },
expectedErrors: binding.Errors{}, {
}, description: "Invalid port with IPv6 address",
{ data: TestForm{
description: "Multi URLs with newline", URLs: "http://[::1]:3x4/",
data: TestForm{ },
URLs: "http://test.lan:3000/\nhttp://test.local/\n", expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URLs"},
Classification: binding.ERR_URL,
Message: "http://[::1]:3x4/",
},
},
}, },
expectedErrors: binding.Errors{}, {
}, description: "Multi URLs",
{ data: TestForm{
description: "List with invalid entry", URLs: "http://test.lan:3000/\nhttp://test.local/",
data: TestForm{ },
URLs: "http://test.lan:3000/\nhttp://[::1]:3x4/", expectedErrors: binding.Errors{},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "Multi URLs with newline",
FieldNames: []string{"URLs"}, data: TestForm{
Classification: binding.ERR_URL, URLs: "http://test.lan:3000/\nhttp://test.local/\n",
Message: "http://[::1]:3x4/",
}, },
expectedErrors: binding.Errors{},
}, },
}, {
{ description: "List with invalid entry",
description: "List with two invalid entries", data: TestForm{
data: TestForm{ URLs: "http://test.lan:3000/\nhttp://[::1]:3x4/",
URLs: "ftp://test.lan:3000/\nhttp://[::1]:3x4/\n", },
expectedErrors: binding.Errors{
binding.Error{
FieldNames: []string{"URLs"},
Classification: binding.ERR_URL,
Message: "http://[::1]:3x4/",
},
},
}, },
expectedErrors: binding.Errors{ {
binding.Error{ description: "List with two invalid entries",
FieldNames: []string{"URLs"}, data: TestForm{
Classification: binding.ERR_URL, URLs: "ftp://test.lan:3000/\nhttp://[::1]:3x4/\n",
Message: "ftp://test.lan:3000/",
}, },
binding.Error{ expectedErrors: binding.Errors{
FieldNames: []string{"URLs"}, binding.Error{
Classification: binding.ERR_URL, FieldNames: []string{"URLs"},
Message: "http://[::1]:3x4/", Classification: binding.ERR_URL,
Message: "ftp://test.lan:3000/",
},
binding.Error{
FieldNames: []string{"URLs"},
Classification: binding.ERR_URL,
Message: "http://[::1]:3x4/",
},
}, },
}, },
}, }
}
func Test_ValidURLListValidation(t *testing.T) {
AddBindingRules()
for _, testCase := range urlListValidationTestCases { for _, testCase := range urlListValidationTestCases {
t.Run(testCase.description, func(t *testing.T) { t.Run(testCase.description, func(t *testing.T) {

@ -6,6 +6,7 @@ package middleware
import ( import (
"fmt" "fmt"
"html/template" "html/template"
"net/http"
"net/url" "net/url"
"code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/reqctx"
@ -65,3 +66,27 @@ func (f *Flash) Success(msg any, current ...bool) {
f.SuccessMsg = flashMsgStringOrHTML(msg) f.SuccessMsg = flashMsgStringOrHTML(msg)
f.set("success", f.SuccessMsg, current...) f.set("success", f.SuccessMsg, current...)
} }
func ParseCookieFlashMessage(val string) *Flash {
if vals, _ := url.ParseQuery(val); len(vals) > 0 {
return &Flash{
Values: vals,
ErrorMsg: vals.Get("error"),
SuccessMsg: vals.Get("success"),
InfoMsg: vals.Get("info"),
WarningMsg: vals.Get("warning"),
}
}
return nil
}
func GetSiteCookieFlashMessage(dataStore reqctx.RequestDataStore, req *http.Request, cookieName string) (string, *Flash) {
// Get the last flash message from cookie
lastFlashCookie := GetSiteCookie(req, cookieName)
lastFlashMsg := ParseCookieFlashMessage(lastFlashCookie)
if lastFlashMsg != nil {
lastFlashMsg.DataStore = dataStore
return lastFlashCookie, lastFlashMsg
}
return lastFlashCookie, nil
}

@ -1,14 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package middleware
import (
"net/http"
"strings"
)
// IsAPIPath returns true if the specified URL is an API path
func IsAPIPath(req *http.Request) bool {
return strings.HasPrefix(req.URL.Path, "/api/")
}

@ -258,18 +258,18 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
} }
// GetFeedType return if it is a feed request and altered name and feed type. // GetFeedType return if it is a feed request and altered name and feed type.
func GetFeedType(name string, req *http.Request) (bool, string, string) { func GetFeedType(name string, req *http.Request) (showFeed bool, feedType string) {
if strings.HasSuffix(name, ".rss") || if strings.HasSuffix(name, ".rss") ||
strings.Contains(req.Header.Get("Accept"), "application/rss+xml") { strings.Contains(req.Header.Get("Accept"), "application/rss+xml") {
return true, strings.TrimSuffix(name, ".rss"), "rss" return true, "rss"
} }
if strings.HasSuffix(name, ".atom") || if strings.HasSuffix(name, ".atom") ||
strings.Contains(req.Header.Get("Accept"), "application/atom+xml") { strings.Contains(req.Header.Get("Accept"), "application/atom+xml") {
return true, strings.TrimSuffix(name, ".atom"), "atom" return true, "atom"
} }
return false, name, "" return false, ""
} }
// feedActionsToFeedItems convert gitea's Repo's Releases to feeds Item // feedActionsToFeedItems convert gitea's Repo's Releases to feeds Item

@ -9,7 +9,7 @@ import (
// RenderBranchFeed render format for branch or file // RenderBranchFeed render format for branch or file
func RenderBranchFeed(ctx *context.Context) { func RenderBranchFeed(ctx *context.Context) {
_, _, showFeedType := GetFeedType(ctx.PathParam("reponame"), ctx.Req) _, showFeedType := GetFeedType(ctx.PathParam("reponame"), ctx.Req)
if ctx.Repo.TreePath == "" { if ctx.Repo.TreePath == "" {
ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType) ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
} else { } else {

@ -311,21 +311,21 @@ func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) {
} }
func handleRepoHomeFeed(ctx *context.Context) bool { func handleRepoHomeFeed(ctx *context.Context) bool {
if setting.Other.EnableFeed { if !setting.Other.EnableFeed {
isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam("reponame"), ctx.Req) return false
if isFeed { }
switch { isFeed, showFeedType := feed.GetFeedType(ctx.PathParam("reponame"), ctx.Req)
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType): if !isFeed {
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType) return false
case ctx.Repo.TreePath == "": }
feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType) if ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType) {
case ctx.Repo.TreePath != "": feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType) } else if ctx.Repo.TreePath == "" {
} feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
return true } else {
} feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
} }
return false return true
} }
// Home render repository home page // Home render repository home page

@ -1152,7 +1152,7 @@ func registerRoutes(m *web.Router) {
) )
// end "/{username}/{reponame}/settings" // end "/{username}/{reponame}/settings"
// user/org home, including rss feeds // user/org home, including rss feeds like "/{username}/{reponame}.rss"
m.Get("/{username}/{reponame}", optSignIn, context.RepoAssignment, context.RepoRefByType(git.RefTypeBranch), repo.SetEditorconfigIfExists, repo.Home) m.Get("/{username}/{reponame}", optSignIn, context.RepoAssignment, context.RepoRefByType(git.RefTypeBranch), repo.SetEditorconfigIfExists, repo.Home)
m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, reqUnitsWithMarkdown, web.Bind(structs.MarkupOption{}), misc.Markup) m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, reqUnitsWithMarkdown, web.Bind(structs.MarkupOption{}), misc.Markup)

@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
"sync"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/webauthn" "code.gitea.io/gitea/modules/auth/webauthn"
@ -21,44 +22,74 @@ import (
user_service "code.gitea.io/gitea/services/user" user_service "code.gitea.io/gitea/services/user"
) )
type globalVarsStruct struct {
gitRawOrAttachPathRe *regexp.Regexp
lfsPathRe *regexp.Regexp
archivePathRe *regexp.Regexp
}
var globalVars = sync.OnceValue(func() *globalVarsStruct {
return &globalVarsStruct{
gitRawOrAttachPathRe: regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/)|(?:attachments/))`),
lfsPathRe: regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`),
archivePathRe: regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/archive/`),
}
})
// Init should be called exactly once when the application starts to allow plugins // Init should be called exactly once when the application starts to allow plugins
// to allocate necessary resources // to allocate necessary resources
func Init() { func Init() {
webauthn.Init() webauthn.Init()
} }
type authPathDetector struct {
req *http.Request
vars *globalVarsStruct
}
func newAuthPathDetector(req *http.Request) *authPathDetector {
return &authPathDetector{req: req, vars: globalVars()}
}
// isAPIPath returns true if the specified URL is an API path
func (a *authPathDetector) isAPIPath() bool {
return strings.HasPrefix(a.req.URL.Path, "/api/")
}
// isAttachmentDownload check if request is a file download (GET) with URL to an attachment // isAttachmentDownload check if request is a file download (GET) with URL to an attachment
func isAttachmentDownload(req *http.Request) bool { func (a *authPathDetector) isAttachmentDownload() bool {
return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET" return strings.HasPrefix(a.req.URL.Path, "/attachments/") && a.req.Method == "GET"
} }
// isContainerPath checks if the request targets the container endpoint // isContainerPath checks if the request targets the container endpoint
func isContainerPath(req *http.Request) bool { func (a *authPathDetector) isContainerPath() bool {
return strings.HasPrefix(req.URL.Path, "/v2/") return strings.HasPrefix(a.req.URL.Path, "/v2/")
} }
var ( func (a *authPathDetector) isGitRawOrAttachPath() bool {
gitRawOrAttachPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/)|(?:attachments/))`) return a.vars.gitRawOrAttachPathRe.MatchString(a.req.URL.Path)
lfsPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`)
archivePathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/archive/`)
)
func isGitRawOrAttachPath(req *http.Request) bool {
return gitRawOrAttachPathRe.MatchString(req.URL.Path)
} }
func isGitRawOrAttachOrLFSPath(req *http.Request) bool { func (a *authPathDetector) isGitRawOrAttachOrLFSPath() bool {
if isGitRawOrAttachPath(req) { if a.isGitRawOrAttachPath() {
return true return true
} }
if setting.LFS.StartServer { if setting.LFS.StartServer {
return lfsPathRe.MatchString(req.URL.Path) return a.vars.lfsPathRe.MatchString(a.req.URL.Path)
} }
return false return false
} }
func isArchivePath(req *http.Request) bool { func (a *authPathDetector) isArchivePath() bool {
return archivePathRe.MatchString(req.URL.Path) return a.vars.archivePathRe.MatchString(a.req.URL.Path)
}
func (a *authPathDetector) isAuthenticatedTokenRequest() bool {
switch a.req.URL.Path {
case "/login/oauth/userinfo", "/login/oauth/introspect":
return true
}
return false
} }
// handleSignIn clears existing session variables and stores new ones for the specified user object // handleSignIn clears existing session variables and stores new ones for the specified user object

@ -110,21 +110,21 @@ func Test_isGitRawOrLFSPath(t *testing.T) {
t.Run(tt.path, func(t *testing.T) { t.Run(tt.path, func(t *testing.T) {
req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil) req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil)
setting.LFS.StartServer = false setting.LFS.StartServer = false
assert.Equal(t, tt.want, isGitRawOrAttachOrLFSPath(req)) assert.Equal(t, tt.want, newAuthPathDetector(req).isGitRawOrAttachOrLFSPath())
setting.LFS.StartServer = true setting.LFS.StartServer = true
assert.Equal(t, tt.want, isGitRawOrAttachOrLFSPath(req)) assert.Equal(t, tt.want, newAuthPathDetector(req).isGitRawOrAttachOrLFSPath())
}) })
} }
for _, tt := range lfsTests { for _, tt := range lfsTests {
t.Run(tt, func(t *testing.T) { t.Run(tt, func(t *testing.T) {
req, _ := http.NewRequest("POST", tt, nil) req, _ := http.NewRequest("POST", tt, nil)
setting.LFS.StartServer = false setting.LFS.StartServer = false
got := isGitRawOrAttachOrLFSPath(req) got := newAuthPathDetector(req).isGitRawOrAttachOrLFSPath()
assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, gitRawOrAttachPathRe.MatchString(tt)) assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, globalVars().gitRawOrAttachPathRe.MatchString(tt))
setting.LFS.StartServer = true setting.LFS.StartServer = true
got = isGitRawOrAttachOrLFSPath(req) got = newAuthPathDetector(req).isGitRawOrAttachOrLFSPath()
assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer) assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer)
}) })
} }

@ -17,7 +17,6 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
) )
// Ensure the struct implements the interface. // Ensure the struct implements the interface.
@ -49,7 +48,8 @@ func (b *Basic) Name() string {
// Returns nil if header is empty or validation fails. // Returns nil if header is empty or validation fails.
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) { func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
// Basic authentication should only fire on API, Download or on Git or LFSPaths // Basic authentication should only fire on API, Download or on Git or LFSPaths
if !middleware.IsAPIPath(req) && !isContainerPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) { detector := newAuthPathDetector(req)
if !detector.isAPIPath() && !detector.isContainerPath() && !detector.isAttachmentDownload() && !detector.isGitRawOrAttachOrLFSPath() {
return nil, nil return nil, nil
} }

@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/actions" "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/oauth2_provider" "code.gitea.io/gitea/services/oauth2_provider"
) )
@ -162,8 +161,9 @@ func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store Dat
// Returns nil if verification fails. // Returns nil if verification fails.
func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) { func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
// These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs // These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) && detector := newAuthPathDetector(req)
!isGitRawOrAttachPath(req) && !isArchivePath(req) { if !detector.isAPIPath() && !detector.isAttachmentDownload() && !detector.isAuthenticatedTokenRequest() &&
!detector.isGitRawOrAttachPath() && !detector.isArchivePath() {
return nil, nil return nil, nil
} }
@ -190,13 +190,3 @@ func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStor
log.Trace("OAuth2 Authorization: Logged in user %-v", user) log.Trace("OAuth2 Authorization: Logged in user %-v", user)
return user, nil return user, nil
} }
func isAuthenticatedTokenRequest(req *http.Request) bool {
switch req.URL.Path {
case "/login/oauth/userinfo":
fallthrough
case "/login/oauth/introspect":
return true
}
return false
}

@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
gouuid "github.com/google/uuid" gouuid "github.com/google/uuid"
) )
@ -117,7 +116,8 @@ func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store Da
} }
// Make sure requests to API paths, attachment downloads, git and LFS do not create a new session // Make sure requests to API paths, attachment downloads, git and LFS do not create a new session
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawOrAttachOrLFSPath(req) { detector := newAuthPathDetector(req)
if !detector.isAPIPath() && !detector.isAttachmentDownload() && !detector.isGitRawOrAttachOrLFSPath() {
if sess != nil && (sess.Get("uid") == nil || sess.Get("uid").(int64) != user.ID) { if sess != nil && (sess.Get("uid") == nil || sess.Get("uid").(int64) != user.ID) {
handleSignIn(w, req, sess, user) handleSignIn(w, req, sess, user)
} }

@ -17,7 +17,6 @@ import (
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/auth/source/sspi" "code.gitea.io/gitea/services/auth/source/sspi"
gitea_context "code.gitea.io/gitea/services/context" gitea_context "code.gitea.io/gitea/services/context"
@ -120,7 +119,8 @@ func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore,
} }
// Make sure requests to API paths and PWA resources do not create a new session // Make sure requests to API paths and PWA resources do not create a new session
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) { detector := newAuthPathDetector(req)
if !detector.isAPIPath() && !detector.isAttachmentDownload() {
handleSignIn(w, req, sess, user) handleSignIn(w, req, sess, user)
} }
@ -155,8 +155,9 @@ func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) {
} else if req.FormValue("auth_with_sspi") == "1" { } else if req.FormValue("auth_with_sspi") == "1" {
shouldAuth = true shouldAuth = true
} }
} else if middleware.IsAPIPath(req) || isAttachmentDownload(req) { } else {
shouldAuth = true detector := newAuthPathDetector(req)
shouldAuth = detector.isAPIPath() || detector.isAttachmentDownload()
} }
return shouldAuth return shouldAuth
} }

@ -165,18 +165,10 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Base.SetContextValue(WebContextKey, ctx) ctx.Base.SetContextValue(WebContextKey, ctx)
ctx.Csrf = NewCSRFProtector(csrfOpts) ctx.Csrf = NewCSRFProtector(csrfOpts)
// Get the last flash message from cookie // get the last flash message from cookie
lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash) lastFlashCookie, lastFlashMsg := middleware.GetSiteCookieFlashMessage(ctx, ctx.Req, CookieNameFlash)
if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 { if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
// store last Flash message into the template data, to render it ctx.Data["Flash"] = lastFlashMsg // store last Flash message into the template data, to render it
ctx.Data["Flash"] = &middleware.Flash{
DataStore: ctx,
Values: vals,
ErrorMsg: vals.Get("error"),
SuccessMsg: vals.Get("success"),
InfoMsg: vals.Get("info"),
WarningMsg: vals.Get("warning"),
}
} }
// if there are new messages in the ctx.Flash, write them into cookie // if there are new messages in the ctx.Flash, write them into cookie

@ -12,7 +12,6 @@ import (
"testing" "testing"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
gitea_context "code.gitea.io/gitea/services/context"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -58,9 +57,8 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
}) })
session.MakeRequest(t, req, http.StatusSeeOther) session.MakeRequest(t, req, http.StatusSeeOther)
// Check if master branch has been locked successfully // Check if master branch has been locked successfully
flashCookie := session.GetCookie(gitea_context.CookieNameFlash) flashMsg := session.GetCookieFlashMessage()
assert.NotNil(t, flashCookie) assert.EqualValues(t, `Branch protection for rule "master" has been updated.`, flashMsg.SuccessMsg)
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522master%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
// Request editor page // Request editor page
req = NewRequest(t, "GET", "/user2/repo1/_new/master/") req = NewRequest(t, "GET", "/user2/repo1/_new/master/")
@ -98,9 +96,8 @@ func TestCreateFileOnProtectedBranch(t *testing.T) {
assert.EqualValues(t, "/user2/repo1/settings/branches", res["redirect"]) assert.EqualValues(t, "/user2/repo1/settings/branches", res["redirect"])
// Check if master branch has been locked successfully // Check if master branch has been locked successfully
flashCookie = session.GetCookie(gitea_context.CookieNameFlash) flashMsg = session.GetCookieFlashMessage()
assert.NotNil(t, flashCookie) assert.EqualValues(t, `Removing branch protection rule "1" failed.`, flashMsg.ErrorMsg)
assert.EqualValues(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25221%2522%2Bfailed.", flashCookie.Value)
}) })
} }

@ -28,7 +28,6 @@ import (
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
gitea_context "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -464,9 +463,8 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhit
ctx.Session.MakeRequest(t, req, http.StatusSeeOther) ctx.Session.MakeRequest(t, req, http.StatusSeeOther)
// Check if master branch has been locked successfully // Check if master branch has been locked successfully
flashCookie := ctx.Session.GetCookie(gitea_context.CookieNameFlash) flashMsg := ctx.Session.GetCookieFlashMessage()
assert.NotNil(t, flashCookie) assert.EqualValues(t, `Branch protection for rule "`+branch+`" has been updated.`, flashMsg.SuccessMsg)
assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522"+url.QueryEscape(branch)+"%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value)
} }
} }

@ -29,6 +29,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
gitea_context "code.gitea.io/gitea/services/context" gitea_context "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
@ -118,12 +119,11 @@ type TestSession struct {
jar http.CookieJar jar http.CookieJar
} }
func (s *TestSession) GetCookie(name string) *http.Cookie { func (s *TestSession) GetRawCookie(name string) *http.Cookie {
baseURL, err := url.Parse(setting.AppURL) baseURL, err := url.Parse(setting.AppURL)
if err != nil { if err != nil {
return nil return nil
} }
for _, c := range s.jar.Cookies(baseURL) { for _, c := range s.jar.Cookies(baseURL) {
if c.Name == name { if c.Name == name {
return c return c
@ -132,6 +132,20 @@ func (s *TestSession) GetCookie(name string) *http.Cookie {
return nil return nil
} }
func (s *TestSession) GetSiteCookie(name string) string {
c := s.GetRawCookie(name)
if c != nil {
v, _ := url.QueryUnescape(c.Value)
return v
}
return ""
}
func (s *TestSession) GetCookieFlashMessage() *middleware.Flash {
cookie := s.GetSiteCookie(gitea_context.CookieNameFlash)
return middleware.ParseCookieFlashMessage(cookie)
}
func (s *TestSession) MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest.ResponseRecorder { func (s *TestSession) MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest.ResponseRecorder {
t.Helper() t.Helper()
req := rw.Request req := rw.Request
@ -458,9 +472,9 @@ func VerifyJSONSchema(t testing.TB, resp *httptest.ResponseRecorder, schemaFile
// GetUserCSRFToken returns CSRF token for current user // GetUserCSRFToken returns CSRF token for current user
func GetUserCSRFToken(t testing.TB, session *TestSession) string { func GetUserCSRFToken(t testing.TB, session *TestSession) string {
t.Helper() t.Helper()
cookie := session.GetCookie("_csrf") cookie := session.GetSiteCookie("_csrf")
require.NotEmpty(t, cookie) require.NotEmpty(t, cookie)
return cookie.Value return cookie
} }
// GetUserCSRFToken returns CSRF token for anonymous user (not logged in) // GetUserCSRFToken returns CSRF token for anonymous user (not logged in)

@ -9,7 +9,6 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings"
"testing" "testing"
"time" "time"
@ -20,7 +19,6 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
gitea_context "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/migrations" "code.gitea.io/gitea/services/migrations"
mirror_service "code.gitea.io/gitea/services/mirror" mirror_service "code.gitea.io/gitea/services/mirror"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
@ -92,9 +90,8 @@ func testCreatePushMirror(t *testing.T, session *TestSession, owner, repo, addre
}) })
session.MakeRequest(t, req, http.StatusSeeOther) session.MakeRequest(t, req, http.StatusSeeOther)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash) flashMsg := session.GetCookieFlashMessage()
assert.NotNil(t, flashCookie) assert.NotEmpty(t, flashMsg.SuccessMsg)
assert.Contains(t, flashCookie.Value, "success")
} }
func doRemovePushMirror(t *testing.T, session *TestSession, owner, repo string, pushMirrorID int64) bool { func doRemovePushMirror(t *testing.T, session *TestSession, owner, repo string, pushMirrorID int64) bool {
@ -104,8 +101,8 @@ func doRemovePushMirror(t *testing.T, session *TestSession, owner, repo string,
"push_mirror_id": strconv.FormatInt(pushMirrorID, 10), "push_mirror_id": strconv.FormatInt(pushMirrorID, 10),
}) })
resp := session.MakeRequest(t, req, NoExpectedStatus) resp := session.MakeRequest(t, req, NoExpectedStatus)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash) flashMsg := session.GetCookieFlashMessage()
return resp.Code == http.StatusSeeOther && flashCookie != nil && strings.Contains(flashCookie.Value, "success") return resp.Code == http.StatusSeeOther && assert.NotEmpty(t, flashMsg.SuccessMsg)
} }
func doUpdatePushMirror(t *testing.T, session *TestSession, owner, repo string, pushMirrorID int64, interval string) bool { func doUpdatePushMirror(t *testing.T, session *TestSession, owner, repo string, pushMirrorID int64, interval string) bool {

@ -11,7 +11,6 @@ import (
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
gitea_context "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -82,9 +81,8 @@ func testRenameBranch(t *testing.T, u *url.URL) {
"to": "branch1", "to": "branch1",
}) })
session.MakeRequest(t, req, http.StatusSeeOther) session.MakeRequest(t, req, http.StatusSeeOther)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash) flashMsg := session.GetCookieFlashMessage()
assert.NotNil(t, flashCookie) assert.NotEmpty(t, flashMsg.ErrorMsg)
assert.Contains(t, flashCookie.Value, "error")
branch2 = unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "branch2"}) branch2 = unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "branch2"})
assert.Equal(t, "branch2", branch2.Name) assert.Equal(t, "branch2", branch2.Name)
@ -110,9 +108,8 @@ func testRenameBranch(t *testing.T, u *url.URL) {
}) })
session.MakeRequest(t, req, http.StatusSeeOther) session.MakeRequest(t, req, http.StatusSeeOther)
flashCookie = session.GetCookie(gitea_context.CookieNameFlash) flashMsg = session.GetCookieFlashMessage()
assert.NotNil(t, flashCookie) assert.NotEmpty(t, flashMsg.SuccessMsg)
assert.Contains(t, flashCookie.Value, "success")
unittest.AssertNotExistsBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "branch2"}) unittest.AssertNotExistsBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "branch2"})
branch1 = unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "branch1"}) branch1 = unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "branch1"})

@ -79,7 +79,7 @@ func TestSigninWithRememberMe(t *testing.T) {
}) })
session.MakeRequest(t, req, http.StatusSeeOther) session.MakeRequest(t, req, http.StatusSeeOther)
c := session.GetCookie(setting.CookieRememberName) c := session.GetRawCookie(setting.CookieRememberName)
assert.NotNil(t, c) assert.NotNil(t, c)
session = emptyTestSession(t) session = emptyTestSession(t)

Loading…
Cancel
Save