From 0cf834e5329da10010c76e37ef82b3dafa8b43e3 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Tue, 2 Oct 2018 12:00:29 -0400 Subject: [PATCH] chore(platform): refactor platform http handler to allow for auth --- cmd/influxd/main.go | 110 +++++------------ http/api_handler.go | 249 +++++++++++++++++++++++++++++++++++++++ http/platform_handler.go | 164 +++----------------------- 3 files changed, 291 insertions(+), 232 deletions(-) create mode 100644 http/api_handler.go diff --git a/cmd/influxd/main.go b/cmd/influxd/main.go index f6471a8b5e..77d9048f0d 100644 --- a/cmd/influxd/main.go +++ b/cmd/influxd/main.go @@ -35,7 +35,6 @@ import ( taskbolt "github.com/influxdata/platform/task/backend/bolt" "github.com/influxdata/platform/task/backend/coordinator" taskexecutor "github.com/influxdata/platform/task/backend/executor" - pzap "github.com/influxdata/platform/zap" "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -174,9 +173,9 @@ func platformF(cmd *cobra.Command, args []string) { dashboardSvc = c } - var cellSvc platform.ViewService + var viewSvc platform.ViewService { - cellSvc = c + viewSvc = c } var sourceSvc platform.SourceService @@ -286,87 +285,34 @@ func platformF(cmd *cobra.Command, args []string) { Addr: httpBindAddress, } + handlerConfig := &http.APIBackend{ + Logger: logger, + NewBucketService: source.NewBucketService, + NewQueryService: source.NewQueryService, + PublisherFn: func(r io.Reader) error { + return publisher.Publish(IngressSubject, r) + }, + AuthorizationService: authSvc, + BucketService: bucketSvc, + SessionService: sessionSvc, + UserService: userSvc, + OrganizationService: orgSvc, + UserResourceMappingService: userResourceSvc, + DashboardService: dashboardSvc, + ViewService: viewSvc, + SourceService: sourceSvc, + MacroService: macroSvc, + BasicAuthService: basicAuthSvc, + OnboardingService: onboardingSvc, + QueryService: queryService, + TaskService: taskSvc, + ScraperTargetStoreService: scraperTargetSvc, + ChronografService: chronografSvc, + } + // HTTP server go func() { - sessionHandler := http.NewSessionHandler() - sessionHandler.BasicAuthService = basicAuthSvc - sessionHandler.SessionService = sessionSvc - - bucketHandler := http.NewBucketHandler() - bucketHandler.BucketService = bucketSvc - - orgHandler := http.NewOrgHandler() - orgHandler.OrganizationService = orgSvc - orgHandler.BucketService = bucketSvc - orgHandler.UserResourceMappingService = userResourceSvc - - userHandler := http.NewUserHandler() - userHandler.UserService = userSvc - - dashboardHandler := http.NewDashboardHandler() - dashboardHandler.DashboardService = dashboardSvc - - cellHandler := http.NewViewHandler() - cellHandler.ViewService = cellSvc - - macroHandler := http.NewMacroHandler() - macroHandler.MacroService = macroSvc - - authHandler := http.NewAuthorizationHandler() - authHandler.AuthorizationService = authSvc - authHandler.Logger = logger.With(zap.String("handler", "auth")) - - assetHandler := http.NewAssetHandler() - assetHandler.Develop = developerMode - fluxLangHandler := http.NewFluxLangHandler() - - sourceHandler := http.NewSourceHandler() - sourceHandler.SourceService = sourceSvc - sourceHandler.NewBucketService = source.NewBucketService - sourceHandler.NewQueryService = source.NewQueryService - - setupHandler := http.NewSetupHandler() - setupHandler.OnboardingService = onboardingSvc - - taskHandler := http.NewTaskHandler(logger) - taskHandler.TaskService = taskSvc - - publishFn := func(r io.Reader) error { - return publisher.Publish(IngressSubject, r) - } - - writeHandler := http.NewWriteHandler(publishFn) - writeHandler.AuthorizationService = authSvc - writeHandler.OrganizationService = orgSvc - writeHandler.BucketService = bucketSvc - writeHandler.Logger = logger.With(zap.String("handler", "write")) - - queryHandler := http.NewFluxHandler() - queryHandler.AuthorizationService = authSvc - queryHandler.OrganizationService = orgSvc - queryHandler.Logger = logger.With(zap.String("handler", "query")) - queryHandler.ProxyQueryService = pzap.NewProxyQueryService(queryHandler.Logger) - - // TODO(desa): what to do about idpe. - chronografHandler := http.NewChronografHandler(chronografSvc) - - platformHandler := &http.PlatformHandler{ - BucketHandler: bucketHandler, - OrgHandler: orgHandler, - UserHandler: userHandler, - AuthorizationHandler: authHandler, - DashboardHandler: dashboardHandler, - AssetHandler: assetHandler, - FluxLangHandler: fluxLangHandler, - ChronografHandler: chronografHandler, - SourceHandler: sourceHandler, - TaskHandler: taskHandler, - ViewHandler: cellHandler, - MacroHandler: macroHandler, - QueryHandler: queryHandler, - WriteHandler: writeHandler, - SetupHandler: setupHandler, - } + platformHandler := http.NewPlatformHandler(handlerConfig) reg.MustRegister(platformHandler.PrometheusCollectors()...) h := http.NewHandlerFromRegistry("platform", reg) diff --git a/http/api_handler.go b/http/api_handler.go new file mode 100644 index 0000000000..4ac494df07 --- /dev/null +++ b/http/api_handler.go @@ -0,0 +1,249 @@ +package http + +import ( + "io" + http "net/http" + "strings" + + "github.com/influxdata/platform" + "github.com/influxdata/platform/chronograf/server" + "github.com/influxdata/platform/query" + pzap "github.com/influxdata/platform/zap" + "go.uber.org/zap" +) + +// APIHandler is a collection of all the service handlers. +type APIHandler struct { + BucketHandler *BucketHandler + UserHandler *UserHandler + OrgHandler *OrgHandler + AuthorizationHandler *AuthorizationHandler + DashboardHandler *DashboardHandler + AssetHandler *AssetHandler + ChronografHandler *ChronografHandler + ViewHandler *ViewHandler + SourceHandler *SourceHandler + MacroHandler *MacroHandler + TaskHandler *TaskHandler + FluxLangHandler *FluxLangHandler + QueryHandler *FluxHandler + WriteHandler *WriteHandler + SetupHandler *SetupHandler + SessionHandler *SessionHandler +} + +// APIBackend is all services and associated parameters required to construct +// an APIHandler. +type APIBackend struct { + Logger *zap.Logger + + NewBucketService func(*platform.Source) (platform.BucketService, error) + NewQueryService func(*platform.Source) (query.ProxyQueryService, error) + + PublisherFn func(r io.Reader) error + + AuthorizationService platform.AuthorizationService + BucketService platform.BucketService + SessionService platform.SessionService + UserService platform.UserService + OrganizationService platform.OrganizationService + UserResourceMappingService platform.UserResourceMappingService + DashboardService platform.DashboardService + ViewService platform.ViewService + SourceService platform.SourceService + MacroService platform.MacroService + BasicAuthService platform.BasicAuthService + OnboardingService platform.OnboardingService + QueryService query.QueryService + TaskService platform.TaskService + ScraperTargetStoreService platform.ScraperTargetStoreService + ChronografService *server.Service +} + +// NewAPIHandler constructs all api handlers beneath it and returns an APIHandler +func NewAPIHandler(b *APIBackend) *APIHandler { + h := &APIHandler{} + h.SessionHandler = NewSessionHandler() + h.SessionHandler.BasicAuthService = b.BasicAuthService + h.SessionHandler.SessionService = b.SessionService + + h.BucketHandler = NewBucketHandler() + h.BucketHandler.BucketService = b.BucketService + + h.OrgHandler = NewOrgHandler() + h.OrgHandler.OrganizationService = b.OrganizationService + h.OrgHandler.BucketService = b.BucketService + h.OrgHandler.UserResourceMappingService = b.UserResourceMappingService + + h.UserHandler = NewUserHandler() + h.UserHandler.UserService = b.UserService + + h.DashboardHandler = NewDashboardHandler() + h.DashboardHandler.DashboardService = b.DashboardService + + h.ViewHandler = NewViewHandler() + h.ViewHandler.ViewService = b.ViewService + + h.MacroHandler = NewMacroHandler() + h.MacroHandler.MacroService = b.MacroService + + h.AuthorizationHandler = NewAuthorizationHandler() + h.AuthorizationHandler.AuthorizationService = b.AuthorizationService + h.AuthorizationHandler.Logger = b.Logger.With(zap.String("handler", "auth")) + + h.FluxLangHandler = NewFluxLangHandler() + + h.SourceHandler = NewSourceHandler() + h.SourceHandler.SourceService = b.SourceService + h.SourceHandler.NewBucketService = b.NewBucketService + h.SourceHandler.NewQueryService = b.NewQueryService + + h.SetupHandler = NewSetupHandler() + h.SetupHandler.OnboardingService = b.OnboardingService + + h.TaskHandler = NewTaskHandler(b.Logger) + h.TaskHandler.TaskService = b.TaskService + + h.WriteHandler = NewWriteHandler(b.PublisherFn) + h.WriteHandler.AuthorizationService = b.AuthorizationService + h.WriteHandler.OrganizationService = b.OrganizationService + h.WriteHandler.BucketService = b.BucketService + h.WriteHandler.Logger = b.Logger.With(zap.String("handler", "write")) + + h.QueryHandler = NewFluxHandler() + h.QueryHandler.AuthorizationService = b.AuthorizationService + h.QueryHandler.OrganizationService = b.OrganizationService + h.QueryHandler.Logger = b.Logger.With(zap.String("handler", "query")) + h.QueryHandler.ProxyQueryService = pzap.NewProxyQueryService(h.QueryHandler.Logger) + + h.ChronografHandler = NewChronografHandler(b.ChronografService) + + return h +} + +var apiLinks = map[string]interface{}{ + "signin": "/api/v2/signin", + "signout": "/api/v2/singout", + "setup": "/api/v2/setup", + "sources": "/api/v2/sources", + "dashboards": "/api/v2/dashboards", + "query": "/api/v2/query", + "write": "/api/v2/write", + "orgs": "/api/v2/orgs", + "auths": "/api/v2/authorizations", + "buckets": "/api/v2/buckets", + "users": "/api/v2/users", + "tasks": "/api/v2/tasks", + "flux": map[string]string{ + "self": "/api/v2/flux", + "ast": "/api/v2/flux/ast", + "suggestions": "/api/v2/flux/suggestions", + }, + "external": map[string]string{ + "statusFeed": "https://www.influxdata.com/feed/json", + }, + "system": map[string]string{ + "metrics": "/metrics", + "debug": "/debug/pprof", + "health": "/healthz", + }, +} + +func (h *APIHandler) serveLinks(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + if err := encodeResponse(ctx, w, http.StatusOK, apiLinks); err != nil { + EncodeError(ctx, err, w) + return + } +} + +// ServeHTTP delegates a request to the appropriate subhandler. +func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + setCORSResponseHeaders(w, r) + if r.Method == "OPTIONS" { + return + } + + // Serve the links base links for the API. + if r.URL.Path == "/api/v2/" || r.URL.Path == "/api/v2" { + h.serveLinks(w, r) + return + } + + if r.URL.Path == "/api/v2/signin" || r.URL.Path == "/api/v2/signout" { + h.SessionHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/setup") { + h.SetupHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/write") { + h.WriteHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/query") { + h.QueryHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/buckets") { + h.BucketHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/users") { + h.UserHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/orgs") { + h.OrgHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/authorizations") { + h.AuthorizationHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/dashboards") { + h.DashboardHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/sources") { + h.SourceHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/tasks") { + h.TaskHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/views") { + h.ViewHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/macros") { + h.MacroHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/api/v2/flux") { + h.FluxLangHandler.ServeHTTP(w, r) + return + } + + if strings.HasPrefix(r.URL.Path, "/chronograf/") { + h.ChronografHandler.ServeHTTP(w, r) + return + } + + http.NotFound(w, r) +} diff --git a/http/platform_handler.go b/http/platform_handler.go index 3dd1a40872..f6c58b10c2 100644 --- a/http/platform_handler.go +++ b/http/platform_handler.go @@ -1,35 +1,19 @@ package http import ( - "context" - nethttp "net/http" + "net/http" "strings" - idpctx "github.com/influxdata/platform/context" "github.com/prometheus/client_golang/prometheus" ) // PlatformHandler is a collection of all the service handlers. type PlatformHandler struct { - BucketHandler *BucketHandler - UserHandler *UserHandler - OrgHandler *OrgHandler - AuthorizationHandler *AuthorizationHandler - DashboardHandler *DashboardHandler - AssetHandler *AssetHandler - ChronografHandler *ChronografHandler - ViewHandler *ViewHandler - SourceHandler *SourceHandler - MacroHandler *MacroHandler - TaskHandler *TaskHandler - FluxLangHandler *FluxLangHandler - QueryHandler *FluxHandler - WriteHandler *WriteHandler - SetupHandler *SetupHandler - SessionHandler *SessionHandler + AssetHandler *AssetHandler + APIHandler http.Handler } -func setCORSResponseHeaders(w nethttp.ResponseWriter, r *nethttp.Request) { +func setCORSResponseHeaders(w http.ResponseWriter, r *http.Request) { if origin := r.Header.Get("Origin"); origin != "" { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") @@ -37,44 +21,20 @@ func setCORSResponseHeaders(w nethttp.ResponseWriter, r *nethttp.Request) { } } -var platformLinks = map[string]interface{}{ - "signin": "/api/v2/signin", - "signout": "/api/v2/singout", - "setup": "/api/v2/setup", - "sources": "/api/v2/sources", - "dashboards": "/api/v2/dashboards", - "query": "/api/v2/query", - "write": "/api/v2/write", - "orgs": "/api/v2/orgs", - "auths": "/api/v2/authorizations", - "buckets": "/api/v2/buckets", - "users": "/api/v2/users", - "tasks": "/api/v2/tasks", - "flux": map[string]string{ - "self": "/api/v2/flux", - "ast": "/api/v2/flux/ast", - "suggestions": "/api/v2/flux/suggestions", - }, - "external": map[string]string{ - "statusFeed": "https://www.influxdata.com/feed/json", - }, - "system": map[string]string{ - "metrics": "/metrics", - "debug": "/debug/pprof", - "health": "/healthz", - }, -} +// NewPlatformHandler returns a platform handler that serves the API and associated assets. +func NewPlatformHandler(b *APIBackend) *PlatformHandler { + h := NewAuthenticationHandler() + h.Handler = NewAPIHandler(b) + h.RegisterNoAuthRoute("GET", "/api/v2") -func (h *PlatformHandler) serveLinks(w nethttp.ResponseWriter, r *nethttp.Request) { - ctx := r.Context() - if err := encodeResponse(ctx, w, nethttp.StatusOK, platformLinks); err != nil { - EncodeError(ctx, err, w) - return + return &PlatformHandler{ + AssetHandler: NewAssetHandler(), + APIHandler: h, } } // ServeHTTP delegates a request to the appropriate subhandler. -func (h *PlatformHandler) ServeHTTP(w nethttp.ResponseWriter, r *nethttp.Request) { +func (h *PlatformHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { setCORSResponseHeaders(w, r) if r.Method == "OPTIONS" { return @@ -89,95 +49,7 @@ func (h *PlatformHandler) ServeHTTP(w nethttp.ResponseWriter, r *nethttp.Request return } - // Serve the links base links for the API. - if r.URL.Path == "/api/v2/" || r.URL.Path == "/api/v2" { - h.serveLinks(w, r) - return - } - - if r.URL.Path == "/api/v2/signin" || r.URL.Path == "/api/v2/signout" { - h.SessionHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/setup") { - h.SetupHandler.ServeHTTP(w, r) - return - } - - ctx := r.Context() - var err error - if ctx, err = extractAuthorization(ctx, r); err != nil { - // TODO(desa): remove this when the authentication middleware is added. - } - r = r.WithContext(ctx) - - if strings.HasPrefix(r.URL.Path, "/api/v2/write") { - h.WriteHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/query") { - h.QueryHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/buckets") { - h.BucketHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/users") { - h.UserHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/orgs") { - h.OrgHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/authorizations") { - h.AuthorizationHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/dashboards") { - h.DashboardHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/sources") { - h.SourceHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/tasks") { - h.TaskHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/views") { - h.ViewHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/macros") { - h.MacroHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/api/v2/flux") { - h.FluxLangHandler.ServeHTTP(w, r) - return - } - - if strings.HasPrefix(r.URL.Path, "/chronograf/") { - h.ChronografHandler.ServeHTTP(w, r) - return - } - - nethttp.NotFound(w, r) + h.APIHandler.ServeHTTP(w, r) } // PrometheusCollectors satisfies the prom.PrometheusCollector interface. @@ -185,11 +57,3 @@ func (h *PlatformHandler) PrometheusCollectors() []prometheus.Collector { // TODO: collect and return relevant metrics. return nil } - -func extractAuthorization(ctx context.Context, r *nethttp.Request) (context.Context, error) { - t, err := GetToken(r) - if err != nil { - return ctx, err - } - return idpctx.SetToken(ctx, t), nil -}