Skip to content

Commit 95e4342

Browse files
committed
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: [skip ci] Updated licenses and gitignores Show whether a PR is WIP inside popups (go-gitea#28975) Unify password changing and invalidate auth tokens (go-gitea#27625) Unify user update methods (go-gitea#28733) Do not render empty comments (go-gitea#29039)
2 parents bafab25 + 016c77a commit 95e4342

Some content is hidden

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

54 files changed

+1592
-1071
lines changed

cmd/admin_user_change_password.go

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
package cmd
55

66
import (
7-
"context"
87
"errors"
98
"fmt"
109

1110
user_model "code.gitea.io/gitea/models/user"
12-
pwd "code.gitea.io/gitea/modules/auth/password"
11+
"code.gitea.io/gitea/modules/auth/password"
12+
"code.gitea.io/gitea/modules/optional"
1313
"code.gitea.io/gitea/modules/setting"
14+
user_service "code.gitea.io/gitea/services/user"
1415

1516
"github.com/urfave/cli/v2"
1617
)
@@ -50,35 +51,32 @@ func runChangePassword(c *cli.Context) error {
5051
if err := initDB(ctx); err != nil {
5152
return err
5253
}
53-
if len(c.String("password")) < setting.MinPasswordLength {
54-
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
55-
}
5654

57-
if !pwd.IsComplexEnough(c.String("password")) {
58-
return errors.New("Password does not meet complexity requirements")
59-
}
60-
pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
61-
if err != nil {
62-
return err
63-
}
64-
if pwned {
65-
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
66-
}
67-
uname := c.String("username")
68-
user, err := user_model.GetUserByName(ctx, uname)
55+
user, err := user_model.GetUserByName(ctx, c.String("username"))
6956
if err != nil {
7057
return err
7158
}
72-
if err = user.SetPassword(c.String("password")); err != nil {
73-
return err
74-
}
7559

60+
var mustChangePassword optional.Option[bool]
7661
if c.IsSet("must-change-password") {
77-
user.MustChangePassword = c.Bool("must-change-password")
62+
mustChangePassword = optional.Some(c.Bool("must-change-password"))
7863
}
7964

80-
if err = user_model.UpdateUserCols(ctx, user, "must_change_password", "passwd", "passwd_hash_algo", "salt"); err != nil {
81-
return err
65+
opts := &user_service.UpdateAuthOptions{
66+
Password: optional.Some(c.String("password")),
67+
MustChangePassword: mustChangePassword,
68+
}
69+
if err := user_service.UpdateAuth(ctx, user, opts); err != nil {
70+
switch {
71+
case errors.Is(err, password.ErrMinLength):
72+
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
73+
case errors.Is(err, password.ErrComplexity):
74+
return errors.New("Password does not meet complexity requirements")
75+
case errors.Is(err, password.ErrIsPwned):
76+
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
77+
default:
78+
return err
79+
}
8280
}
8381

8482
fmt.Printf("%s's password has been successfully updated!\n", user.Name)

models/auth/auth_token.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ func DeleteAuthTokenByID(ctx context.Context, id string) error {
5454
return err
5555
}
5656

57+
func DeleteAuthTokensByUserID(ctx context.Context, uid int64) error {
58+
_, err := db.GetEngine(ctx).Where(builder.Eq{"user_id": uid}).Delete(&AuthToken{})
59+
return err
60+
}
61+
5762
func DeleteExpiredAuthTokens(ctx context.Context) error {
5863
_, err := db.GetEngine(ctx).Where(builder.Lt{"expires_unix": timeutil.TimeStampNow()}).Delete(&AuthToken{})
5964
return err

models/fixtures/email_address.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,11 @@
285285
lower_email: [email protected]
286286
is_activated: true
287287
is_primary: false
288+
289+
-
290+
id: 37
291+
uid: 37
292+
293+
lower_email: [email protected]
294+
is_activated: true
295+
is_primary: true

models/fixtures/user.yml

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,7 @@
10951095
allow_git_hook: false
10961096
allow_import_local: false
10971097
allow_create_organization: true
1098-
prohibit_login: true
1098+
prohibit_login: false
10991099
avatar: avatar29
11001100
avatar_email: [email protected]
11011101
use_custom_avatar: false
@@ -1332,3 +1332,40 @@
13321332
repo_admin_change_team_access: false
13331333
theme: ""
13341334
keep_activity_private: false
1335+
1336+
-
1337+
id: 37
1338+
lower_name: user37
1339+
name: user37
1340+
full_name: User 37
1341+
1342+
keep_email_private: false
1343+
email_notifications_preference: enabled
1344+
passwd: ZogKvWdyEx:password
1345+
passwd_hash_algo: dummy
1346+
must_change_password: false
1347+
login_source: 0
1348+
login_name: user37
1349+
type: 0
1350+
salt: ZogKvWdyEx
1351+
max_repo_creation: -1
1352+
is_active: true
1353+
is_admin: false
1354+
is_restricted: false
1355+
allow_git_hook: false
1356+
allow_import_local: false
1357+
allow_create_organization: true
1358+
prohibit_login: true
1359+
avatar: avatar29
1360+
avatar_email: [email protected]
1361+
use_custom_avatar: false
1362+
num_followers: 0
1363+
num_following: 0
1364+
num_stars: 0
1365+
num_repos: 0
1366+
num_teams: 0
1367+
num_members: 0
1368+
visibility: 0
1369+
repo_admin_change_team_access: false
1370+
theme: ""
1371+
keep_activity_private: false

models/user/email_address.go

Lines changed: 43 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,24 @@ func (email *EmailAddress) BeforeInsert() {
142142
}
143143
}
144144

145+
func InsertEmailAddress(ctx context.Context, email *EmailAddress) (*EmailAddress, error) {
146+
if err := db.Insert(ctx, email); err != nil {
147+
return nil, err
148+
}
149+
return email, nil
150+
}
151+
152+
func UpdateEmailAddress(ctx context.Context, email *EmailAddress) error {
153+
_, err := db.GetEngine(ctx).ID(email.ID).AllCols().Update(email)
154+
return err
155+
}
156+
145157
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])?)*$")
146158

