Skip to content

Commit 1125967

Browse files
committed
Merge branch 'main' into lunny/fix_webhook
2 parents f7d1be7 + 48183d2 commit 1125967

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1451
-577
lines changed

models/issues/reaction.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bytes"
88
"context"
99
"fmt"
10+
"strings"
1011

1112
"code.gitea.io/gitea/models/db"
1213
repo_model "code.gitea.io/gitea/models/repo"
@@ -321,6 +322,11 @@ func valuesUser(m map[int64]*user_model.User) []*user_model.User {
321322
return values
322323
}
323324

325+
// newMigrationOriginalUser creates and returns a fake user for external user
326+
func newMigrationOriginalUser(name string) *user_model.User {
327+
return &user_model.User{ID: 0, Name: name, LowerName: strings.ToLower(name)}
328+
}
329+
324330
// LoadUsers loads reactions' all users
325331
func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Repository) ([]*user_model.User, error) {
326332
if len(list) == 0 {
@@ -338,7 +344,7 @@ func (list ReactionList) LoadUsers(ctx context.Context, repo *repo_model.Reposit
338344

339345
for _, reaction := range list {
340346
if reaction.OriginalAuthor != "" {
341-
reaction.User = user_model.NewReplaceUser(fmt.Sprintf("%s(%s)", reaction.OriginalAuthor, repo.OriginalServiceType.Name()))
347+
reaction.User = newMigrationOriginalUser(fmt.Sprintf("%s(%s)", reaction.OriginalAuthor, repo.OriginalServiceType.Name()))
342348
} else if user, ok := userMaps[reaction.UserID]; ok {
343349
reaction.User = user
344350
} else {

models/user/avatar.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,30 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error {
3838

3939
u.Avatar = avatars.HashEmail(seed)
4040

41-
// Don't share the images so that we can delete them easily
42-
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
43-
if err := png.Encode(w, img); err != nil {
44-
log.Error("Encode: %v", err)
41+
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
42+
if err != nil {
43+
// If unable to Stat the avatar file (usually it means non-existing), then try to save a new one
44+
// Don't share the images so that we can delete them easily
45+
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
46+
if err := png.Encode(w, img); err != nil {
47+
log.Error("Encode: %v", err)
48+
}
49+
return nil
50+
}); err != nil {
51+
return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err)
4552
}
46-
return err
47-
}); err != nil {
48-
return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err)
4953
}
5054

5155
if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil {
5256
return err
5357
}
5458

55-
log.Info("New random avatar created: %d", u.ID)
5659
return nil
5760
}
5861

5962
// AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size
6063
func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
61-
if u.IsGhost() {
64+
if u.IsGhost() || u.IsGiteaActions() {
6265
return avatars.DefaultAvatarLink()
6366
}
6467

models/user/avatar_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
package user
55

66
import (
7+
"context"
8+
"io"
9+
"strings"
710
"testing"
811

912
"code.gitea.io/gitea/models/db"
13+
"code.gitea.io/gitea/models/unittest"
1014
"code.gitea.io/gitea/modules/setting"
15+
"code.gitea.io/gitea/modules/storage"
1116
"code.gitea.io/gitea/modules/test"
1217

1318
"github.com/stretchr/testify/assert"
19+
"github.com/stretchr/testify/require"
1420
)
1521

1622
func TestUserAvatarLink(t *testing.T) {
@@ -26,3 +32,37 @@ func TestUserAvatarLink(t *testing.T) {
2632
link = u.AvatarLink(db.DefaultContext)
2733
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
2834
}
35+
36+
func TestUserAvatarGenerate(t *testing.T) {
37+
assert.NoError(t, unittest.PrepareTestDatabase())
38+
var err error
39+
tmpDir := t.TempDir()
40+
storage.Avatars, err = storage.NewLocalStorage(context.Background(), &setting.Storage{Path: tmpDir})
41+
require.NoError(t, err)
42+
43+
u := unittest.AssertExistsAndLoadBean(t, &User{ID: 2})
44+
45+
// there was no avatar, generate a new one
46+
assert.Empty(t, u.Avatar)
47+
err = GenerateRandomAvatar(db.DefaultContext, u)
48+
require.NoError(t, err)
49+
assert.NotEmpty(t, u.Avatar)
50+
51+
// make sure the generated one exists
52+
oldAvatarPath := u.CustomAvatarRelativePath()
53+
_, err = storage.Avatars.Stat(u.CustomAvatarRelativePath())
54+
require.NoError(t, err)
55+
// and try to change its content
56+
_, err = storage.Avatars.Save(u.CustomAvatarRelativePath(), strings.NewReader("abcd"), 4)
57+
require.NoError(t, err)
58+
59+
// try to generate again
60+
err = GenerateRandomAvatar(db.DefaultContext, u)
61+
require.NoError(t, err)
62+
assert.Equal(t, oldAvatarPath, u.CustomAvatarRelativePath())
63+
f, err := storage.Avatars.Open(u.CustomAvatarRelativePath())
64+
require.NoError(t, err)
65+
defer f.Close()
66+
content, _ := io.ReadAll(f)
67+
assert.Equal(t, "abcd", string(content))
68+
}

models/user/email_address.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"context"
99
"fmt"
1010
"net/mail"
11-
"regexp"
1211
"strings"
1312
"time"
1413

@@ -153,8 +152,6 @@ func UpdateEmailAddress(ctx context.Context, email *EmailAddress) error {
153152
return err
154153
}
155154

156-
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])?)*$")
157-
158155
// ValidateEmail check if email is a valid & allowed address
159156
func ValidateEmail(email string) error {
160157
if err := validateEmailBasic(email); err != nil {
@@ -514,7 +511,7 @@ func validateEmailBasic(email string) error {
514511
return ErrEmailInvalid{email}
515512
}
516513

517-
if !emailRegexp.MatchString(email) {
514+
if !globalVars().emailRegexp.MatchString(email) {
518515
return ErrEmailCharIsNotSupported{email}
519516
}
520517

@@ -545,3 +542,13 @@ func IsEmailDomainAllowed(email string) bool {
545542

546543
return validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email)
547544
}
545+
546+
func GetActivatedEmailAddresses(ctx context.Context, uid int64) ([]string, error) {
547+
emails := make([]string, 0, 2)
548+
if err := db.GetEngine(ctx).Table("email_address").Select("email").
549+
Where("uid=? AND is_activated=?", uid, true).Asc("id").
550+
Find(&emails); err != nil {
551+
return nil, err
552+
}
553+
return emails, nil
554+
}

models/user/openid.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ import (
1111
"code.gitea.io/gitea/modules/util"
1212
)
1313

14-
// ErrOpenIDNotExist openid is not known
15-
var ErrOpenIDNotExist = util.NewNotExistErrorf("OpenID is unknown")
16-
1714
// UserOpenID is the list of all OpenID identities of a user.
1815
// Since this is a middle table, name it OpenID is not suitable, so we ignore the lint here
1916
type UserOpenID struct { //revive:disable-line:exported
@@ -99,7 +96,7 @@ func DeleteUserOpenID(ctx context.Context, openid *UserOpenID) (err error) {
9996
if err != nil {
10097
return err
10198
} else if deleted != 1 {
102-
return ErrOpenIDNotExist
99+
return util.NewNotExistErrorf("OpenID is unknown")
103100
}
104101
return nil
105102
}

models/user/user.go

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"path/filepath"
1515
"regexp"
1616
"strings"
17+
"sync"
1718
"time"
1819
"unicode"
1920

@@ -213,7 +214,7 @@ func (u *User) GetPlaceholderEmail() string {
213214
return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress)
214215
}
215216

216-
// GetEmail returns an noreply email, if the user has set to keep his
217+
// GetEmail returns a noreply email, if the user has set to keep his
217218
// email address private, otherwise the primary email address.
218219
func (u *User) GetEmail() string {
219220
if u.KeepEmailPrivate {
@@ -417,19 +418,9 @@ func (u *User) DisplayName() string {
417418
return u.Name
418419
}
419420

420-
var emailToReplacer = strings.NewReplacer(
421-
"\n", "",
422-
"\r", "",
423-
"<", "",
424-
">", "",
425-
",", "",
426-
":", "",
427-
";", "",
428-
)
429-
430421
// EmailTo returns a string suitable to be put into a e-mail `To:` header.
431422
func (u *User) EmailTo() string {
432-
sanitizedDisplayName := emailToReplacer.Replace(u.DisplayName())
423+
sanitizedDisplayName := globalVars().emailToReplacer.Replace(u.DisplayName())
433424

434425
// should be an edge case but nice to have
435426
if sanitizedDisplayName == u.Email {
@@ -526,28 +517,58 @@ func GetUserSalt() (string, error) {
526517
if err != nil {
527518
return "", err
528519
}
529-
// Returns a 32 bytes long string.
520+
// Returns a 32-byte long string.
530521
return hex.EncodeToString(rBytes), nil
531522
}
532523

533-
// Note: The set of characters here can safely expand without a breaking change,
534-
// but characters removed from this set can cause user account linking to break
535-
var (
536-
customCharsReplacement = strings.NewReplacer("Æ", "AE")
537-
removeCharsRE = regexp.MustCompile("['`´]")
538-
transformDiacritics = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
539-
replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`)
540-
)
524+
type globalVarsStruct struct {
525+
customCharsReplacement *strings.Replacer
526+
removeCharsRE *regexp.Regexp
527+
transformDiacritics transform.Transformer
528+
replaceCharsHyphenRE *regexp.Regexp
529+
emailToReplacer *strings.Replacer
530+
emailRegexp *regexp.Regexp
531+
systemUserNewFuncs map[int64]func() *User
532+
}
533+
534+
var globalVars = sync.OnceValue(func() *globalVarsStruct {
535+
return &globalVarsStruct{
536+
// Note: The set of characters here can safely expand without a breaking change,
537+
// but characters removed from this set can cause user account linking to break
538+
customCharsReplacement: strings.NewReplacer("Æ", "AE"),
539+
540+
removeCharsRE: regexp.MustCompile("['`´]"),
541+
transformDiacritics: transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC),
542+
replaceCharsHyphenRE: regexp.MustCompile(`[\s~+]`),
543+
544+
emailToReplacer: strings.NewReplacer(
545+
"\n", "",
546+
"\r", "",
547+
"<", "",
548+
">", "",
549+
",", "",
550+
":", "",
551+
";", "",
552+
),
553+
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])?)*$"),
554+
555+
systemUserNewFuncs: map[int64]func() *User{
556+
GhostUserID: NewGhostUser,
557+
ActionsUserID: NewActionsUser,
558+
},
559+
}
560+
})
541561

