fix(auth): prevent login for non admin for ldap and oauth [EE-648] (#5283)

pull/6398/head
Chaim Lev-Ari 2022-01-13 07:27:26 +02:00 committed by GitHub
parent 6c32edc5b5
commit 085762a1f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 35 deletions

View File

@ -12,6 +12,7 @@ import (
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/internal/authorization"
)
type authenticatePayload struct {
@ -49,7 +50,7 @@ func (payload *authenticatePayload) Validate(r *http.Request) error {
// @failure 422 "Invalid Credentials"
// @failure 500 "Server error"
// @router /auth [post]
func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
func (handler *Handler) authenticate(rw http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload authenticatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
@ -61,39 +62,36 @@ func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *ht
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
}
u, err := handler.DataStore.User().UserByUsername(payload.Username)
if err != nil && !handler.DataStore.IsErrObjectNotFound(err) {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a user with the specified username from the database", err}
user, err := handler.DataStore.User().UserByUsername(payload.Username)
if err != nil {
if !handler.DataStore.IsErrObjectNotFound(err) {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a user with the specified username from the database", err}
}
if settings.AuthenticationMethod == portainer.AuthenticationInternal ||
settings.AuthenticationMethod == portainer.AuthenticationOAuth ||
(settings.AuthenticationMethod == portainer.AuthenticationLDAP && !settings.LDAPSettings.AutoCreateUsers) {
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized}
}
}
if handler.DataStore.IsErrObjectNotFound(err) && (settings.AuthenticationMethod == portainer.AuthenticationInternal || settings.AuthenticationMethod == portainer.AuthenticationOAuth) {
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized}
if user != nil && isUserInitialAdmin(user) || settings.AuthenticationMethod == portainer.AuthenticationInternal {
return handler.authenticateInternal(rw, user, payload.Password)
}
if settings.AuthenticationMethod == portainer.AuthenticationOAuth {
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Only initial admin is allowed to login without oauth", httperrors.ErrUnauthorized}
}
if settings.AuthenticationMethod == portainer.AuthenticationLDAP {
if u == nil && settings.LDAPSettings.AutoCreateUsers {
return handler.authenticateLDAPAndCreateUser(w, payload.Username, payload.Password, &settings.LDAPSettings)
} else if u == nil && !settings.LDAPSettings.AutoCreateUsers {
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized}
}
return handler.authenticateLDAP(w, u, payload.Password, &settings.LDAPSettings)
return handler.authenticateLDAP(rw, user, payload.Username, payload.Password, &settings.LDAPSettings)
}
return handler.authenticateInternal(w, u, payload.Password)
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Login method is not supported", httperrors.ErrUnauthorized}
}
func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer.User, password string, ldapSettings *portainer.LDAPSettings) *httperror.HandlerError {
err := handler.LDAPService.AuthenticateUser(user.Username, password, ldapSettings)
if err != nil {
return handler.authenticateInternal(w, user, password)
}
err = handler.addUserIntoTeams(user, ldapSettings)
if err != nil {
log.Printf("Warning: unable to automatically add user into teams: %s\n", err.Error())
}
return handler.writeToken(w, user)
func isUserInitialAdmin(user *portainer.User) bool {
return int(user.ID) == 1
}
func (handler *Handler) authenticateInternal(w http.ResponseWriter, user *portainer.User, password string) *httperror.HandlerError {
@ -105,20 +103,27 @@ func (handler *Handler) authenticateInternal(w http.ResponseWriter, user *portai
return handler.writeToken(w, user)
}
func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, username, password string, ldapSettings *portainer.LDAPSettings) *httperror.HandlerError {
func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer.User, username, password string, ldapSettings *portainer.LDAPSettings) *httperror.HandlerError {
err := handler.LDAPService.AuthenticateUser(username, password, ldapSettings)
if err != nil {
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", err}
return &httperror.HandlerError{
StatusCode: http.StatusForbidden,
Message: "Only initial admin is allowed to login without oauth",
Err: err,
}
}
user := &portainer.User{
Username: username,
Role: portainer.StandardUserRole,
}
if user == nil {
user = &portainer.User{
Username: username,
Role: portainer.StandardUserRole,
PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(),
}
err = handler.DataStore.User().Create(user)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist user inside the database", err}
err = handler.DataStore.User().Create(user)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist user inside the database", err}
}
}
err = handler.addUserIntoTeams(user, ldapSettings)
@ -130,7 +135,9 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use
}
func (handler *Handler) writeToken(w http.ResponseWriter, user *portainer.User) *httperror.HandlerError {
return handler.persistAndWriteToken(w, composeTokenData(user))
tokenData := composeTokenData(user)
return handler.persistAndWriteToken(w, tokenData)
}
func (handler *Handler) persistAndWriteToken(w http.ResponseWriter, tokenData *portainer.TokenData) *httperror.HandlerError {

View File

@ -87,6 +87,12 @@ func (handler *Handler) userCreate(w http.ResponseWriter, r *http.Request) *http
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
}
// when ldap/oauth is on, can only add users without password
if (settings.AuthenticationMethod == portainer.AuthenticationLDAP || settings.AuthenticationMethod == portainer.AuthenticationOAuth) && payload.Password != "" {
errMsg := "A user with password can not be created when authentication method is Oauth or LDAP"
return &httperror.HandlerError{http.StatusBadRequest, errMsg, errors.New(errMsg)}
}
if settings.AuthenticationMethod == portainer.AuthenticationInternal {
user.Password, err = handler.CryptoService.Hash(payload.Password)
if err != nil {