From 085762a1f4b482509879f4fef63dfe83a58c3885 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Thu, 13 Jan 2022 07:27:26 +0200 Subject: [PATCH] fix(auth): prevent login for non admin for ldap and oauth [EE-648] (#5283) --- api/http/handler/auth/authenticate.go | 77 +++++++++++++++------------ api/http/handler/users/user_create.go | 6 +++ 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/api/http/handler/auth/authenticate.go b/api/http/handler/auth/authenticate.go index 150abdc9e..37c895b44 100644 --- a/api/http/handler/auth/authenticate.go +++ b/api/http/handler/auth/authenticate.go @@ -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 { diff --git a/api/http/handler/users/user_create.go b/api/http/handler/users/user_create.go index 10bcda3eb..165222f28 100644 --- a/api/http/handler/users/user_create.go +++ b/api/http/handler/users/user_create.go @@ -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 {