542562
// NormalizeUserName only takes the name part if it is an email address, transforms it diacritics to ASCII characters.
543563
// It returns a string with the single-quotes removed, and any other non-supported username characters are replaced with a `-` character
544564
func NormalizeUserName(s string) (string, error) {
565+
vars := globalVars()
545566
s, _, _ = strings.Cut(s, "@")
546-
strDiacriticsRemoved, n, err := transform.String(transformDiacritics, customCharsReplacement.Replace(s))
567+
strDiacriticsRemoved, n, err := transform.String(vars.transformDiacritics, vars.customCharsReplacement.Replace(s))
547568
if err != nil {
548569
return "", fmt.Errorf("failed to normalize the string of provided username %q at position %d", s, n)
549570
}
550-
return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil
571+
return vars.replaceCharsHyphenRE.ReplaceAllLiteralString(vars.removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil
551572
}
552573

553574
var (
@@ -963,30 +984,28 @@ func GetUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
963984
return users, err
964985
}
965986

966-
// GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0
987+
// GetPossibleUserByID returns the user if id > 0 or returns system user if id < 0
967988
func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) {
968-
switch id {
969-
case GhostUserID:
970-
return NewGhostUser(), nil
971-
case ActionsUserID:
972-
return NewActionsUser(), nil
973-
case 0:
989+
if id < 0 {
990+
if newFunc, ok := globalVars().systemUserNewFuncs[id]; ok {
991+
return newFunc(), nil
992+
}
993+
return nil, ErrUserNotExist{UID: id}
994+
} else if id == 0 {
974995
return nil, ErrUserNotExist{}
975-
default:
976-
return GetUserByID(ctx, id)
977996
}
997+
return GetUserByID(ctx, id)
978998
}
979999

980-
// GetPossibleUserByIDs returns the users if id > 0 or return system users if id < 0
1000+
// GetPossibleUserByIDs returns the users if id > 0 or returns system users if id < 0
9811001
func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
9821002
uniqueIDs := container.SetOf(ids...)
9831003
users := make([]*User, 0, len(ids))
9841004
_ = uniqueIDs.Remove(0)
985-
if uniqueIDs.Remove(GhostUserID) {
986-
users = append(users, NewGhostUser())
987-
}
988-
if uniqueIDs.Remove(ActionsUserID) {
989-
users = append(users, NewActionsUser())
1005+
for systemUID, newFunc := range globalVars().systemUserNewFuncs {
1006+
if uniqueIDs.Remove(systemUID) {
1007+
users = append(users, newFunc())
1008+
}
9901009
}
9911010
res, err := GetUserByIDs(ctx, uniqueIDs.Values())
9921011
if err != nil {
@@ -996,7 +1015,7 @@ func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) {
9961015
return users, nil
9971016
}
9981017

999-
// GetUserByNameCtx returns user by given name.
1018+
// GetUserByName returns user by given name.
10001019
func GetUserByName(ctx context.Context, name string) (*User, error) {
10011020
if len(name) == 0 {
10021021
return nil, ErrUserNotExist{Name: name}
@@ -1027,8 +1046,8 @@ func GetUserEmailsByNames(ctx context.Context, names []string) []string {
10271046
return mails
10281047
}
10291048

1030-
// GetMaileableUsersByIDs gets users from ids, but only if they can receive mails
1031-
func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([]*User, error) {
1049+
// GetMailableUsersByIDs gets users from ids, but only if they can receive mails
1050+
func GetMailableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([]*User, error) {
10321051
if len(ids) == 0 {
10331052
return nil, nil
10341053
}
@@ -1053,17 +1072,6 @@ func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([
10531072
Find(&ous)
10541073
}
10551074

1056-
// GetUserNamesByIDs returns usernames for all resolved users from a list of Ids.
1057-
func GetUserNamesByIDs(ctx context.Context, ids []int64) ([]string, error) {
1058-
unames := make([]string, 0, len(ids))
1059-
err := db.GetEngine(ctx).In("id", ids).
1060-
Table("user").
1061-
Asc("name").
1062-
Cols("name").
1063-
Find(&unames)
1064-
return unames, err
1065-
}
1066-
10671075
// GetUserNameByID returns username for the id
10681076
func GetUserNameByID(ctx context.Context, id int64) (string, error) {
10691077
var name string

0 commit comments

Comments
 (0)