147159
// ValidateEmail check if email is a allowed address
148160
func ValidateEmail(email string) error {
149161
if len(email) == 0 {
150-
return nil
162+
return ErrEmailInvalid{email}
151163
}
152164

153165
if !emailRegexp.MatchString(email) {
@@ -177,6 +189,36 @@ func ValidateEmail(email string) error {
177189
return nil
178190
}
179191

192+
func GetEmailAddressByEmail(ctx context.Context, email string) (*EmailAddress, error) {
193+
ea := &EmailAddress{}
194+
if has, err := db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(ea); err != nil {
195+
return nil, err
196+
} else if !has {
197+
return nil, ErrEmailAddressNotExist{email}
198+
}
199+
return ea, nil
200+
}
201+
202+
func GetEmailAddressOfUser(ctx context.Context, email string, uid int64) (*EmailAddress, error) {
203+
ea := &EmailAddress{}
204+
if has, err := db.GetEngine(ctx).Where("lower_email=? AND uid=?", strings.ToLower(email), uid).Get(ea); err != nil {
205+
return nil, err
206+
} else if !has {
207+
return nil, ErrEmailAddressNotExist{email}
208+
}
209+
return ea, nil
210+
}
211+
212+
func GetPrimaryEmailAddressOfUser(ctx context.Context, uid int64) (*EmailAddress, error) {
213+
ea := &EmailAddress{}
214+
if has, err := db.GetEngine(ctx).Where("uid=? AND is_primary=?", uid, true).Get(ea); err != nil {
215+
return nil, err
216+
} else if !has {
217+
return nil, ErrEmailAddressNotExist{}
218+
}
219+
return ea, nil
220+
}
221+
180222
// GetEmailAddresses returns all email addresses belongs to given user.
181223
func GetEmailAddresses(ctx context.Context, uid int64) ([]*EmailAddress, error) {
182224
emails := make([]*EmailAddress, 0, 5)
@@ -235,91 +277,6 @@ func IsEmailUsed(ctx context.Context, email string) (bool, error) {
235277
return db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
236278
}
237279

238-
// AddEmailAddress adds an email address to given user.
239-
func AddEmailAddress(ctx context.Context, email *EmailAddress) error {
240-
email.Email = strings.TrimSpace(email.Email)
241-
used, err := IsEmailUsed(ctx, email.Email)
242-
if err != nil {
243-
return err
244-
} else if used {
245-
return ErrEmailAlreadyUsed{email.Email}
246-
}
247-
248-
if err = ValidateEmail(email.Email); err != nil {
249-
return err
250-
}
251-
252-
return db.Insert(ctx, email)
253-
}
254-
255-
// AddEmailAddresses adds an email address to given user.
256-
func AddEmailAddresses(ctx context.Context, emails []*EmailAddress) error {
257-
if len(emails) == 0 {
258-
return nil
259-
}
260-
261-
// Check if any of them has been used
262-
for i := range emails {
263-
emails[i].Email = strings.TrimSpace(emails[i].Email)
264-
used, err := IsEmailUsed(ctx, emails[i].Email)
265-
if err != nil {
266-
return err
267-
} else if used {
268-
return ErrEmailAlreadyUsed{emails[i].Email}
269-
}
270-
if err = ValidateEmail(emails[i].Email); err != nil {
271-
return err
272-
}
273-
}
274-
275-
if err := db.Insert(ctx, emails); err != nil {
276-
return fmt.Errorf("Insert: %w", err)
277-
}
278-
279-
return nil
280-
}
281-
282-
// DeleteEmailAddress deletes an email address of given user.
283-
func DeleteEmailAddress(ctx context.Context, email *EmailAddress) (err error) {
284-
if email.IsPrimary {
285-
return ErrPrimaryEmailCannotDelete{Email: email.Email}
286-
}
287-
288-
var deleted int64
289-
// ask to check UID
290-
address := EmailAddress{
291-
UID: email.UID,
292-
}
293-
if email.ID > 0 {
294-
deleted, err = db.GetEngine(ctx).ID(email.ID).Delete(&address)
295-
} else {
296-
if email.Email != "" && email.LowerEmail == "" {
297-
email.LowerEmail = strings.ToLower(email.Email)
298-
}
299-
deleted, err = db.GetEngine(ctx).
300-
Where("lower_email=?", email.LowerEmail).
301-
Delete(&address)
302-
}
303-
304-
if err != nil {
305-
return err
306-
} else if deleted != 1 {
307-
return ErrEmailAddressNotExist{Email: email.Email}
308-
}
309-
return nil
310-
}
311-
312-
// DeleteEmailAddresses deletes multiple email addresses
313-
func DeleteEmailAddresses(ctx context.Context, emails []*EmailAddress) (err error) {
314-
for i := range emails {
315-
if err = DeleteEmailAddress(ctx, emails[i]); err != nil {
316-
return err
317-
}
318-
}
319-
320-
return nil
321-
}
322-
323280
// DeleteInactiveEmailAddresses deletes inactive email addresses
324281
func DeleteInactiveEmailAddresses(ctx context.Context) error {
325282
_, err := db.GetEngine(ctx).

models/user/email_address_test.go

Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -42,96 +42,6 @@ func TestIsEmailUsed(t *testing.T) {
4242
assert.False(t, isExist)
4343
}
4444

45-
func TestAddEmailAddress(t *testing.T) {
46-
assert.NoError(t, unittest.PrepareTestDatabase())
47-
48-
assert.NoError(t, user_model.AddEmailAddress(db.DefaultContext, &user_model.EmailAddress{
49-
50-
LowerEmail: "[email protected]",
51-
IsPrimary: true,
52-
IsActivated: true,
53-
}))
54-
55-
// ErrEmailAlreadyUsed
56-
err := user_model.AddEmailAddress(db.DefaultContext, &user_model.EmailAddress{
57-
58-
LowerEmail: "[email protected]",
59-
})
60-
assert.Error(t, err)
61-
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
62-
}
63-
64-
func TestAddEmailAddresses(t *testing.T) {
65-
assert.NoError(t, unittest.PrepareTestDatabase())
66-
67-
// insert multiple email address
68-
emails := make([]*user_model.EmailAddress, 2)
69-
emails[0] = &user_model.EmailAddress{
70-
71-
LowerEmail: "[email protected]",
72-
IsActivated: true,
73-
}
74-
emails[1] = &user_model.EmailAddress{
75-
76-
LowerEmail: "[email protected]",
77-
IsActivated: true,
78-
}
79-
assert.NoError(t, user_model.AddEmailAddresses(db.DefaultContext, emails))
80-
81-
// ErrEmailAlreadyUsed
82-
err := user_model.AddEmailAddresses(db.DefaultContext, emails)
83-
assert.Error(t, err)
84-
assert.True(t, user_model.IsErrEmailAlreadyUsed(err))
85-
}
86-
87-
func TestDeleteEmailAddress(t *testing.T) {
88-
assert.NoError(t, unittest.PrepareTestDatabase())
89-
90-
assert.NoError(t, user_model.DeleteEmailAddress(db.DefaultContext, &user_model.EmailAddress{
91-
UID: int64(1),
92-
ID: int64(33),
93-
94-
LowerEmail: "[email protected]",
95-
}))
96-
97-
assert.NoError(t, user_model.DeleteEmailAddress(db.DefaultContext, &user_model.EmailAddress{
98-
UID: int64(1),
99-
100-
LowerEmail: "[email protected]",
101-
}))
102-
103-
// Email address does not exist
104-
err := user_model.DeleteEmailAddress(db.DefaultContext, &user_model.EmailAddress{
105-
UID: int64(1),
106-
107-
LowerEmail: "[email protected]",
108-
})
109-
assert.Error(t, err)
110-
}
111-
112-
func TestDeleteEmailAddresses(t *testing.T) {
113-
assert.NoError(t, unittest.PrepareTestDatabase())
114-
115-
// delete multiple email address
116-
emails := make([]*user_model.EmailAddress, 2)
117-
emails[0] = &user_model.EmailAddress{
118-
UID: int64(2),
119-
ID: int64(3),
120-
121-
LowerEmail: "[email protected]",
122-
}
123-
emails[1] = &user_model.EmailAddress{
124-
UID: int64(2),
125-
126-
LowerEmail: "[email protected]",
127-
}
128-
assert.NoError(t, user_model.DeleteEmailAddresses(db.DefaultContext, emails))
129-
130-
// ErrEmailAlreadyUsed
131-
err := user_model.DeleteEmailAddresses(db.DefaultContext, emails)
132-
assert.Error(t, err)
133-
}
134-
13545
func TestMakeEmailPrimary(t *testing.T) {
13646
assert.NoError(t, unittest.PrepareTestDatabase())
13747

0 commit comments

Comments
 (0)