1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-25 00:09:40 +02:00

feat(auth): add custom user timeout (#3871)

* feat(auth): introduce new timeout constant

* feat(auth): pass timeout from handler

* feat(auth): add timeout selector to auth settings view

* feat(settings): add user session timeout property

* feat(auth): load user session timeout from settings

* fix(settings): use correct time format

* feat(auth): remove no-auth flag

* refactor(auth): move timeout mgmt to jwt service

* refactor(client): remove no-auth checks from client

* refactor(cli): remove defaultNoAuth

* feat(settings): create settings with default user timeout value

* refactor(db): save user session timeout always

* refactor(jwt): return error

* feat(auth): set session timeout in jwt service on update

* feat(auth): add description and time settings

* feat(auth): parse duration

* feat(settings): validate user timeout format

* refactor(settings): remove unneccesary import
This commit is contained in:
Chaim Lev-Ari 2020-06-09 12:55:36 +03:00 committed by GitHub
parent b58c2facfe
commit b02749f877
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 214 additions and 236 deletions

View file

@ -32,10 +32,6 @@ func (payload *authenticatePayload) Validate(r *http.Request) error {
}
func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
if handler.authDisabled {
return &httperror.HandlerError{http.StatusServiceUnavailable, "Cannot authenticate user. Portainer was started with the --no-auth flag", ErrAuthDisabled}
}
var payload authenticatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {

View file

@ -10,16 +10,9 @@ import (
"github.com/portainer/portainer/api/http/security"
)
const (
// ErrAuthDisabled is an error raised when trying to access the authentication endpoints
// when the server has been started with the --no-auth flag
ErrAuthDisabled = portainer.Error("Authentication is disabled")
)
// Handler is the HTTP handler used to handle authentication operations.
type Handler struct {
*mux.Router
authDisabled bool
DataStore portainer.DataStore
CryptoService portainer.CryptoService
JWTService portainer.JWTService
@ -29,10 +22,9 @@ type Handler struct {
}
// NewHandler creates a handler to manage authentication operations.
func NewHandler(bouncer *security.RequestBouncer, rateLimiter *security.RateLimiter, authDisabled bool) *Handler {
func NewHandler(bouncer *security.RequestBouncer, rateLimiter *security.RateLimiter) *Handler {
h := &Handler{
Router: mux.NewRouter(),
authDisabled: authDisabled,
Router: mux.NewRouter(),
}
h.Handle("/auth/oauth/validate",

View file

@ -17,11 +17,12 @@ func hideFields(settings *portainer.Settings) {
// Handler is the HTTP handler used to handle settings operations.
type Handler struct {
*mux.Router
AuthorizationService *portainer.AuthorizationService
DataStore portainer.DataStore
LDAPService portainer.LDAPService
FileService portainer.FileService
JobScheduler portainer.JobScheduler
AuthorizationService *portainer.AuthorizationService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
}
// NewHandler creates a handler to manage settings operations.

View file

@ -2,6 +2,7 @@ package settings
import (
"net/http"
"time"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
@ -25,6 +26,7 @@ type settingsUpdatePayload struct {
TemplatesURL *string
EdgeAgentCheckinInterval *int
EnableEdgeComputeFeatures *bool
UserSessionTimeout *string
}
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
@ -37,6 +39,13 @@ func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
if payload.TemplatesURL != nil && *payload.TemplatesURL != "" && !govalidator.IsURL(*payload.TemplatesURL) {
return portainer.Error("Invalid external templates URL. Must correspond to a valid URL format")
}
if payload.UserSessionTimeout != nil {
_, err := time.ParseDuration(*payload.UserSessionTimeout)
if err != nil {
return portainer.Error("Invalid user session timeout")
}
}
return nil
}
@ -125,6 +134,14 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
settings.EdgeAgentCheckinInterval = *payload.EdgeAgentCheckinInterval
}
if payload.UserSessionTimeout != nil {
settings.UserSessionTimeout = *payload.UserSessionTimeout
userSessionDuration, _ := time.ParseDuration(*payload.UserSessionTimeout)
handler.JWTService.SetUserSessionDuration(userSessionDuration)
}
tlsError := handler.updateTLS(settings)
if tlsError != nil {
return tlsError

View file

@ -16,7 +16,6 @@ type (
dataStore portainer.DataStore
jwtService portainer.JWTService
rbacExtensionClient *rbacExtensionClient
authDisabled bool
}
// RestrictedRequestContext is a data structure containing information
@ -30,12 +29,11 @@ type (
)
// NewRequestBouncer initializes a new RequestBouncer
func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService, authenticationDisabled bool, rbacExtensionURL string) *RequestBouncer {
func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService, rbacExtensionURL string) *RequestBouncer {
return &RequestBouncer{
dataStore: dataStore,
jwtService: jwtService,
rbacExtensionClient: newRBACExtensionClient(rbacExtensionURL),
authDisabled: authenticationDisabled,
}
}
@ -289,44 +287,38 @@ func (bouncer *RequestBouncer) mwUpgradeToRestrictedRequest(next http.Handler) h
func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var tokenData *portainer.TokenData
if !bouncer.authDisabled {
var token string
var token string
// Optionally, token might be set via the "token" query parameter.
// For example, in websocket requests
token = r.URL.Query().Get("token")
// Optionally, token might be set via the "token" query parameter.
// For example, in websocket requests
token = r.URL.Query().Get("token")
// Get token from the Authorization header
tokens, ok := r.Header["Authorization"]
if ok && len(tokens) >= 1 {
token = tokens[0]
token = strings.TrimPrefix(token, "Bearer ")
}
// Get token from the Authorization header
tokens, ok := r.Header["Authorization"]
if ok && len(tokens) >= 1 {
token = tokens[0]
token = strings.TrimPrefix(token, "Bearer ")
}
if token == "" {
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", portainer.ErrUnauthorized)
return
}
if token == "" {
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", portainer.ErrUnauthorized)
return
}
var err error
tokenData, err = bouncer.jwtService.ParseAndVerifyToken(token)
if err != nil {
httperror.WriteError(w, http.StatusUnauthorized, "Invalid JWT token", err)
return
}
var err error
tokenData, err = bouncer.jwtService.ParseAndVerifyToken(token)
if err != nil {
httperror.WriteError(w, http.StatusUnauthorized, "Invalid JWT token", err)
return
}
_, err = bouncer.dataStore.User().User(tokenData.ID)
if err != nil && err == portainer.ErrObjectNotFound {
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", portainer.ErrUnauthorized)
return
} else if err != nil {
httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user details from the database", err)
return
}
} else {
tokenData = &portainer.TokenData{
Role: portainer.AdministratorRole,
}
_, err = bouncer.dataStore.User().User(tokenData.ID)
if err != nil && err == portainer.ErrObjectNotFound {
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", portainer.ErrUnauthorized)
return
} else if err != nil {
httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user details from the database", err)
return
}
ctx := storeTokenData(r, tokenData)

View file

@ -47,7 +47,6 @@ import (
type Server struct {
BindAddress string
AssetsPath string
AuthDisabled bool
Status *portainer.Status
ReverseTunnelService portainer.ReverseTunnelService
ExtensionManager portainer.ExtensionManager
@ -77,11 +76,11 @@ func (server *Server) Start() error {
authorizationService := portainer.NewAuthorizationService(server.DataStore)
rbacExtensionURL := proxyManager.GetExtensionURL(portainer.RBACExtension)
requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService, server.AuthDisabled, rbacExtensionURL)
requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService, rbacExtensionURL)
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
var authHandler = auth.NewHandler(requestBouncer, rateLimiter, server.AuthDisabled)
var authHandler = auth.NewHandler(requestBouncer, rateLimiter)
authHandler.DataStore = server.DataStore
authHandler.CryptoService = server.CryptoService
authHandler.JWTService = server.JWTService
@ -153,11 +152,12 @@ func (server *Server) Start() error {
schedulesHandler.ReverseTunnelService = server.ReverseTunnelService
var settingsHandler = settings.NewHandler(requestBouncer)
settingsHandler.AuthorizationService = authorizationService
settingsHandler.DataStore = server.DataStore
settingsHandler.LDAPService = server.LDAPService
settingsHandler.FileService = server.FileService
settingsHandler.JobScheduler = server.JobScheduler
settingsHandler.AuthorizationService = authorizationService
settingsHandler.JWTService = server.JWTService
settingsHandler.LDAPService = server.LDAPService
var stackHandler = stacks.NewHandler(requestBouncer)
stackHandler.DataStore = server.DataStore