Merge pull request 'fix: use ValidateEmail as binding across web forms' (#5158) from solomonv/consolidate-email-validation into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5158 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
commit
f298bf125a
24 changed files with 281 additions and 221 deletions
|
@ -7,8 +7,6 @@ package user
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -18,53 +16,10 @@ 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/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/validation"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrEmailNotActivated e-mail address has not been activated error
|
|
||||||
var ErrEmailNotActivated = util.NewInvalidArgumentErrorf("e-mail address has not been activated")
|
|
||||||
|
|
||||||
// ErrEmailCharIsNotSupported e-mail address contains unsupported character
|
|
||||||
type ErrEmailCharIsNotSupported struct {
|
|
||||||
Email string
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrEmailCharIsNotSupported checks if an error is an ErrEmailCharIsNotSupported
|
|
||||||
func IsErrEmailCharIsNotSupported(err error) bool {
|
|
||||||
_, ok := err.(ErrEmailCharIsNotSupported)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrEmailCharIsNotSupported) Error() string {
|
|
||||||
return fmt.Sprintf("e-mail address contains unsupported character [email: %s]", err.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrEmailCharIsNotSupported) Unwrap() error {
|
|
||||||
return util.ErrInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
|
|
||||||
// or has a leading '-' character
|
|
||||||
type ErrEmailInvalid struct {
|
|
||||||
Email string
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrEmailInvalid checks if an error is an ErrEmailInvalid
|
|
||||||
func IsErrEmailInvalid(err error) bool {
|
|
||||||
_, ok := err.(ErrEmailInvalid)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrEmailInvalid) Error() string {
|
|
||||||
return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrEmailInvalid) Unwrap() error {
|
|
||||||
return util.ErrInvalidArgument
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error.
|
// ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error.
|
||||||
type ErrEmailAlreadyUsed struct {
|
type ErrEmailAlreadyUsed struct {
|
||||||
Email string
|
Email string
|
||||||
|
@ -156,22 +111,6 @@ func UpdateEmailAddress(ctx context.Context, email *EmailAddress) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
|
||||||
|
|
||||||
// ValidateEmail check if email is a valid & allowed address
|
|
||||||
func ValidateEmail(email string) error {
|
|
||||||
if err := validateEmailBasic(email); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return validateEmailDomain(email)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateEmailForAdmin check if email is a valid address when admins manually add or edit users
|
|
||||||
func ValidateEmailForAdmin(email string) error {
|
|
||||||
return validateEmailBasic(email)
|
|
||||||
// In this case we do not need to check the email domain
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEmailAddressByEmail(ctx context.Context, email string) (*EmailAddress, error) {
|
func GetEmailAddressByEmail(ctx context.Context, email string) (*EmailAddress, error) {
|
||||||
ea := &EmailAddress{}
|
ea := &EmailAddress{}
|
||||||
if has, err := db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(ea); err != nil {
|
if has, err := db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(ea); err != nil {
|
||||||
|
@ -462,41 +401,3 @@ func ActivateUserEmail(ctx context.Context, userID int64, email string, activate
|
||||||
|
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateEmailBasic checks whether the email complies with the rules
|
|
||||||
func validateEmailBasic(email string) error {
|
|
||||||
if len(email) == 0 {
|
|
||||||
return ErrEmailInvalid{email}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !emailRegexp.MatchString(email) {
|
|
||||||
return ErrEmailCharIsNotSupported{email}
|
|
||||||
}
|
|
||||||
|
|
||||||
if email[0] == '-' {
|
|
||||||
return ErrEmailInvalid{email}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := mail.ParseAddress(email); err != nil {
|
|
||||||
return ErrEmailInvalid{email}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateEmailDomain checks whether the email domain is allowed or blocked
|
|
||||||
func validateEmailDomain(email string) error {
|
|
||||||
if !IsEmailDomainAllowed(email) {
|
|
||||||
return ErrEmailInvalid{email}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsEmailDomainAllowed(email string) bool {
|
|
||||||
if len(setting.Service.EmailDomainAllowList) == 0 {
|
|
||||||
return !validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email)
|
|
||||||
}
|
|
||||||
|
|
||||||
return validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email)
|
|
||||||
}
|
|
||||||
|
|
|
@ -130,63 +130,6 @@ func TestListEmails(t *testing.T) {
|
||||||
assert.Greater(t, count, int64(len(emails)))
|
assert.Greater(t, count, int64(len(emails)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmailAddressValidate(t *testing.T) {
|
|
||||||
kases := map[string]error{
|
|
||||||
"abc@gmail.com": nil,
|
|
||||||
"132@hotmail.com": nil,
|
|
||||||
"1-3-2@test.org": nil,
|
|
||||||
"1.3.2@test.org": nil,
|
|
||||||
"a_123@test.org.cn": nil,
|
|
||||||
`first.last@iana.org`: nil,
|
|
||||||
`first!last@iana.org`: nil,
|
|
||||||
`first#last@iana.org`: nil,
|
|
||||||
`first$last@iana.org`: nil,
|
|
||||||
`first%last@iana.org`: nil,
|
|
||||||
`first&last@iana.org`: nil,
|
|
||||||
`first'last@iana.org`: nil,
|
|
||||||
`first*last@iana.org`: nil,
|
|
||||||
`first+last@iana.org`: nil,
|
|
||||||
`first/last@iana.org`: nil,
|
|
||||||
`first=last@iana.org`: nil,
|
|
||||||
`first?last@iana.org`: nil,
|
|
||||||
`first^last@iana.org`: nil,
|
|
||||||
"first`last@iana.org": nil,
|
|
||||||
`first{last@iana.org`: nil,
|
|
||||||
`first|last@iana.org`: nil,
|
|
||||||
`first}last@iana.org`: nil,
|
|
||||||
`first~last@iana.org`: nil,
|
|
||||||
`first;last@iana.org`: user_model.ErrEmailCharIsNotSupported{`first;last@iana.org`},
|
|
||||||
".233@qq.com": user_model.ErrEmailInvalid{".233@qq.com"},
|
|
||||||
"!233@qq.com": nil,
|
|
||||||
"#233@qq.com": nil,
|
|
||||||
"$233@qq.com": nil,
|
|
||||||
"%233@qq.com": nil,
|
|
||||||
"&233@qq.com": nil,
|
|
||||||
"'233@qq.com": nil,
|
|
||||||
"*233@qq.com": nil,
|
|
||||||
"+233@qq.com": nil,
|
|
||||||
"-233@qq.com": user_model.ErrEmailInvalid{"-233@qq.com"},
|
|
||||||
"/233@qq.com": nil,
|
|
||||||
"=233@qq.com": nil,
|
|
||||||
"?233@qq.com": nil,
|
|
||||||
"^233@qq.com": nil,
|
|
||||||
"_233@qq.com": nil,
|
|
||||||
"`233@qq.com": nil,
|
|
||||||
"{233@qq.com": nil,
|
|
||||||
"|233@qq.com": nil,
|
|
||||||
"}233@qq.com": nil,
|
|
||||||
"~233@qq.com": nil,
|
|
||||||
";233@qq.com": user_model.ErrEmailCharIsNotSupported{";233@qq.com"},
|
|
||||||
"Foo <foo@bar.com>": user_model.ErrEmailCharIsNotSupported{"Foo <foo@bar.com>"},
|
|
||||||
string([]byte{0xE2, 0x84, 0xAA}): user_model.ErrEmailCharIsNotSupported{string([]byte{0xE2, 0x84, 0xAA})},
|
|
||||||
}
|
|
||||||
for kase, err := range kases {
|
|
||||||
t.Run(kase, func(t *testing.T) {
|
|
||||||
assert.EqualValues(t, err, user_model.ValidateEmail(kase))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetActivatedEmailAddresses(t *testing.T) {
|
func TestGetActivatedEmailAddresses(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
|
|
@ -717,11 +717,11 @@ func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefa
|
||||||
}
|
}
|
||||||
|
|
||||||
if createdByAdmin {
|
if createdByAdmin {
|
||||||
if err := ValidateEmailForAdmin(u.Email); err != nil {
|
if err := validation.ValidateEmailForAdmin(u.Email); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := ValidateEmail(u.Email); err != nil {
|
if err := validation.ValidateEmail(u.Email); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -885,7 +885,7 @@ func (u User) Validate() []string {
|
||||||
if err := ValidateUser(&u); err != nil {
|
if err := ValidateUser(&u); err != nil {
|
||||||
result = append(result, err.Error())
|
result = append(result, err.Error())
|
||||||
}
|
}
|
||||||
if err := ValidateEmail(u.Email); err != nil {
|
if err := validation.ValidateEmail(u.Email); err != nil {
|
||||||
result = append(result, err.Error())
|
result = append(result, err.Error())
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/validation"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -320,7 +321,7 @@ func TestCreateUserInvalidEmail(t *testing.T) {
|
||||||
|
|
||||||
err := user_model.CreateUser(db.DefaultContext, user)
|
err := user_model.CreateUser(db.DefaultContext, user)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.True(t, user_model.IsErrEmailCharIsNotSupported(err))
|
assert.True(t, validation.IsErrEmailCharIsNotSupported(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateUserEmailAlreadyUsed(t *testing.T) {
|
func TestCreateUserEmailAlreadyUsed(t *testing.T) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ type CreateUserOption struct {
|
||||||
FullName string `json:"full_name" binding:"MaxSize(100)"`
|
FullName string `json:"full_name" binding:"MaxSize(100)"`
|
||||||
// required: true
|
// required: true
|
||||||
// swagger:strfmt email
|
// swagger:strfmt email
|
||||||
Email string `json:"email" binding:"Required;Email;MaxSize(254)"`
|
Email string `json:"email" binding:"Required;EmailForAdmin;MaxSize(254)"`
|
||||||
Password string `json:"password" binding:"MaxSize(255)"`
|
Password string `json:"password" binding:"MaxSize(255)"`
|
||||||
MustChangePassword *bool `json:"must_change_password"`
|
MustChangePassword *bool `json:"must_change_password"`
|
||||||
SendNotify bool `json:"send_notify"`
|
SendNotify bool `json:"send_notify"`
|
||||||
|
|
|
@ -7,7 +7,7 @@ package structs
|
||||||
// Email an email address belonging to a user
|
// Email an email address belonging to a user
|
||||||
type Email struct {
|
type Email struct {
|
||||||
// swagger:strfmt email
|
// swagger:strfmt email
|
||||||
Email string `json:"email"`
|
Email string `json:"email" binding:"EmailWithAllowedDomain"`
|
||||||
Verified bool `json:"verified"`
|
Verified bool `json:"verified"`
|
||||||
Primary bool `json:"primary"`
|
Primary bool `json:"primary"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
|
|
|
@ -26,6 +26,8 @@ const (
|
||||||
ErrUsername = "UsernameError"
|
ErrUsername = "UsernameError"
|
||||||
// ErrInvalidGroupTeamMap is returned when a group team mapping is invalid
|
// ErrInvalidGroupTeamMap is returned when a group team mapping is invalid
|
||||||
ErrInvalidGroupTeamMap = "InvalidGroupTeamMap"
|
ErrInvalidGroupTeamMap = "InvalidGroupTeamMap"
|
||||||
|
// ErrEmail is returned when an email address is invalid
|
||||||
|
ErrEmail = "Email"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddBindingRules adds additional binding rules
|
// AddBindingRules adds additional binding rules
|
||||||
|
@ -38,6 +40,7 @@ func AddBindingRules() {
|
||||||
addGlobOrRegexPatternRule()
|
addGlobOrRegexPatternRule()
|
||||||
addUsernamePatternRule()
|
addUsernamePatternRule()
|
||||||
addValidGroupTeamMapRule()
|
addValidGroupTeamMapRule()
|
||||||
|
addEmailBindingRules()
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGitRefNameBindingRule() {
|
func addGitRefNameBindingRule() {
|
||||||
|
@ -185,6 +188,34 @@ func addValidGroupTeamMapRule() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addEmailBindingRules() {
|
||||||
|
binding.AddRule(&binding.Rule{
|
||||||
|
IsMatch: func(rule string) bool {
|
||||||
|
return strings.HasPrefix(rule, "EmailWithAllowedDomain")
|
||||||
|
},
|
||||||
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
|
if err := ValidateEmail(fmt.Sprintf("%v", val)); err != nil {
|
||||||
|
errs.Add([]string{name}, ErrEmail, err.Error())
|
||||||
|
return false, errs
|
||||||
|
}
|
||||||
|
return true, errs
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.AddRule(&binding.Rule{
|
||||||
|
IsMatch: func(rule string) bool {
|
||||||
|
return strings.HasPrefix(rule, "EmailForAdmin")
|
||||||
|
},
|
||||||
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
|
if err := ValidateEmailForAdmin(fmt.Sprintf("%v", val)); err != nil {
|
||||||
|
errs.Add([]string{name}, ErrEmail, err.Error())
|
||||||
|
return false, errs
|
||||||
|
}
|
||||||
|
return true, errs
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func portOnly(hostport string) string {
|
func portOnly(hostport string) string {
|
||||||
colon := strings.IndexByte(hostport, ':')
|
colon := strings.IndexByte(hostport, ':')
|
||||||
if colon == -1 {
|
if colon == -1 {
|
||||||
|
|
131
modules/validation/email.go
Normal file
131
modules/validation/email.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/mail"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/gobwas/glob"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrEmailNotActivated e-mail address has not been activated error
|
||||||
|
var ErrEmailNotActivated = util.NewInvalidArgumentErrorf("e-mail address has not been activated")
|
||||||
|
|
||||||
|
// ErrEmailCharIsNotSupported e-mail address contains unsupported character
|
||||||
|
type ErrEmailCharIsNotSupported struct {
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrEmailCharIsNotSupported checks if an error is an ErrEmailCharIsNotSupported
|
||||||
|
func IsErrEmailCharIsNotSupported(err error) bool {
|
||||||
|
_, ok := err.(ErrEmailCharIsNotSupported)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrEmailCharIsNotSupported) Error() string {
|
||||||
|
return fmt.Sprintf("e-mail address contains unsupported character [email: %s]", err.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
|
||||||
|
// or has a leading '-' character
|
||||||
|
type ErrEmailInvalid struct {
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrEmailInvalid checks if an error is an ErrEmailInvalid
|
||||||
|
func IsErrEmailInvalid(err error) bool {
|
||||||
|
_, ok := err.(ErrEmailInvalid)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrEmailInvalid) Error() string {
|
||||||
|
return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrEmailInvalid) Unwrap() error {
|
||||||
|
return util.ErrInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||||
|
|
||||||
|
// check if email is a valid address with allowed domain
|
||||||
|
func ValidateEmail(email string) error {
|
||||||
|
if err := validateEmailBasic(email); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validateEmailDomain(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if email is a valid address when admins manually add or edit users
|
||||||
|
func ValidateEmailForAdmin(email string) error {
|
||||||
|
return validateEmailBasic(email)
|
||||||
|
// In this case we do not need to check the email domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateEmailBasic checks whether the email complies with the rules
|
||||||
|
func validateEmailBasic(email string) error {
|
||||||
|
if len(email) == 0 {
|
||||||
|
return ErrEmailInvalid{email}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !emailRegexp.MatchString(email) {
|
||||||
|
return ErrEmailCharIsNotSupported{email}
|
||||||
|
}
|
||||||
|
|
||||||
|
if email[0] == '-' {
|
||||||
|
return ErrEmailInvalid{email}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := mail.ParseAddress(email); err != nil {
|
||||||
|
return ErrEmailInvalid{email}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateEmailDomain(email string) error {
|
||||||
|
if !IsEmailDomainAllowed(email) {
|
||||||
|
return ErrEmailInvalid{email}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsEmailDomainAllowed(email string) bool {
|
||||||
|
if len(setting.Service.EmailDomainAllowList) == 0 {
|
||||||
|
return !isEmailDomainListed(setting.Service.EmailDomainBlockList, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
return isEmailDomainListed(setting.Service.EmailDomainAllowList, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEmailDomainListed checks whether the domain of an email address
|
||||||
|
// matches a list of domains
|
||||||
|
func isEmailDomainListed(globs []glob.Glob, email string) bool {
|
||||||
|
if len(globs) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
n := strings.LastIndex(email, "@")
|
||||||
|
if n <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := strings.ToLower(email[n+1:])
|
||||||
|
|
||||||
|
for _, g := range globs {
|
||||||
|
if g.Match(domain) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
67
modules/validation/email_test.go
Normal file
67
modules/validation/email_test.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEmailAddressValidate(t *testing.T) {
|
||||||
|
kases := map[string]error{
|
||||||
|
"abc@gmail.com": nil,
|
||||||
|
"132@hotmail.com": nil,
|
||||||
|
"1-3-2@test.org": nil,
|
||||||
|
"1.3.2@test.org": nil,
|
||||||
|
"a_123@test.org.cn": nil,
|
||||||
|
`first.last@iana.org`: nil,
|
||||||
|
`first!last@iana.org`: nil,
|
||||||
|
`first#last@iana.org`: nil,
|
||||||
|
`first$last@iana.org`: nil,
|
||||||
|
`first%last@iana.org`: nil,
|
||||||
|
`first&last@iana.org`: nil,
|
||||||
|
`first'last@iana.org`: nil,
|
||||||
|
`first*last@iana.org`: nil,
|
||||||
|
`first+last@iana.org`: nil,
|
||||||
|
`first/last@iana.org`: nil,
|
||||||
|
`first=last@iana.org`: nil,
|
||||||
|
`first?last@iana.org`: nil,
|
||||||
|
`first^last@iana.org`: nil,
|
||||||
|
"first`last@iana.org": nil,
|
||||||
|
`first{last@iana.org`: nil,
|
||||||
|
`first|last@iana.org`: nil,
|
||||||
|
`first}last@iana.org`: nil,
|
||||||
|
`first~last@iana.org`: nil,
|
||||||
|
`first;last@iana.org`: ErrEmailCharIsNotSupported{`first;last@iana.org`},
|
||||||
|
".233@qq.com": ErrEmailInvalid{".233@qq.com"},
|
||||||
|
"!233@qq.com": nil,
|
||||||
|
"#233@qq.com": nil,
|
||||||
|
"$233@qq.com": nil,
|
||||||
|
"%233@qq.com": nil,
|
||||||
|
"&233@qq.com": nil,
|
||||||
|
"'233@qq.com": nil,
|
||||||
|
"*233@qq.com": nil,
|
||||||
|
"+233@qq.com": nil,
|
||||||
|
"-233@qq.com": ErrEmailInvalid{"-233@qq.com"},
|
||||||
|
"/233@qq.com": nil,
|
||||||
|
"=233@qq.com": nil,
|
||||||
|
"?233@qq.com": nil,
|
||||||
|
"^233@qq.com": nil,
|
||||||
|
"_233@qq.com": nil,
|
||||||
|
"`233@qq.com": nil,
|
||||||
|
"{233@qq.com": nil,
|
||||||
|
"|233@qq.com": nil,
|
||||||
|
"}233@qq.com": nil,
|
||||||
|
"~233@qq.com": nil,
|
||||||
|
";233@qq.com": ErrEmailCharIsNotSupported{";233@qq.com"},
|
||||||
|
"Foo <foo@bar.com>": ErrEmailCharIsNotSupported{"Foo <foo@bar.com>"},
|
||||||
|
string([]byte{0xE2, 0x84, 0xAA}): ErrEmailCharIsNotSupported{string([]byte{0xE2, 0x84, 0xAA})},
|
||||||
|
}
|
||||||
|
for kase, err := range kases {
|
||||||
|
t.Run(kase, func(t *testing.T) {
|
||||||
|
assert.EqualValues(t, err, ValidateEmail(kase))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,8 +10,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var externalTrackerRegex = regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`)
|
var externalTrackerRegex = regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`)
|
||||||
|
@ -50,29 +48,6 @@ func IsValidSiteURL(uri string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmailDomainListed checks whether the domain of an email address
|
|
||||||
// matches a list of domains
|
|
||||||
func IsEmailDomainListed(globs []glob.Glob, email string) bool {
|
|
||||||
if len(globs) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
n := strings.LastIndex(email, "@")
|
|
||||||
if n <= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := strings.ToLower(email[n+1:])
|
|
||||||
|
|
||||||
for _, g := range globs {
|
|
||||||
if g.Match(domain) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAPIURL checks if URL is current Gitea instance API URL
|
// IsAPIURL checks if URL is current Gitea instance API URL
|
||||||
func IsAPIURL(uri string) bool {
|
func IsAPIURL(uri string) bool {
|
||||||
return strings.HasPrefix(strings.ToLower(uri), strings.ToLower(setting.AppURL+"api"))
|
return strings.HasPrefix(strings.ToLower(uri), strings.ToLower(setting.AppURL+"api"))
|
||||||
|
|
|
@ -143,6 +143,8 @@ func Validate(errs binding.Errors, data map[string]any, f any, l translation.Loc
|
||||||
}
|
}
|
||||||
case validation.ErrInvalidGroupTeamMap:
|
case validation.ErrInvalidGroupTeamMap:
|
||||||
data["ErrorMsg"] = trName + l.TrString("form.invalid_group_team_map_error", errs[0].Message)
|
data["ErrorMsg"] = trName + l.TrString("form.invalid_group_team_map_error", errs[0].Message)
|
||||||
|
case validation.ErrEmail:
|
||||||
|
data["ErrorMsg"] = trName + l.TrString("form.email_error")
|
||||||
default:
|
default:
|
||||||
msg := errs[0].Classification
|
msg := errs[0].Classification
|
||||||
if msg != "" && errs[0].Message != "" {
|
if msg != "" && errs[0].Message != "" {
|
||||||
|
|
|
@ -6,22 +6,22 @@ package activitypub
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/user"
|
"code.gitea.io/gitea/modules/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_UserEmailValidate(t *testing.T) {
|
func Test_UserEmailValidate(t *testing.T) {
|
||||||
sut := "ab@cd.ef"
|
sut := "ab@cd.ef"
|
||||||
if err := user.ValidateEmail(sut); err != nil {
|
if err := validation.ValidateEmail(sut); err != nil {
|
||||||
t.Errorf("sut should be valid, %v, %v", sut, err)
|
t.Errorf("sut should be valid, %v, %v", sut, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sut = "83ce13c8-af0b-4112-8327-55a54e54e664@code.cartoon-aa.xyz"
|
sut = "83ce13c8-af0b-4112-8327-55a54e54e664@code.cartoon-aa.xyz"
|
||||||
if err := user.ValidateEmail(sut); err != nil {
|
if err := validation.ValidateEmail(sut); err != nil {
|
||||||
t.Errorf("sut should be valid, %v, %v", sut, err)
|
t.Errorf("sut should be valid, %v, %v", sut, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sut = "1"
|
sut = "1"
|
||||||
if err := user.ValidateEmail(sut); err == nil {
|
if err := validation.ValidateEmail(sut); err == nil {
|
||||||
t.Errorf("sut should not be valid, %v", sut)
|
t.Errorf("sut should not be valid, %v", sut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"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"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/validation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/user"
|
"code.gitea.io/gitea/routers/api/v1/user"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
@ -138,8 +139,8 @@ func CreateUser(ctx *context.APIContext) {
|
||||||
user_model.IsErrEmailAlreadyUsed(err) ||
|
user_model.IsErrEmailAlreadyUsed(err) ||
|
||||||
db.IsErrNameReserved(err) ||
|
db.IsErrNameReserved(err) ||
|
||||||
db.IsErrNameCharsNotAllowed(err) ||
|
db.IsErrNameCharsNotAllowed(err) ||
|
||||||
user_model.IsErrEmailCharIsNotSupported(err) ||
|
validation.IsErrEmailCharIsNotSupported(err) ||
|
||||||
user_model.IsErrEmailInvalid(err) ||
|
validation.IsErrEmailInvalid(err) ||
|
||||||
db.IsErrNamePatternNotAllowed(err) {
|
db.IsErrNamePatternNotAllowed(err) {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -148,7 +149,7 @@ func CreateUser(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !user_model.IsEmailDomainAllowed(u.Email) {
|
if !validation.IsEmailDomainAllowed(u.Email) {
|
||||||
ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", u.Email))
|
ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", u.Email))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +225,7 @@ func EditUser(ctx *context.APIContext) {
|
||||||
if form.Email != nil {
|
if form.Email != nil {
|
||||||
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
|
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
|
case validation.IsErrEmailCharIsNotSupported(err), validation.IsErrEmailInvalid(err):
|
||||||
ctx.Error(http.StatusBadRequest, "EmailInvalid", err)
|
ctx.Error(http.StatusBadRequest, "EmailInvalid", err)
|
||||||
case user_model.IsErrEmailAlreadyUsed(err):
|
case user_model.IsErrEmailAlreadyUsed(err):
|
||||||
ctx.Error(http.StatusBadRequest, "EmailUsed", err)
|
ctx.Error(http.StatusBadRequest, "EmailUsed", err)
|
||||||
|
@ -234,7 +235,7 @@ func EditUser(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !user_model.IsEmailDomainAllowed(*form.Email) {
|
if !validation.IsEmailDomainAllowed(*form.Email) {
|
||||||
ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email))
|
ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/validation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
@ -66,12 +67,12 @@ func AddEmail(ctx *context.APIContext) {
|
||||||
if err := user_service.AddEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
|
if err := user_service.AddEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
|
||||||
if user_model.IsErrEmailAlreadyUsed(err) {
|
if user_model.IsErrEmailAlreadyUsed(err) {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
|
ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
|
||||||
} else if user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) {
|
} else if validation.IsErrEmailCharIsNotSupported(err) || validation.IsErrEmailInvalid(err) {
|
||||||
email := ""
|
email := ""
|
||||||
if typedError, ok := err.(user_model.ErrEmailInvalid); ok {
|
if typedError, ok := err.(validation.ErrEmailInvalid); ok {
|
||||||
email = typedError.Email
|
email = typedError.Email
|
||||||
}
|
}
|
||||||
if typedError, ok := err.(user_model.ErrEmailCharIsNotSupported); ok {
|
if typedError, ok := err.(validation.ErrEmailCharIsNotSupported); ok {
|
||||||
email = typedError.Email
|
email = typedError.Email
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ 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/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/modules/validation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/web/explore"
|
"code.gitea.io/gitea/routers/web/explore"
|
||||||
user_setting "code.gitea.io/gitea/routers/web/user/setting"
|
user_setting "code.gitea.io/gitea/routers/web/user/setting"
|
||||||
|
@ -185,7 +186,7 @@ func NewUserPost(ctx *context.Context) {
|
||||||
case user_model.IsErrEmailAlreadyUsed(err):
|
case user_model.IsErrEmailAlreadyUsed(err):
|
||||||
ctx.Data["Err_Email"] = true
|
ctx.Data["Err_Email"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form)
|
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form)
|
||||||
case user_model.IsErrEmailInvalid(err), user_model.IsErrEmailCharIsNotSupported(err):
|
case validation.IsErrEmailInvalid(err), validation.IsErrEmailCharIsNotSupported(err):
|
||||||
ctx.Data["Err_Email"] = true
|
ctx.Data["Err_Email"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
|
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form)
|
||||||
case db.IsErrNameReserved(err):
|
case db.IsErrNameReserved(err):
|
||||||
|
@ -203,7 +204,7 @@ func NewUserPost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !user_model.IsEmailDomainAllowed(u.Email) {
|
if !validation.IsEmailDomainAllowed(u.Email) {
|
||||||
ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", u.Email))
|
ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", u.Email))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,7 +415,7 @@ func EditUserPost(ctx *context.Context) {
|
||||||
if form.Email != "" {
|
if form.Email != "" {
|
||||||
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil {
|
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
|
case validation.IsErrEmailCharIsNotSupported(err), validation.IsErrEmailInvalid(err):
|
||||||
ctx.Data["Err_Email"] = true
|
ctx.Data["Err_Email"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
|
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form)
|
||||||
case user_model.IsErrEmailAlreadyUsed(err):
|
case user_model.IsErrEmailAlreadyUsed(err):
|
||||||
|
@ -425,7 +426,7 @@ func EditUserPost(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !user_model.IsEmailDomainAllowed(form.Email) {
|
if !validation.IsEmailDomainAllowed(form.Email) {
|
||||||
ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", form.Email))
|
ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", form.Email))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ 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/validation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
auth_service "code.gitea.io/gitea/services/auth"
|
auth_service "code.gitea.io/gitea/services/auth"
|
||||||
|
@ -575,10 +576,10 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us
|
||||||
case user_model.IsErrEmailAlreadyUsed(err):
|
case user_model.IsErrEmailAlreadyUsed(err):
|
||||||
ctx.Data["Err_Email"] = true
|
ctx.Data["Err_Email"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form)
|
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form)
|
||||||
case user_model.IsErrEmailCharIsNotSupported(err):
|
case validation.IsErrEmailCharIsNotSupported(err):
|
||||||
ctx.Data["Err_Email"] = true
|
ctx.Data["Err_Email"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
|
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
|
||||||
case user_model.IsErrEmailInvalid(err):
|
case validation.IsErrEmailInvalid(err):
|
||||||
ctx.Data["Err_Email"] = true
|
ctx.Data["Err_Email"] = true
|
||||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
|
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form)
|
||||||
case db.IsErrNameReserved(err):
|
case db.IsErrNameReserved(err):
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"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/validation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
@ -131,7 +132,7 @@ func TeamsAction(ctx *context.Context) {
|
||||||
u, err = user_model.GetUserByName(ctx, uname)
|
u, err = user_model.GetUserByName(ctx, uname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if user_model.IsErrUserNotExist(err) {
|
if user_model.IsErrUserNotExist(err) {
|
||||||
if setting.MailService != nil && user_model.ValidateEmail(uname) == nil {
|
if setting.MailService != nil && validation.ValidateEmail(uname) == nil {
|
||||||
if err := org_service.CreateTeamInvite(ctx, ctx.Doer, ctx.Org.Team, uname); err != nil {
|
if err := org_service.CreateTeamInvite(ctx, ctx.Doer, ctx.Org.Team, uname); err != nil {
|
||||||
if org_model.IsErrTeamInviteAlreadyExist(err) {
|
if org_model.IsErrTeamInviteAlreadyExist(err) {
|
||||||
ctx.Flash.Error(ctx.Tr("form.duplicate_invite_to_team"))
|
ctx.Flash.Error(ctx.Tr("form.duplicate_invite_to_team"))
|
||||||
|
|
|
@ -17,6 +17,7 @@ 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/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/validation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/auth"
|
"code.gitea.io/gitea/services/auth"
|
||||||
"code.gitea.io/gitea/services/auth/source/db"
|
"code.gitea.io/gitea/services/auth/source/db"
|
||||||
|
@ -205,7 +206,7 @@ func EmailPost(ctx *context.Context) {
|
||||||
loadAccountData(ctx)
|
loadAccountData(ctx)
|
||||||
|
|
||||||
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form)
|
ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form)
|
||||||
} else if user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) {
|
} else if validation.IsErrEmailCharIsNotSupported(err) || validation.IsErrEmailInvalid(err) {
|
||||||
loadAccountData(ctx)
|
loadAccountData(ctx)
|
||||||
|
|
||||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsAccount, &form)
|
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsAccount, &form)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/auth/pam"
|
"code.gitea.io/gitea/modules/auth/pam"
|
||||||
"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/validation"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
@ -39,13 +40,13 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
||||||
if idx > -1 {
|
if idx > -1 {
|
||||||
username = pamLogin[:idx]
|
username = pamLogin[:idx]
|
||||||
}
|
}
|
||||||
if user_model.ValidateEmail(email) != nil {
|
if validation.ValidateEmail(email) != nil {
|
||||||
if source.EmailDomain != "" {
|
if source.EmailDomain != "" {
|
||||||
email = fmt.Sprintf("%s@%s", username, source.EmailDomain)
|
email = fmt.Sprintf("%s@%s", username, source.EmailDomain)
|
||||||
} else {
|
} else {
|
||||||
email = fmt.Sprintf("%s@%s", username, setting.Service.NoReplyAddress)
|
email = fmt.Sprintf("%s@%s", username, setting.Service.NoReplyAddress)
|
||||||
}
|
}
|
||||||
if user_model.ValidateEmail(email) != nil {
|
if validation.ValidateEmail(email) != nil {
|
||||||
email = uuid.New().String() + "@localhost"
|
email = uuid.New().String() + "@localhost"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/user"
|
"code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/validation"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
@ -31,7 +32,7 @@ func iterateUserAccounts(ctx context.Context, each func(*user.User) error) error
|
||||||
func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error {
|
func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error {
|
||||||
// We could use quirky SQL to get all users that start without a [a-zA-Z0-9], but that would mean
|
// We could use quirky SQL to get all users that start without a [a-zA-Z0-9], but that would mean
|
||||||
// DB provider-specific SQL and only works _now_. So instead we iterate through all user accounts
|
// DB provider-specific SQL and only works _now_. So instead we iterate through all user accounts
|
||||||
// and use the user.ValidateEmail function to be future-proof.
|
// and use the validation.ValidateEmail function to be future-proof.
|
||||||
var invalidUserCount int64
|
var invalidUserCount int64
|
||||||
if err := iterateUserAccounts(ctx, func(u *user.User) error {
|
if err := iterateUserAccounts(ctx, func(u *user.User) error {
|
||||||
// Only check for users, skip
|
// Only check for users, skip
|
||||||
|
@ -39,7 +40,7 @@ func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := user.ValidateEmail(u.Email); err != nil {
|
if err := validation.ValidateEmail(u.Email); err != nil {
|
||||||
invalidUserCount++
|
invalidUserCount++
|
||||||
logger.Warn("User[id=%d name=%q] have not a valid e-mail: %v", u.ID, u.Name, err)
|
logger.Warn("User[id=%d name=%q] have not a valid e-mail: %v", u.ID, u.Name, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ type AdminCreateUserForm struct {
|
||||||
LoginType string `binding:"Required"`
|
LoginType string `binding:"Required"`
|
||||||
LoginName string
|
LoginName string
|
||||||
UserName string `binding:"Required;Username;MaxSize(40)"`
|
UserName string `binding:"Required;Username;MaxSize(40)"`
|
||||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
Email string `binding:"Required;EmailForAdmin;MaxSize(254)"`
|
||||||
Password string `binding:"MaxSize(255)"`
|
Password string `binding:"MaxSize(255)"`
|
||||||
SendNotify bool
|
SendNotify bool
|
||||||
MustChangePassword bool
|
MustChangePassword bool
|
||||||
|
@ -37,7 +37,7 @@ type AdminEditUserForm struct {
|
||||||
UserName string `binding:"Username;MaxSize(40)"`
|
UserName string `binding:"Username;MaxSize(40)"`
|
||||||
LoginName string
|
LoginName string
|
||||||
FullName string `binding:"MaxSize(100)"`
|
FullName string `binding:"MaxSize(100)"`
|
||||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
Email string `binding:"Required;EmailForAdmin;MaxSize(254)"`
|
||||||
Password string `binding:"MaxSize(255)"`
|
Password string `binding:"MaxSize(255)"`
|
||||||
Website string `binding:"ValidUrl;MaxSize(255)"`
|
Website string `binding:"ValidUrl;MaxSize(255)"`
|
||||||
Location string `binding:"MaxSize(50)"`
|
Location string `binding:"MaxSize(50)"`
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/validation"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.
|
||||||
// domains in the whitelist or if it doesn't match any of
|
// domains in the whitelist or if it doesn't match any of
|
||||||
// domains in the blocklist, if any such list is not empty.
|
// domains in the blocklist, if any such list is not empty.
|
||||||
func (f *RegisterForm) IsEmailDomainAllowed() bool {
|
func (f *RegisterForm) IsEmailDomainAllowed() bool {
|
||||||
return user_model.IsEmailDomainAllowed(f.Email)
|
return validation.IsEmailDomainAllowed(f.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustChangePasswordForm form for updating your password after account creation
|
// MustChangePasswordForm form for updating your password after account creation
|
||||||
|
@ -258,7 +258,7 @@ const (
|
||||||
type AvatarForm struct {
|
type AvatarForm struct {
|
||||||
Source string
|
Source string
|
||||||
Avatar *multipart.FileHeader
|
Avatar *multipart.FileHeader
|
||||||
Gravatar string `binding:"OmitEmpty;Email;MaxSize(254)"`
|
Gravatar string `binding:"OmitEmpty;EmailWithAllowedDomain;MaxSize(254)"`
|
||||||
Federavatar bool
|
Federavatar bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +270,7 @@ func (f *AvatarForm) Validate(req *http.Request, errs binding.Errors) binding.Er
|
||||||
|
|
||||||
// AddEmailForm form for adding new email
|
// AddEmailForm form for adding new email
|
||||||
type AddEmailForm struct {
|
type AddEmailForm struct {
|
||||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
Email string `binding:"Required;EmailWithAllowedDomain;MaxSize(254)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the fields
|
// Validate validates the fields
|
||||||
|
|
|
@ -27,7 +27,7 @@ func (f *SignInOpenIDForm) Validate(req *http.Request, errs binding.Errors) bind
|
||||||
// SignUpOpenIDForm form for signin up with OpenID
|
// SignUpOpenIDForm form for signin up with OpenID
|
||||||
type SignUpOpenIDForm struct {
|
type SignUpOpenIDForm struct {
|
||||||
UserName string `binding:"Required;Username;MaxSize(40)"`
|
UserName string `binding:"Required;Username;MaxSize(40)"`
|
||||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
Email string `binding:"Required;EmailWithAllowedDomain;MaxSize(254)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the fields
|
// Validate validates the fields
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"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/validation"
|
||||||
"code.gitea.io/gitea/services/mailer"
|
"code.gitea.io/gitea/services/mailer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ func AdminAddOrSetPrimaryEmailAddress(ctx context.Context, u *user_model.User, e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := user_model.ValidateEmailForAdmin(emailStr); err != nil {
|
if err := validation.ValidateEmailForAdmin(emailStr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +75,7 @@ func ReplacePrimaryEmailAddress(ctx context.Context, u *user_model.User, emailSt
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := user_model.ValidateEmail(emailStr); err != nil {
|
if err := validation.ValidateEmail(emailStr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +120,7 @@ func ReplacePrimaryEmailAddress(ctx context.Context, u *user_model.User, emailSt
|
||||||
|
|
||||||
func AddEmailAddresses(ctx context.Context, u *user_model.User, emails []string) error {
|
func AddEmailAddresses(ctx context.Context, u *user_model.User, emails []string) error {
|
||||||
for _, emailStr := range emails {
|
for _, emailStr := range emails {
|
||||||
if err := user_model.ValidateEmail(emailStr); err != nil {
|
if err := validation.ValidateEmail(emailStr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue