Skip email domain check when admin users adds user manually (#29522)

Fix #27457

Administrators should be able to manually create any user even if the
user's email address is not in `EMAIL_DOMAIN_ALLOWLIST`.

(cherry picked from commit 4fd9c56ed09b31e2f6164a5f534a31c6624d0478)
This commit is contained in:
Zettat123 2024-03-05 13:55:47 +08:00 committed by Earl Warren
parent 907c3d0c20
commit b6057a34db
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
5 changed files with 93 additions and 32 deletions

View file

@ -157,37 +157,18 @@ func UpdateEmailAddress(ctx context.Context, email *EmailAddress) error {
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])?)*$") 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 allowed address // ValidateEmail check if email is a valid & allowed address
func ValidateEmail(email string) error { func ValidateEmail(email string) error {
if len(email) == 0 { if err := validateEmailBasic(email); err != nil {
return ErrEmailInvalid{email} return err
} }
return validateEmailDomain(email)
}
if !emailRegexp.MatchString(email) { // ValidateEmailForAdmin check if email is a valid address when admins manually add users
return ErrEmailCharIsNotSupported{email} func ValidateEmailForAdmin(email string) error {
} return validateEmailBasic(email)
// In this case we do not need to check the email domain
if email[0] == '-' {
return ErrEmailInvalid{email}
}
if _, err := mail.ParseAddress(email); err != nil {
return ErrEmailInvalid{email}
}
// if there is no allow list, then check email against block list
if len(setting.Service.EmailDomainAllowList) == 0 &&
validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email) {
return ErrEmailInvalid{email}
}
// if there is an allow list, then check email against allow list
if len(setting.Service.EmailDomainAllowList) > 0 &&
!validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email) {
return ErrEmailInvalid{email}
}
return nil
} }
func GetEmailAddressByEmail(ctx context.Context, email string) (*EmailAddress, error) { func GetEmailAddressByEmail(ctx context.Context, email string) (*EmailAddress, error) {
@ -543,3 +524,41 @@ 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 there is no allow list, then check email against block list
if len(setting.Service.EmailDomainAllowList) == 0 &&
validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email) {
return ErrEmailInvalid{email}
}
// if there is an allow list, then check email against allow list
if len(setting.Service.EmailDomainAllowList) > 0 &&
!validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email) {
return ErrEmailInvalid{email}
}
return nil
}

View file

@ -598,6 +598,16 @@ type CreateUserOverwriteOptions struct {
// CreateUser creates record of a new user. // CreateUser creates record of a new user.
func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
return createUser(ctx, u, false, overwriteDefault...)
}
// AdminCreateUser is used by admins to manually create users
func AdminCreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
return createUser(ctx, u, true, overwriteDefault...)
}
// createUser creates record of a new user.
func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) {
if err = IsUsableUsername(u.Name); err != nil { if err = IsUsableUsername(u.Name); err != nil {
return err return err
} }
@ -651,8 +661,14 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve
return err return err
} }
if err := ValidateEmail(u.Email); err != nil { if createdByAdmin {
return err if err := ValidateEmailForAdmin(u.Email); err != nil {
return err
}
} else {
if err := ValidateEmail(u.Email); err != nil {
return err
}
} }
ctx, committer, err := db.TxContext(ctx) ctx, committer, err := db.TxContext(ctx)

View file

@ -133,7 +133,7 @@ func CreateUser(ctx *context.APIContext) {
u.UpdatedUnix = u.CreatedUnix u.UpdatedUnix = u.CreatedUnix
} }
if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil { if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil {
if user_model.IsErrUserAlreadyExist(err) || if user_model.IsErrUserAlreadyExist(err) ||
user_model.IsErrEmailAlreadyUsed(err) || user_model.IsErrEmailAlreadyUsed(err) ||
db.IsErrNameReserved(err) || db.IsErrNameReserved(err) ||

View file

@ -177,7 +177,7 @@ func NewUserPost(ctx *context.Context) {
u.MustChangePassword = form.MustChangePassword u.MustChangePassword = form.MustChangePassword
} }
if err := user_model.CreateUser(ctx, u, overwriteDefault); err != nil { if err := user_model.AdminCreateUser(ctx, u, overwriteDefault); err != nil {
switch { switch {
case user_model.IsErrUserAlreadyExist(err): case user_model.IsErrUserAlreadyExist(err):
ctx.Data["Err_UserName"] = true ctx.Data["Err_UserName"] = true

View file

@ -14,9 +14,11 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/gobwas/glob"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -333,3 +335,27 @@ func TestAPICron(t *testing.T) {
} }
}) })
} }
func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) {
defer tests.PrepareTestEnv(t)()
setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")}
defer func() {
setting.Service.EmailDomainAllowList = []glob.Glob{}
}()
adminUsername := "user1"
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{
"email": "allowedUser1@example1.org",
"login_name": "allowedUser1",
"username": "allowedUser1",
"password": "allowedUser1_pass",
"must_change_password": "true",
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusCreated)
req = NewRequest(t, "DELETE", "/api/v1/admin/users/allowedUser1").AddTokenAuth(token)
MakeRequest(t, req, http.StatusNoContent)
}