mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Password Complexity Checks (#6230)
Add password complexity checks. The default settings require a lowercase, uppercase, number and a special character within passwords. Co-Authored-By: T-M-A <maxim.tkachenko@gmail.com> Co-Authored-By: Lanre Adelowo <adelowomailbox@gmail.com> Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-Authored-By: Lauris BH <lauris@nix.lv>
This commit is contained in:
		 Maxim Tkachenko
					Maxim Tkachenko
				
			
				
					committed by
					
						 zeripath
						zeripath
					
				
			
			
				
	
			
			
			 zeripath
						zeripath
					
				
			
						parent
						
							f9aba9ba0f
						
					
				
				
					commit
					db657192d0
				
			
							
								
								
									
										19
									
								
								cmd/admin.go
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								cmd/admin.go
									
									
									
									
									
								
							| @@ -13,9 +13,9 @@ import ( | |||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/auth/oauth2" | 	"code.gitea.io/gitea/modules/auth/oauth2" | ||||||
| 	"code.gitea.io/gitea/modules/generate" |  | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	pwd "code.gitea.io/gitea/modules/password" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
| 	"github.com/urfave/cli" | 	"github.com/urfave/cli" | ||||||
| @@ -233,7 +233,9 @@ func runChangePassword(c *cli.Context) error { | |||||||
| 	if err := initDB(); err != nil { | 	if err := initDB(); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	if !pwd.IsComplexEnough(c.String("password")) { | ||||||
|  | 		return errors.New("Password does not meet complexity requirements") | ||||||
|  | 	} | ||||||
| 	uname := c.String("username") | 	uname := c.String("username") | ||||||
| 	user, err := models.GetUserByName(uname) | 	user, err := models.GetUserByName(uname) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -243,6 +245,7 @@ func runChangePassword(c *cli.Context) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	user.HashPassword(c.String("password")) | 	user.HashPassword(c.String("password")) | ||||||
|  |  | ||||||
| 	if err := models.UpdateUserCols(user, "passwd", "salt"); err != nil { | 	if err := models.UpdateUserCols(user, "passwd", "salt"); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -275,26 +278,24 @@ func runCreateUser(c *cli.Context) error { | |||||||
| 		fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n") | 		fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var password string | 	if err := initDB(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var password string | ||||||
| 	if c.IsSet("password") { | 	if c.IsSet("password") { | ||||||
| 		password = c.String("password") | 		password = c.String("password") | ||||||
| 	} else if c.IsSet("random-password") { | 	} else if c.IsSet("random-password") { | ||||||
| 		var err error | 		var err error | ||||||
| 		password, err = generate.GetRandomString(c.Int("random-password-length")) | 		password, err = pwd.Generate(c.Int("random-password-length")) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		fmt.Printf("generated random password is '%s'\n", password) | 		fmt.Printf("generated random password is '%s'\n", password) | ||||||
| 	} else { | 	} else { | ||||||
| 		return errors.New("must set either password or random-password flag") | 		return errors.New("must set either password or random-password flag") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := initDB(); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// always default to true | 	// always default to true | ||||||
| 	var changePassword = true | 	var changePassword = true | ||||||
|  |  | ||||||
|   | |||||||
| @@ -332,6 +332,9 @@ MIN_PASSWORD_LENGTH = 6 | |||||||
| IMPORT_LOCAL_PATHS = false | IMPORT_LOCAL_PATHS = false | ||||||
| ; Set to true to prevent all users (including admin) from creating custom git hooks | ; Set to true to prevent all users (including admin) from creating custom git hooks | ||||||
| DISABLE_GIT_HOOKS = false | DISABLE_GIT_HOOKS = false | ||||||
|  | ;Comma separated list of character classes required to pass minimum complexity. | ||||||
|  | ;If left empty or no valid values are specified, the default values (`lower,upper,digit,spec`) will be used. | ||||||
|  | PASSWORD_COMPLEXITY = lower,upper,digit,spec | ||||||
| ; Password Hash algorithm, either "pbkdf2", "argon2", "scrypt" or "bcrypt" | ; Password Hash algorithm, either "pbkdf2", "argon2", "scrypt" or "bcrypt" | ||||||
| PASSWORD_HASH_ALGO = pbkdf2 | PASSWORD_HASH_ALGO = pbkdf2 | ||||||
| ; Set false to allow JavaScript to read CSRF cookie | ; Set false to allow JavaScript to read CSRF cookie | ||||||
|   | |||||||
| @@ -208,6 +208,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. | |||||||
| - `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`) | - `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`) | ||||||
| - `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[pbkdf2, argon2, scrypt, bcrypt\]. | - `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[pbkdf2, argon2, scrypt, bcrypt\]. | ||||||
| - `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie. | - `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie. | ||||||
|  | - `PASSWORD_COMPLEXITY`: **lower,upper,digit,spec**: Comma separated list of character classes required to pass minimum complexity. If left empty or no valid values are specified, the default values will be used. Possible values are:  | ||||||
|  |     - lower - use one or more lower latin characters | ||||||
|  |     - upper - use one or more upper latin characters | ||||||
|  |     - digit - use one or more digits | ||||||
|  |     - spec - use one or more special characters as ``][!"#$%&'()*+,./:;<=>?@\^_{|}~`-`` and space symbol.  | ||||||
|  |  | ||||||
| ## OpenID (`openid`) | ## OpenID (`openid`) | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										73
									
								
								modules/password/password.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								modules/password/password.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | // Copyright 2019 The Gitea Authors. All rights reserved. | ||||||
|  | // Use of this source code is governed by a MIT-style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
|  | package password | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"math/big" | ||||||
|  | 	"regexp" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var matchComplexities = map[string]regexp.Regexp{} | ||||||
|  | var matchComplexityOnce sync.Once | ||||||
|  | var validChars string | ||||||
|  | var validComplexities = map[string]string{ | ||||||
|  | 	"lower": "abcdefghijklmnopqrstuvwxyz", | ||||||
|  | 	"upper": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", | ||||||
|  | 	"digit": "0123456789", | ||||||
|  | 	"spec":  `][ !"#$%&'()*+,./:;<=>?@\^_{|}~` + "`-", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewComplexity for preparation | ||||||
|  | func NewComplexity() { | ||||||
|  | 	matchComplexityOnce.Do(func() { | ||||||
|  | 		if len(setting.PasswordComplexity) > 0 { | ||||||
|  | 			for key, val := range setting.PasswordComplexity { | ||||||
|  | 				matchComplexity := regexp.MustCompile(val) | ||||||
|  | 				matchComplexities[key] = *matchComplexity | ||||||
|  | 				validChars += validComplexities[key] | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			for _, val := range validComplexities { | ||||||
|  | 				validChars += val | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsComplexEnough return True if password is Complexity | ||||||
|  | func IsComplexEnough(pwd string) bool { | ||||||
|  | 	if len(setting.PasswordComplexity) > 0 { | ||||||
|  | 		NewComplexity() | ||||||
|  | 		for _, val := range matchComplexities { | ||||||
|  | 			if !val.MatchString(pwd) { | ||||||
|  | 				return false | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Generate  a random password | ||||||
|  | func Generate(n int) (string, error) { | ||||||
|  | 	NewComplexity() | ||||||
|  | 	buffer := make([]byte, n) | ||||||
|  | 	max := big.NewInt(int64(len(validChars))) | ||||||
|  | 	for { | ||||||
|  | 		for j := 0; j < n; j++ { | ||||||
|  | 			rnd, err := rand.Int(rand.Reader, max) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return "", err | ||||||
|  | 			} | ||||||
|  | 			buffer[j] = validChars[rnd.Int64()] | ||||||
|  | 		} | ||||||
|  | 		if IsComplexEnough(string(buffer)) && string(buffer[0]) != " " && string(buffer[n-1]) != " " { | ||||||
|  | 			return string(buffer), nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -146,6 +146,7 @@ var ( | |||||||
| 	MinPasswordLength     int | 	MinPasswordLength     int | ||||||
| 	ImportLocalPaths      bool | 	ImportLocalPaths      bool | ||||||
| 	DisableGitHooks       bool | 	DisableGitHooks       bool | ||||||
|  | 	PasswordComplexity    map[string]string | ||||||
| 	PasswordHashAlgo      string | 	PasswordHashAlgo      string | ||||||
|  |  | ||||||
| 	// UI settings | 	// UI settings | ||||||
| @@ -774,6 +775,27 @@ func NewContext() { | |||||||
|  |  | ||||||
| 	InternalToken = loadInternalToken(sec) | 	InternalToken = loadInternalToken(sec) | ||||||
|  |  | ||||||
|  | 	var dictPC = map[string]string{ | ||||||
|  | 		"lower": "[a-z]+", | ||||||
|  | 		"upper": "[A-Z]+", | ||||||
|  | 		"digit": "[0-9]+", | ||||||
|  | 		"spec":  `][ !"#$%&'()*+,./:;<=>?@\\^_{|}~` + "`-", | ||||||
|  | 	} | ||||||
|  | 	PasswordComplexity = make(map[string]string) | ||||||
|  | 	cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",") | ||||||
|  | 	for _, y := range cfgdata { | ||||||
|  | 		ts := strings.TrimSpace(y) | ||||||
|  | 		for a := range dictPC { | ||||||
|  | 			if strings.ToLower(ts) == a { | ||||||
|  | 				PasswordComplexity[ts] = dictPC[ts] | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(PasswordComplexity) == 0 { | ||||||
|  | 		PasswordComplexity = dictPC | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	sec = Cfg.Section("attachment") | 	sec = Cfg.Section("attachment") | ||||||
| 	AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) | 	AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments")) | ||||||
| 	if !filepath.IsAbs(AttachmentPath) { | 	if !filepath.IsAbs(AttachmentPath) { | ||||||
|   | |||||||
| @@ -315,6 +315,7 @@ team_no_units_error = Allow access to at least one repository section. | |||||||
| email_been_used = The email address is already used. | email_been_used = The email address is already used. | ||||||
| openid_been_used = The OpenID address '%s' is already used. | openid_been_used = The OpenID address '%s' is already used. | ||||||
| username_password_incorrect = Username or password is incorrect. | username_password_incorrect = Username or password is incorrect. | ||||||
|  | password_complexity =  Password does not pass complexity requirements. | ||||||
| enterred_invalid_repo_name = The repository name you entered is incorrect. | enterred_invalid_repo_name = The repository name you entered is incorrect. | ||||||
| enterred_invalid_owner_name = The new owner name is not valid. | enterred_invalid_owner_name = The new owner name is not valid. | ||||||
| enterred_invalid_password = The password you entered is incorrect. | enterred_invalid_password = The password you entered is incorrect. | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/password" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/routers" | 	"code.gitea.io/gitea/routers" | ||||||
| 	"code.gitea.io/gitea/services/mailer" | 	"code.gitea.io/gitea/services/mailer" | ||||||
| @@ -94,7 +95,10 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) { | |||||||
| 			u.LoginName = form.LoginName | 			u.LoginName = form.LoginName | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	if !password.IsComplexEnough(form.Password) { | ||||||
|  | 		ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserNew, &form) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	if err := models.CreateUser(u); err != nil { | 	if err := models.CreateUser(u); err != nil { | ||||||
| 		switch { | 		switch { | ||||||
| 		case models.IsErrUserAlreadyExist(err): | 		case models.IsErrUserAlreadyExist(err): | ||||||
| @@ -201,6 +205,10 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) { | |||||||
| 			ctx.ServerError("UpdateUser", err) | 			ctx.ServerError("UpdateUser", err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 		if !password.IsComplexEnough(form.Password) { | ||||||
|  | 			ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplUserEdit, &form) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 		u.HashPassword(form.Password) | 		u.HashPassword(form.Password) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,9 +6,12 @@ | |||||||
| package admin | package admin | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/password" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/convert" | 	"code.gitea.io/gitea/routers/api/v1/convert" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/user" | 	"code.gitea.io/gitea/routers/api/v1/user" | ||||||
| @@ -73,7 +76,11 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) { | |||||||
| 	if ctx.Written() { | 	if ctx.Written() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	if !password.IsComplexEnough(form.Password) { | ||||||
|  | 		err := errors.New("PasswordComplexity") | ||||||
|  | 		ctx.Error(400, "PasswordComplexity", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	if err := models.CreateUser(u); err != nil { | 	if err := models.CreateUser(u); err != nil { | ||||||
| 		if models.IsErrUserAlreadyExist(err) || | 		if models.IsErrUserAlreadyExist(err) || | ||||||
| 			models.IsErrEmailAlreadyUsed(err) || | 			models.IsErrEmailAlreadyUsed(err) || | ||||||
| @@ -131,6 +138,11 @@ func EditUser(ctx *context.APIContext, form api.EditUserOption) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(form.Password) > 0 { | 	if len(form.Password) > 0 { | ||||||
|  | 		if !password.IsComplexEnough(form.Password) { | ||||||
|  | 			err := errors.New("PasswordComplexity") | ||||||
|  | 			ctx.Error(400, "PasswordComplexity", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 		var err error | 		var err error | ||||||
| 		if u.Salt, err = models.GetUserSalt(); err != nil { | 		if u.Salt, err = models.GetUserSalt(); err != nil { | ||||||
| 			ctx.Error(500, "UpdateUser", err) | 			ctx.Error(500, "UpdateUser", err) | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/password" | ||||||
| 	"code.gitea.io/gitea/modules/recaptcha" | 	"code.gitea.io/gitea/modules/recaptcha" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| @@ -1334,6 +1335,11 @@ func ResetPasswdPost(ctx *context.Context) { | |||||||
| 		ctx.Data["Err_Password"] = true | 		ctx.Data["Err_Password"] = true | ||||||
| 		ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil) | 		ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil) | ||||||
| 		return | 		return | ||||||
|  | 	} else if !password.IsComplexEnough(passwd) { | ||||||
|  | 		ctx.Data["IsResetForm"] = true | ||||||
|  | 		ctx.Data["Err_Password"] = true | ||||||
|  | 		ctx.RenderWithErr(ctx.Tr("form.password_complexity"), tplResetPassword, nil) | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var err error | 	var err error | ||||||
| @@ -1364,7 +1370,6 @@ func ResetPasswdPost(ctx *context.Context) { | |||||||
| func MustChangePassword(ctx *context.Context) { | func MustChangePassword(ctx *context.Context) { | ||||||
| 	ctx.Data["Title"] = ctx.Tr("auth.must_change_password") | 	ctx.Data["Title"] = ctx.Tr("auth.must_change_password") | ||||||
| 	ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password" | 	ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password" | ||||||
|  |  | ||||||
| 	ctx.HTML(200, tplMustChangePassword) | 	ctx.HTML(200, tplMustChangePassword) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1372,16 +1377,12 @@ func MustChangePassword(ctx *context.Context) { | |||||||
| // account was created by an admin | // account was created by an admin | ||||||
| func MustChangePasswordPost(ctx *context.Context, cpt *captcha.Captcha, form auth.MustChangePasswordForm) { | func MustChangePasswordPost(ctx *context.Context, cpt *captcha.Captcha, form auth.MustChangePasswordForm) { | ||||||
| 	ctx.Data["Title"] = ctx.Tr("auth.must_change_password") | 	ctx.Data["Title"] = ctx.Tr("auth.must_change_password") | ||||||
|  |  | ||||||
| 	ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password" | 	ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password" | ||||||
|  |  | ||||||
| 	if ctx.HasError() { | 	if ctx.HasError() { | ||||||
| 		ctx.HTML(200, tplMustChangePassword) | 		ctx.HTML(200, tplMustChangePassword) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	u := ctx.User | 	u := ctx.User | ||||||
|  |  | ||||||
| 	// Make sure only requests for users who are eligible to change their password via | 	// Make sure only requests for users who are eligible to change their password via | ||||||
| 	// this method passes through | 	// this method passes through | ||||||
| 	if !u.MustChangePassword { | 	if !u.MustChangePassword { | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/base" | 	"code.gitea.io/gitea/modules/base" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/password" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	"code.gitea.io/gitea/services/mailer" | 	"code.gitea.io/gitea/services/mailer" | ||||||
| @@ -52,6 +53,8 @@ func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) { | |||||||
| 		ctx.Flash.Error(ctx.Tr("settings.password_incorrect")) | 		ctx.Flash.Error(ctx.Tr("settings.password_incorrect")) | ||||||
| 	} else if form.Password != form.Retype { | 	} else if form.Password != form.Retype { | ||||||
| 		ctx.Flash.Error(ctx.Tr("form.password_not_match")) | 		ctx.Flash.Error(ctx.Tr("form.password_not_match")) | ||||||
|  | 	} else if !password.IsComplexEnough(form.Password) { | ||||||
|  | 		ctx.Flash.Error(ctx.Tr("settings.password_complexity")) | ||||||
| 	} else { | 	} else { | ||||||
| 		var err error | 		var err error | ||||||
| 		if ctx.User.Salt, err = models.GetUserSalt(); err != nil { | 		if ctx.User.Salt, err = models.GetUserSalt(); err != nil { | ||||||
|   | |||||||
| @@ -19,36 +19,77 @@ import ( | |||||||
| func TestChangePassword(t *testing.T) { | func TestChangePassword(t *testing.T) { | ||||||
| 	oldPassword := "password" | 	oldPassword := "password" | ||||||
| 	setting.MinPasswordLength = 6 | 	setting.MinPasswordLength = 6 | ||||||
|  | 	setting.PasswordComplexity = map[string]string{ | ||||||
|  | 		"lower": "[a-z]+", | ||||||
|  | 		"upper": "[A-Z]+", | ||||||
|  | 		"digit": "[0-9]+", | ||||||
|  | 		"spec":  "[-_]+", | ||||||
|  | 	} | ||||||
|  | 	var pcLUN = map[string]string{ | ||||||
|  | 		"lower": "[a-z]+", | ||||||
|  | 		"upper": "[A-Z]+", | ||||||
|  | 		"digit": "[0-9]+", | ||||||
|  | 	} | ||||||
|  | 	var pcLU = map[string]string{ | ||||||
|  | 		"lower": "[a-z]+", | ||||||
|  | 		"upper": "[A-Z]+", | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	for _, req := range []struct { | 	for _, req := range []struct { | ||||||
| 		OldPassword        string | 		OldPassword        string | ||||||
| 		NewPassword        string | 		NewPassword        string | ||||||
| 		Retype             string | 		Retype             string | ||||||
| 		Message            string | 		Message            string | ||||||
|  | 		PasswordComplexity map[string]string | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			OldPassword:        oldPassword, | 			OldPassword:        oldPassword, | ||||||
| 			NewPassword: "123456", | 			NewPassword:        "Qwerty123456-", | ||||||
| 			Retype:      "123456", | 			Retype:             "Qwerty123456-", | ||||||
| 			Message:            "", | 			Message:            "", | ||||||
|  | 			PasswordComplexity: setting.PasswordComplexity, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			OldPassword:        oldPassword, | 			OldPassword:        oldPassword, | ||||||
| 			NewPassword:        "12345", | 			NewPassword:        "12345", | ||||||
| 			Retype:             "12345", | 			Retype:             "12345", | ||||||
| 			Message:            "auth.password_too_short", | 			Message:            "auth.password_too_short", | ||||||
|  | 			PasswordComplexity: setting.PasswordComplexity, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			OldPassword:        "12334", | 			OldPassword:        "12334", | ||||||
| 			NewPassword:        "123456", | 			NewPassword:        "123456", | ||||||
| 			Retype:             "123456", | 			Retype:             "123456", | ||||||
| 			Message:            "settings.password_incorrect", | 			Message:            "settings.password_incorrect", | ||||||
|  | 			PasswordComplexity: setting.PasswordComplexity, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			OldPassword:        oldPassword, | 			OldPassword:        oldPassword, | ||||||
| 			NewPassword:        "123456", | 			NewPassword:        "123456", | ||||||
| 			Retype:             "12345", | 			Retype:             "12345", | ||||||
| 			Message:            "form.password_not_match", | 			Message:            "form.password_not_match", | ||||||
|  | 			PasswordComplexity: setting.PasswordComplexity, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			OldPassword:        oldPassword, | ||||||
|  | 			NewPassword:        "Qwerty", | ||||||
|  | 			Retype:             "Qwerty", | ||||||
|  | 			Message:            "settings.password_complexity", | ||||||
|  | 			PasswordComplexity: setting.PasswordComplexity, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			OldPassword:        oldPassword, | ||||||
|  | 			NewPassword:        "Qwerty", | ||||||
|  | 			Retype:             "Qwerty", | ||||||
|  | 			Message:            "settings.password_complexity", | ||||||
|  | 			PasswordComplexity: pcLUN, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			OldPassword:        oldPassword, | ||||||
|  | 			NewPassword:        "QWERTY", | ||||||
|  | 			Retype:             "QWERTY", | ||||||
|  | 			Message:            "settings.password_complexity", | ||||||
|  | 			PasswordComplexity: pcLU, | ||||||
| 		}, | 		}, | ||||||
| 	} { | 	} { | ||||||
| 		models.PrepareTestEnv(t) | 		models.PrepareTestEnv(t) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user