mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-21 17:34:24 +01:00
fix merge errors
This commit is contained in:
parent
109d09de1a
commit
a5caf6a1c1
|
@ -7,66 +7,17 @@ package user
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/base"
|
|
||||||
"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/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/validation"
|
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
|
||||||
"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
|
||||||
|
@ -158,22 +109,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 {
|
||||||
|
@ -309,23 +244,6 @@ func updateActivation(ctx context.Context, email *EmailAddress, activate bool) e
|
||||||
return UpdateUserCols(ctx, user, "rands")
|
return UpdateUserCols(ctx, user, "rands")
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyActiveEmailCode verifies active email code when active account
|
|
||||||
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
|
|
||||||
if user := GetVerifyUser(ctx, code); user != nil {
|
|
||||||
// time limit code
|
|
||||||
prefix := code[:base.TimeLimitCodeLength]
|
|
||||||
data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
|
|
||||||
|
|
||||||
if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) {
|
|
||||||
emailAddress := &EmailAddress{UID: user.ID, Email: email}
|
|
||||||
if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
|
|
||||||
return emailAddress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchEmailOrderBy is used to sort the results from SearchEmails()
|
// SearchEmailOrderBy is used to sort the results from SearchEmails()
|
||||||
type SearchEmailOrderBy string
|
type SearchEmailOrderBy string
|
||||||
|
|
||||||
|
@ -464,70 +382,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 {
|
|
||||||
return IsEmailDomainAllowedInternal(
|
|
||||||
email,
|
|
||||||
setting.Service.EmailDomainAllowList,
|
|
||||||
setting.Service.EmailDomainBlockList,
|
|
||||||
setting.Federation.Enabled,
|
|
||||||
setting.AppURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsEmailDomainAllowedInternal(
|
|
||||||
email string,
|
|
||||||
emailDomainAllowList []glob.Glob,
|
|
||||||
emailDomainBlockList []glob.Glob,
|
|
||||||
isFederation bool,
|
|
||||||
fqdn string,
|
|
||||||
) bool {
|
|
||||||
var result bool
|
|
||||||
|
|
||||||
if len(emailDomainAllowList) == 0 {
|
|
||||||
result = !validation.IsEmailDomainListed(emailDomainBlockList, email)
|
|
||||||
} else if isFederation {
|
|
||||||
localFqdn, err := url.ParseRequestURI(fqdn)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
globber, err := glob.Compile(localFqdn.Hostname(), ',')
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
emailDomainAllowList = append(emailDomainAllowList, globber)
|
|
||||||
result = validation.IsEmailDomainListed(emailDomainAllowList, email)
|
|
||||||
} else {
|
|
||||||
result = validation.IsEmailDomainListed(emailDomainAllowList, email)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,54 +12,10 @@ import (
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
|
||||||
"github.com/gobwas/glob"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEmailDomainAllowList(t *testing.T) {
|
|
||||||
res := user_model.IsEmailDomainAllowed("someuser@localhost.localdomain")
|
|
||||||
assert.True(t, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmailDomainAllowListInternal(t *testing.T) {
|
|
||||||
domain, _ := glob.Compile("domain.de", ',')
|
|
||||||
emailDomainAllowList := []glob.Glob{domain}
|
|
||||||
emailDomainBlockList := []glob.Glob{}
|
|
||||||
|
|
||||||
res := user_model.IsEmailDomainAllowedInternal(
|
|
||||||
"user@repo.domain.de",
|
|
||||||
emailDomainAllowList,
|
|
||||||
emailDomainBlockList,
|
|
||||||
false,
|
|
||||||
"https://repo.domain.de")
|
|
||||||
assert.False(t, res)
|
|
||||||
|
|
||||||
res = user_model.IsEmailDomainAllowedInternal(
|
|
||||||
"user@repo.domain.de",
|
|
||||||
emailDomainAllowList,
|
|
||||||
emailDomainBlockList,
|
|
||||||
true,
|
|
||||||
"xttps://repo")
|
|
||||||
assert.False(t, res)
|
|
||||||
|
|
||||||
res = user_model.IsEmailDomainAllowedInternal(
|
|
||||||
"user@repo.Domain.de",
|
|
||||||
emailDomainAllowList,
|
|
||||||
emailDomainBlockList,
|
|
||||||
true,
|
|
||||||
"https://repo.domain.de")
|
|
||||||
assert.True(t, res)
|
|
||||||
|
|
||||||
res = user_model.IsEmailDomainAllowedInternal(
|
|
||||||
"user@repo.domain.de",
|
|
||||||
emailDomainAllowList,
|
|
||||||
emailDomainBlockList,
|
|
||||||
true,
|
|
||||||
"https://repo.domain.de")
|
|
||||||
assert.True(t, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetEmailAddresses(t *testing.T) {
|
func TestGetEmailAddresses(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
@ -173,64 +129,6 @@ func TestListEmails(t *testing.T) {
|
||||||
assert.Len(t, emails, 5)
|
assert.Len(t, emails, 5)
|
||||||
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())
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ package user_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -21,7 +22,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/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"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"
|
||||||
|
@ -321,7 +325,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) {
|
||||||
|
@ -603,6 +607,69 @@ func Test_ValidateUser(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateEmailAuthorizationCode(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.Service.ActiveCodeLives, 2)()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
code, err := user.GenerateEmailAuthorizationCode(db.DefaultContext, auth.UserActivation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
lookupKey, validator, ok := strings.Cut(code, ":")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
rawValidator, err := hex.DecodeString(validator)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.False(t, authToken.IsExpired())
|
||||||
|
assert.EqualValues(t, authToken.HashedValidator, auth.HashValidator(rawValidator))
|
||||||
|
|
||||||
|
authToken.Expiry = authToken.Expiry.Add(-int64(setting.Service.ActiveCodeLives) * 60)
|
||||||
|
assert.True(t, authToken.IsExpired())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyUserAuthorizationToken(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.Service.ActiveCodeLives, 2)()
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
code, err := user.GenerateEmailAuthorizationCode(db.DefaultContext, auth.UserActivation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
lookupKey, _, ok := strings.Cut(code, ":")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
t.Run("Wrong purpose", func(t *testing.T) {
|
||||||
|
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.PasswordReset, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Nil(t, u)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("No delete", func(t *testing.T) {
|
||||||
|
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, user.ID, u.ID)
|
||||||
|
|
||||||
|
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, authToken)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Delete", func(t *testing.T) {
|
||||||
|
u, err := user_model.VerifyUserAuthorizationToken(db.DefaultContext, code, auth.UserActivation, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.EqualValues(t, user.ID, u.ID)
|
||||||
|
|
||||||
|
authToken, err := auth.FindAuthToken(db.DefaultContext, lookupKey, auth.UserActivation)
|
||||||
|
require.ErrorIs(t, err, util.ErrNotExist)
|
||||||
|
assert.Nil(t, authToken)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func Test_NormalizeUserFromEmail(t *testing.T) {
|
func Test_NormalizeUserFromEmail(t *testing.T) {
|
||||||
oldSetting := setting.Service.AllowDotsInUsernames
|
oldSetting := setting.Service.AllowDotsInUsernames
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|
Loading…
Reference in a new issue