influxdb/http/authentication_middleware.go

186 lines
5.0 KiB
Go

package http
import (
"context"
"errors"
"fmt"
"net/http"
"time"
"github.com/influxdata/httprouter"
platform "github.com/influxdata/influxdb/v2"
platcontext "github.com/influxdata/influxdb/v2/context"
"github.com/influxdata/influxdb/v2/jsonweb"
errors2 "github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/influxdata/influxdb/v2/session"
"github.com/opentracing/opentracing-go"
"go.uber.org/zap"
)
// AuthenticationHandler is a middleware for authenticating incoming requests.
type AuthenticationHandler struct {
errors2.HTTPErrorHandler
log *zap.Logger
AuthorizationService platform.AuthorizationService
SessionService platform.SessionService
UserService platform.UserService
TokenParser *jsonweb.TokenParser
SessionRenewDisabled bool
// This is only really used for it's lookup method the specific http
// handler used to register routes does not matter.
noAuthRouter *httprouter.Router
Handler http.Handler
}
// NewAuthenticationHandler creates an authentication handler.
func NewAuthenticationHandler(log *zap.Logger, h errors2.HTTPErrorHandler) *AuthenticationHandler {
return &AuthenticationHandler{
log: log,
HTTPErrorHandler: h,
Handler: http.NotFoundHandler(),
TokenParser: jsonweb.NewTokenParser(jsonweb.EmptyKeyStore),
noAuthRouter: httprouter.New(),
}
}
// RegisterNoAuthRoute excludes routes from needing authentication.
func (h *AuthenticationHandler) RegisterNoAuthRoute(method, path string) {
// the handler specified here does not matter.
h.noAuthRouter.HandlerFunc(method, path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
}
const (
tokenAuthScheme = "token"
sessionAuthScheme = "session"
)
// ProbeAuthScheme probes the http request for the requests for token or cookie session.
func ProbeAuthScheme(r *http.Request) (string, error) {
_, tokenErr := GetToken(r)
_, sessErr := session.DecodeCookieSession(r.Context(), r)
if tokenErr != nil && sessErr != nil {
return "", fmt.Errorf("token required")
}
if tokenErr == nil {
return tokenAuthScheme, nil
}
return sessionAuthScheme, nil
}
func (h *AuthenticationHandler) unauthorized(ctx context.Context, w http.ResponseWriter, err error) {
h.log.Info("Unauthorized", zap.Error(err))
UnauthorizedError(ctx, h, w)
}
// ServeHTTP extracts the session or token from the http request and places the resulting authorizer on the request context.
func (h *AuthenticationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if handler, _, _ := h.noAuthRouter.Lookup(r.Method, r.URL.Path); handler != nil {
h.Handler.ServeHTTP(w, r)
return
}
ctx := r.Context()
scheme, err := ProbeAuthScheme(r)
if err != nil {
h.unauthorized(ctx, w, err)
return
}
var auth platform.Authorizer
switch scheme {
case tokenAuthScheme:
auth, err = h.extractAuthorization(ctx, r)
case sessionAuthScheme:
auth, err = h.extractSession(ctx, r)
default:
// TODO: this error will be nil if it gets here, this should be remedied with some
// sentinel error I'm thinking
err = errors.New("invalid auth scheme")
}
if err != nil {
h.unauthorized(ctx, w, err)
return
}
// jwt based auth is permission based rather than identity based
// and therefor has no associated user. if the user ID is invalid
// disregard the user active check
if auth.GetUserID().Valid() {
if err = h.isUserActive(ctx, auth); err != nil {
InactiveUserError(ctx, h, w)
return
}
}
ctx = platcontext.SetAuthorizer(ctx, auth)
if span := opentracing.SpanFromContext(ctx); span != nil {
span.SetTag("user_id", auth.GetUserID().String())
}
h.Handler.ServeHTTP(w, r.WithContext(ctx))
}
func (h *AuthenticationHandler) isUserActive(ctx context.Context, auth platform.Authorizer) error {
u, err := h.UserService.FindUserByID(ctx, auth.GetUserID())
if err != nil {
return err
}
if u.Status != "inactive" {
return nil
}
return &errors2.Error{Code: errors2.EForbidden, Msg: "User is inactive"}
}
func (h *AuthenticationHandler) extractAuthorization(ctx context.Context, r *http.Request) (platform.Authorizer, error) {
t, err := GetToken(r)
if err != nil {
return nil, err
}
token, err := h.TokenParser.Parse(t)
if err == nil {
return token, nil
}
// if the error returned signifies ths token is
// not a well formed JWT then use it as a lookup
// key for its associated authorization
// otherwise return the error
if !jsonweb.IsMalformedError(err) {
return nil, err
}
return h.AuthorizationService.FindAuthorizationByToken(ctx, t)
}
func (h *AuthenticationHandler) extractSession(ctx context.Context, r *http.Request) (*platform.Session, error) {
k, err := session.DecodeCookieSession(ctx, r)
if err != nil {
return nil, err
}
s, err := h.SessionService.FindSession(ctx, k)
if err != nil {
return nil, err
}
if !h.SessionRenewDisabled {
// if the session is not expired, renew the session
err = h.SessionService.RenewSession(ctx, s, time.Now().Add(platform.RenewSessionTime))
if err != nil {
return nil, err
}
}
return s, err
}