mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
feat(password) EE-2690 enforce strong password policy (#6751)
* feat(password) EE-2690 enforce strong password policy * feat(password) EE-2690 disable create user button if password is not valid * feat(password) EE-2690 show force password change warning only when week password is detected * feat(password) EE-2690 prevent users leave account page by clicking add access token button Co-authored-by: Simon Meng <simon.meng@portainer.io>
This commit is contained in:
parent
9ebc963082
commit
85ad4e334a
26 changed files with 331 additions and 41 deletions
|
@ -13,6 +13,7 @@ import (
|
|||
portainer "github.com/portainer/portainer/api"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/passwordutils"
|
||||
)
|
||||
|
||||
type authenticatePayload struct {
|
||||
|
@ -100,7 +101,8 @@ func (handler *Handler) authenticateInternal(w http.ResponseWriter, user *portai
|
|||
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized}
|
||||
}
|
||||
|
||||
return handler.writeToken(w, user)
|
||||
forceChangePassword := !passwordutils.StrengthCheck(password)
|
||||
return handler.writeToken(w, user, forceChangePassword)
|
||||
}
|
||||
|
||||
func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer.User, username, password string, ldapSettings *portainer.LDAPSettings) *httperror.HandlerError {
|
||||
|
@ -131,11 +133,11 @@ func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer.
|
|||
log.Printf("Warning: unable to automatically add user into teams: %s\n", err.Error())
|
||||
}
|
||||
|
||||
return handler.writeToken(w, user)
|
||||
return handler.writeToken(w, user, false)
|
||||
}
|
||||
|
||||
func (handler *Handler) writeToken(w http.ResponseWriter, user *portainer.User) *httperror.HandlerError {
|
||||
tokenData := composeTokenData(user)
|
||||
func (handler *Handler) writeToken(w http.ResponseWriter, user *portainer.User, forceChangePassword bool) *httperror.HandlerError {
|
||||
tokenData := composeTokenData(user, forceChangePassword)
|
||||
|
||||
return handler.persistAndWriteToken(w, tokenData)
|
||||
}
|
||||
|
@ -206,10 +208,11 @@ func teamMembershipExists(teamID portainer.TeamID, memberships []portainer.TeamM
|
|||
return false
|
||||
}
|
||||
|
||||
func composeTokenData(user *portainer.User) *portainer.TokenData {
|
||||
func composeTokenData(user *portainer.User, forceChangePassword bool) *portainer.TokenData {
|
||||
return &portainer.TokenData{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Role: user.Role,
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Role: user.Role,
|
||||
ForceChangePassword: forceChangePassword,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,5 +110,5 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
|
|||
|
||||
}
|
||||
|
||||
return handler.writeToken(w, user)
|
||||
return handler.writeToken(w, user, false)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/passwordutils"
|
||||
)
|
||||
|
||||
type adminInitPayload struct {
|
||||
|
@ -57,6 +58,10 @@ func (handler *Handler) adminInit(w http.ResponseWriter, r *http.Request) *httpe
|
|||
return &httperror.HandlerError{http.StatusConflict, "Unable to create administrator user", errAdminAlreadyInitialized}
|
||||
}
|
||||
|
||||
if !passwordutils.StrengthCheck(payload.Password) {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Password does not meet the requirements", nil}
|
||||
}
|
||||
|
||||
user := &portainer.User{
|
||||
Username: payload.Username,
|
||||
Role: portainer.AdministratorRole,
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
portainer "github.com/portainer/portainer/api"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/passwordutils"
|
||||
)
|
||||
|
||||
type userCreatePayload struct {
|
||||
|
@ -94,6 +95,10 @@ func (handler *Handler) userCreate(w http.ResponseWriter, r *http.Request) *http
|
|||
}
|
||||
|
||||
if settings.AuthenticationMethod == portainer.AuthenticationInternal {
|
||||
if !passwordutils.StrengthCheck(payload.Password) {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Password does not meet the requirements", nil}
|
||||
}
|
||||
|
||||
user.Password, err = handler.CryptoService.Hash(payload.Password)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to hash user password", errCryptoHashFailure}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
portainer "github.com/portainer/portainer/api"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/passwordutils"
|
||||
)
|
||||
|
||||
type userUpdatePasswordPayload struct {
|
||||
|
@ -81,6 +82,10 @@ func (handler *Handler) userUpdatePassword(w http.ResponseWriter, r *http.Reques
|
|||
return &httperror.HandlerError{http.StatusForbidden, "Specified password do not match actual password", httperrors.ErrUnauthorized}
|
||||
}
|
||||
|
||||
if !passwordutils.StrengthCheck(payload.NewPassword) {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Password does not meet the requirements", nil}
|
||||
}
|
||||
|
||||
user.Password, err = handler.CryptoService.Hash(payload.NewPassword)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to hash user password", errCryptoHashFailure}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue