186 lines
5.0 KiB
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
|
|
}
|