mirror of https://github.com/portainer/portainer
120 lines
3.2 KiB
Go
120 lines
3.2 KiB
Go
package http
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/portainer/portainer"
|
|
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type (
|
|
// middleWareService represents a service to manage HTTP middlewares
|
|
middleWareService struct {
|
|
jwtService portainer.JWTService
|
|
authDisabled bool
|
|
}
|
|
contextKey int
|
|
)
|
|
|
|
const (
|
|
contextAuthenticationKey contextKey = iota
|
|
)
|
|
|
|
func extractTokenDataFromRequestContext(request *http.Request) (*portainer.TokenData, error) {
|
|
contextData := request.Context().Value(contextAuthenticationKey)
|
|
if contextData == nil {
|
|
return nil, portainer.ErrMissingContextData
|
|
}
|
|
|
|
tokenData := contextData.(*portainer.TokenData)
|
|
return tokenData, nil
|
|
}
|
|
|
|
// public defines a chain of middleware for public endpoints (no authentication required)
|
|
func (service *middleWareService) public(h http.Handler) http.Handler {
|
|
h = mwSecureHeaders(h)
|
|
return h
|
|
}
|
|
|
|
// authenticated defines a chain of middleware for private endpoints (authentication required)
|
|
func (service *middleWareService) authenticated(h http.Handler) http.Handler {
|
|
h = service.mwCheckAuthentication(h)
|
|
h = mwSecureHeaders(h)
|
|
return h
|
|
}
|
|
|
|
// administrator defines a chain of middleware for private administrator restricted endpoints
|
|
// (authentication and role admin required)
|
|
func (service *middleWareService) administrator(h http.Handler) http.Handler {
|
|
h = mwCheckAdministratorRole(h)
|
|
h = service.mwCheckAuthentication(h)
|
|
h = mwSecureHeaders(h)
|
|
return h
|
|
}
|
|
|
|
// mwSecureHeaders provides secure headers middleware for handlers
|
|
func mwSecureHeaders(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Add("X-Content-Type-Options", "nosniff")
|
|
w.Header().Add("X-Frame-Options", "DENY")
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// mwCheckAdministratorRole check the role of the user associated to the request
|
|
func mwCheckAdministratorRole(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
tokenData, err := extractTokenDataFromRequestContext(r)
|
|
if err != nil {
|
|
Error(w, portainer.ErrResourceAccessDenied, http.StatusForbidden, nil)
|
|
return
|
|
}
|
|
|
|
if tokenData.Role != portainer.AdministratorRole {
|
|
Error(w, portainer.ErrResourceAccessDenied, http.StatusForbidden, nil)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// mwCheckAuthentication provides Authentication middleware for handlers
|
|
func (service *middleWareService) mwCheckAuthentication(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var tokenData *portainer.TokenData
|
|
if !service.authDisabled {
|
|
var token string
|
|
|
|
// 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 == "" {
|
|
Error(w, portainer.ErrUnauthorized, http.StatusUnauthorized, nil)
|
|
return
|
|
}
|
|
|
|
var err error
|
|
tokenData, err = service.jwtService.ParseAndVerifyToken(token)
|
|
if err != nil {
|
|
Error(w, err, http.StatusUnauthorized, nil)
|
|
return
|
|
}
|
|
} else {
|
|
tokenData = &portainer.TokenData{
|
|
Role: portainer.AdministratorRole,
|
|
}
|
|
}
|
|
|
|
ctx := context.WithValue(r.Context(), contextAuthenticationKey, tokenData)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
return
|
|
})
|
|
}
|