Refactor user package (#33423)

and avoid global variables
pull/31840/merge
wxiaoguang 2 days ago committed by GitHub
parent a9577e0808
commit 8c4f0f02ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -7,6 +7,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -321,6 +322,11 @@ func valuesUser(m map[int64]*user_model.User) []*user_model.User {
return values return values
} }
// newMigrationOriginalUser creates and returns a fake user for external user
func newMigrationOriginalUser(name string) *user_model.User {
return &user_model.User{ID: 0, Name: name, LowerName: strings.ToLower(name)}
}
// LoadUsers loads reactions' all users // LoadUsers loads reactions' all users
func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) ([]*user_model.User, error) { func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) ([]*user_model.User, error) {
if len(list) == 0 { if len(list) == 0 {
@ -338,7 +344,7 @@ func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Reposit
for _, reaction := range list { for _, reaction := range list {
if reaction.OriginalAuthor != "" { if reaction.OriginalAuthor != "" {
reaction.User = user_model.NewReplaceUser(fmt.Sprintf("%s(%s)", reaction.OriginalAuthor, repo.OriginalServiceType.Name())) reaction.User = newMigrationOriginalUser(fmt.Sprintf("%s(%s)", reaction.OriginalAuthor, repo.OriginalServiceType.Name()))
} else if user, ok := userMaps[reaction.UserID]; ok { } else if user, ok := userMaps[reaction.UserID]; ok {
reaction.User = user reaction.User = user
} else { } else {

@ -8,7 +8,6 @@ import (
"context" "context"
"fmt" "fmt"
"net/mail" "net/mail"
"regexp"
"strings" "strings"
"time" "time"
@ -153,8 +152,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 // ValidateEmail check if email is a valid & allowed address
func ValidateEmail(email string) error { func ValidateEmail(email string) error {
if err := validateEmailBasic(email); err != nil { if err := validateEmailBasic(email); err != nil {
@ -514,7 +511,7 @@ func validateEmailBasic(email string) error {
return ErrEmailInvalid{email} return ErrEmailInvalid{email}
} }
if !emailRegexp.MatchString(email) { if !globalVars().emailRegexp.MatchString(email) {
return ErrEmailCharIsNotSupported{email} return ErrEmailCharIsNotSupported{email}
} }

@ -11,9 +11,6 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
// ErrOpenIDNotExist openid is not known
var ErrOpenIDNotExist = util.NewNotExistErrorf("OpenID is unknown")
// UserOpenID is the list of all OpenID identities of a user. // UserOpenID is the list of all OpenID identities of a user.
// Since this is a middle table, name it OpenID is not suitable, so we ignore the lint here // Since this is a middle table, name it OpenID is not suitable, so we ignore the lint here
type UserOpenID struct { //revive:disable-line:exported type UserOpenID struct { //revive:disable-line:exported
@ -99,7 +96,7 @@ func DeleteUserOpenID(ctx context.Context, openid *UserOpenID) (err error) {
if err != nil { if err != nil {
return err return err
} else if deleted != 1 { } else if deleted != 1 {
return ErrOpenIDNotExist return util.NewNotExistErrorf("OpenID is unknown")
} }
return nil return nil
} }

@ -14,6 +14,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"sync"
"time" "time"
"unicode" "unicode"
@ -417,19 +418,9 @@ func (u *User) DisplayName() string {
return u.Name return u.Name
} }
var emailToReplacer = strings.NewReplacer(
"\n", "",
"\r", "",
"<", "",
">", "",
",", "",
":", "",
";", "",
)
// EmailTo returns a string suitable to be put into a e-mail `To:` header. // EmailTo returns a string suitable to be put into a e-mail `To:` header.
func (u *User) EmailTo() string { func (u *User) EmailTo() string {
sanitizedDisplayName := emailToReplacer.Replace(u.DisplayName()) sanitizedDisplayName := globalVars().emailToReplacer.Replace(u.DisplayName())
// should be an edge case but nice to have // should be an edge case but nice to have
if sanitizedDisplayName == u.Email { if sanitizedDisplayName == u.Email {
@ -526,28 +517,52 @@ func GetUserSalt() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
// Returns a 32 bytes long string. // Returns a 32-byte long string.
return hex.EncodeToString(rBytes), nil return hex.EncodeToString(rBytes), nil
} }
// Note: The set of characters here can safely expand without a breaking change, type globalVarsStruct struct {
// but characters removed from this set can cause user account linking to break customCharsReplacement *strings.Replacer
var ( removeCharsRE *regexp.Regexp
customCharsReplacement = strings.NewReplacer("Æ", "AE") transformDiacritics transform.Transformer
removeCharsRE = regexp.MustCompile("['`´]") replaceCharsHyphenRE *regexp.Regexp
transformDiacritics = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) emailToReplacer *strings.Replacer
replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`) emailRegexp *regexp.Regexp
) }
var globalVars = sync.OnceValue(func() *globalVarsStruct {
return &globalVarsStruct{
// Note: The set of characters here can safely expand without a breaking change,
// but characters removed from this set can cause user account linking to break
customCharsReplacement: strings.NewReplacer("Æ", "AE"),
removeCharsRE: regexp.MustCompile("['`´]"),
transformDiacritics: transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC),
replaceCharsHyphenRE: regexp.MustCompile(`[\s~+]`),
emailToReplacer: strings.NewReplacer(
"\n", "",
"\r", "",
"<", "",
">", "",
",", "",
":", "",
";", "",
),
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])?)*$"),
}
})
// NormalizeUserName only takes the name part if it is an email address, transforms it diacritics to ASCII characters. // NormalizeUserName only takes the name part if it is an email address, transforms it diacritics to ASCII characters.
// It returns a string with the single-quotes removed, and any other non-supported username characters are replaced with a `-` character // It returns a string with the single-quotes removed, and any other non-supported username characters are replaced with a `-` character
func NormalizeUserName(s string) (string, error) { func NormalizeUserName(s string) (string, error) {
vars := globalVars()
s, _, _ = strings.Cut(s, "@") s, _, _ = strings.Cut(s, "@")
strDiacriticsRemoved, n, err := transform.String(transformDiacritics, customCharsReplacement.Replace(s)) strDiacriticsRemoved, n, err := transform.String(vars.transformDiacritics, vars.customCharsReplacement.Replace(s))
if err != nil { if err != nil {
return "", fmt.Errorf("failed to normalize the string of provided username %q at position %d", s, n) return "", fmt.Errorf("failed to normalize the string of provided username %q at position %d", s, n)
} }
return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil return vars.replaceCharsHyphenRE.ReplaceAllLiteralString(vars.removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil
} }
var ( var (

@ -10,9 +10,8 @@ import (
) )
const ( const (
GhostUserID = -1 GhostUserID = -1
GhostUserName = "Ghost" GhostUserName = "Ghost"
GhostUserLowerName = "ghost"
) )
// NewGhostUser creates and returns a fake user for someone has deleted their account. // NewGhostUser creates and returns a fake user for someone has deleted their account.
@ -20,10 +19,14 @@ func NewGhostUser() *User {
return &User{ return &User{
ID: GhostUserID, ID: GhostUserID,
Name: GhostUserName, Name: GhostUserName,
LowerName: GhostUserLowerName, LowerName: strings.ToLower(GhostUserName),
} }
} }
func IsGhostUserName(name string) bool {
return strings.EqualFold(name, GhostUserName)
}
// IsGhost check if user is fake user for a deleted account // IsGhost check if user is fake user for a deleted account
func (u *User) IsGhost() bool { func (u *User) IsGhost() bool {
if u == nil { if u == nil {
@ -32,20 +35,10 @@ func (u *User) IsGhost() bool {
return u.ID == GhostUserID && u.Name == GhostUserName return u.ID == GhostUserID && u.Name == GhostUserName
} }
// NewReplaceUser creates and returns a fake user for external user
func NewReplaceUser(name string) *User {
return &User{
ID: 0,
Name: name,
LowerName: strings.ToLower(name),
}
}
const ( const (
ActionsUserID = -2 ActionsUserID = -2
ActionsUserName = "gitea-actions" ActionsUserName = "gitea-actions"
ActionsFullName = "Gitea Actions" ActionsUserEmail = "teabot@gitea.io"
ActionsEmail = "teabot@gitea.io"
) )
// NewActionsUser creates and returns a fake user for running the actions. // NewActionsUser creates and returns a fake user for running the actions.
@ -55,8 +48,8 @@ func NewActionsUser() *User {
Name: ActionsUserName, Name: ActionsUserName,
LowerName: ActionsUserName, LowerName: ActionsUserName,
IsActive: true, IsActive: true,
FullName: ActionsFullName, FullName: "Gitea Actions",
Email: ActionsEmail, Email: ActionsUserEmail,
KeepEmailPrivate: true, KeepEmailPrivate: true,
LoginName: ActionsUserName, LoginName: ActionsUserName,
Type: UserTypeIndividual, Type: UserTypeIndividual,

@ -4,7 +4,6 @@
package user package user
import ( import (
"strings"
"time" "time"
"code.gitea.io/gitea/models/avatars" "code.gitea.io/gitea/models/avatars"
@ -27,7 +26,7 @@ func AvatarByUserName(ctx *context.Context) {
size := int(ctx.PathParamInt64("size")) size := int(ctx.PathParamInt64("size"))
var user *user_model.User var user *user_model.User
if strings.ToLower(userName) != user_model.GhostUserLowerName { if !user_model.IsGhostUserName(userName) {
var err error var err error
if user, err = user_model.GetUserByName(ctx, userName); err != nil { if user, err = user_model.GetUserByName(ctx, userName); err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {

Loading…
Cancel
Save