influxdb/chronograf/server/mux.go

456 lines
19 KiB
Go
Raw Normal View History

2016-10-25 15:20:06 +00:00
package server
import (
"encoding/json"
"fmt"
"net/http"
"path"
2016-10-25 15:20:06 +00:00
"strconv"
"strings"
_ "net/http/pprof"
"github.com/NYTimes/gziphandler"
2016-10-25 15:20:06 +00:00
"github.com/bouk/httprouter"
"github.com/influxdata/influxdb/chronograf"
"github.com/influxdata/influxdb/chronograf/oauth2"
"github.com/influxdata/influxdb/chronograf/roles"
jhttprouter "github.com/julienschmidt/httprouter"
2016-10-25 15:20:06 +00:00
)
const (
// JSONType the mimetype for a json request
JSONType = "application/json"
)
// MuxOpts are the options for the router. Mostly related to auth.
2016-10-25 15:20:06 +00:00
type MuxOpts struct {
Add new auth duration CLI option; add client heartbeat; fix logout (#1119) * User can now set oauth cookie session duration via the CLI to any duration or to expire on browser close * Refactor GET 'me' into heartbeat at constant interval * Add ping route to all routes * Add /chronograf/v1/ping endpoint for server status * Refactor cookie generation to use an interface * WIP adding refreshable tokens * Add reminder to review index.js Login error handling * Refactor Authenticator interface to accommodate cookie duration and logout delay * Update make run-dev to be more TICKStack compliant * Remove heartbeat/logout duration from authentication * WIP Refactor tests to accommodate cookie and auth refactor * Update oauth2 tests to newly refactored design * Update oauth provider tests * Remove unused oauth2/consts.go * Move authentication middleware to server package * Fix authentication comment * Update authenication documentation to mention AUTH_DURATION * Update /chronograf/v1/ping to simply return 204 * Fix Makefile run-dev target * Remove spurious ping route * Update auth docs to clarify authentication duration * Revert "Refactor GET 'me' into heartbeat at constant interval" This reverts commit 298a8c47e1431720d9bd97a9cb853744f04501a3. Conflicts: ui/src/index.js * Add auth test for JWT signing method * Add comments for why coverage isn't written for some areas of jwt code * Update auth docs to explicitly mention how to require re-auth for all users on server restart * Add Duration to Validation interface for Tokens * Make auth duration of zero yield a everlasting token * Revert "Revert "Refactor GET 'me' into heartbeat at constant interval"" This reverts commit b4773c15afe4fcd227ad88aa9d5686beb6b0a6cd. * Rename http status constants and add FORBIDDEN * Heartbeat only when logged in, notify user if heartbeat fails * Update changelog * Fix minor word semantics * Update oauth2 tests to be in the oauth2_test package * Add check at compile time that JWT implements Tokenizer * Rename CookieMux to AuthMux for consistency with earlier refactor * Fix logout middleware * Fix logout button not showing due to obsolete data shape expectations * Update changelog * Fix proptypes for logout button data shape in SideNav
2017-04-06 18:40:57 +00:00
Logger chronograf.Logger
Develop bool // Develop loads assets from filesystem instead of bindata
Basepath string // URL path prefix under which all chronograf routes will be mounted
UseAuth bool // UseAuth turns on Github OAuth and JWT
Auth oauth2.Authenticator // Auth is used to authenticate and authorize
ProviderFuncs []func(func(oauth2.Provider, oauth2.Mux))
StatusFeedURL string // JSON Feed URL for the client Status page News Feed
CustomLinks map[string]string // Any custom external links for client's User menu
PprofEnabled bool // Mount pprof routes for profiling
2016-10-25 15:20:06 +00:00
}
// NewMux attaches all the route handlers; handler returned servers chronograf.
func NewMux(opts MuxOpts, service Service) http.Handler {
hr := httprouter.New()
2016-10-25 15:20:06 +00:00
/* React Application */
assets := Assets(AssetsOpts{
Develop: opts.Develop,
Logger: opts.Logger,
})
// Prefix any URLs found in the React assets with any configured basepath
prefixedAssets := NewDefaultURLPrefixer(opts.Basepath, assets, opts.Logger)
// Compress the assets with gzip if an accepted encoding
compressed := gziphandler.GzipHandler(prefixedAssets)
2016-10-25 15:20:06 +00:00
// The react application handles all the routing if the server does not
// know about the route. This means that we never have unknown routes on
// the server.
hr.NotFound = compressed
var router chronograf.Router = hr
// Set route prefix for all routes if basepath is present
if opts.Basepath != "" {
router = &MountableRouter{
Prefix: opts.Basepath,
Delegate: hr,
}
//The assets handler is always unaware of basepaths, so the
// basepath needs to always be removed before sending requests to it
hr.NotFound = http.StripPrefix(opts.Basepath, hr.NotFound)
}
2016-10-25 15:20:06 +00:00
EnsureMember := func(next http.HandlerFunc) http.HandlerFunc {
return AuthorizedUser(
service.Store,
opts.UseAuth,
roles.MemberRoleName,
opts.Logger,
next,
)
}
_ = EnsureMember
EnsureViewer := func(next http.HandlerFunc) http.HandlerFunc {
return AuthorizedUser(
service.Store,
opts.UseAuth,
roles.ViewerRoleName,
opts.Logger,
next,
)
}
EnsureEditor := func(next http.HandlerFunc) http.HandlerFunc {
return AuthorizedUser(
service.Store,
opts.UseAuth,
roles.EditorRoleName,
opts.Logger,
next,
)
}
EnsureAdmin := func(next http.HandlerFunc) http.HandlerFunc {
return AuthorizedUser(
service.Store,
opts.UseAuth,
roles.AdminRoleName,
opts.Logger,
next,
)
}
2017-11-01 00:58:40 +00:00
EnsureSuperAdmin := func(next http.HandlerFunc) http.HandlerFunc {
return AuthorizedUser(
service.Store,
opts.UseAuth,
roles.SuperAdminStatus,
2017-11-01 00:58:40 +00:00
opts.Logger,
next,
)
}
rawStoreAccess := func(next http.HandlerFunc) http.HandlerFunc {
return RawStoreAccess(opts.Logger, next)
}
ensureOrgMatches := func(next http.HandlerFunc) http.HandlerFunc {
return RouteMatchesPrincipal(
service.Store,
opts.UseAuth,
opts.Logger,
next,
)
}
if opts.PprofEnabled {
// add profiling routes
router.GET("/debug/pprof/:thing", http.DefaultServeMux.ServeHTTP)
}
2016-10-25 15:20:06 +00:00
/* Documentation */
router.GET("/swagger.json", Spec())
router.GET("/docs", Redoc("/swagger.json"))
2016-10-25 15:20:06 +00:00
/* API */
2017-10-20 19:42:34 +00:00
// Organizations
router.GET("/chronograf/v1/organizations", EnsureAdmin(service.Organizations))
2017-11-01 00:58:40 +00:00
router.POST("/chronograf/v1/organizations", EnsureSuperAdmin(service.NewOrganization))
router.GET("/chronograf/v1/organizations/:oid", EnsureAdmin(service.OrganizationID))
router.PATCH("/chronograf/v1/organizations/:oid", EnsureSuperAdmin(service.UpdateOrganization))
router.DELETE("/chronograf/v1/organizations/:oid", EnsureSuperAdmin(service.RemoveOrganization))
2017-10-20 19:42:34 +00:00
2018-02-05 19:54:39 +00:00
// Mappings
2018-02-05 21:47:44 +00:00
router.GET("/chronograf/v1/mappings", EnsureSuperAdmin(service.Mappings))
router.POST("/chronograf/v1/mappings", EnsureSuperAdmin(service.NewMapping))
2018-02-05 19:54:39 +00:00
2018-02-05 21:47:44 +00:00
router.PUT("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.UpdateMapping))
router.DELETE("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.RemoveMapping))
2018-02-05 19:54:39 +00:00
2016-10-25 15:20:06 +00:00
// Sources
router.GET("/chronograf/v1/sources", EnsureViewer(service.Sources))
router.POST("/chronograf/v1/sources", EnsureEditor(service.NewSource))
2016-10-25 15:20:06 +00:00
router.GET("/chronograf/v1/sources/:id", EnsureViewer(service.SourcesID))
router.PATCH("/chronograf/v1/sources/:id", EnsureEditor(service.UpdateSource))
router.DELETE("/chronograf/v1/sources/:id", EnsureEditor(service.RemoveSource))
2018-04-03 22:58:33 +00:00
router.GET("/chronograf/v1/sources/:id/health", EnsureViewer(service.SourceHealth))
2016-10-25 15:20:06 +00:00
// Source Proxy to Influx; Has gzip compression around the handler
influx := gziphandler.GzipHandler(http.HandlerFunc(EnsureViewer(service.Influx)))
router.Handler("POST", "/chronograf/v1/sources/:id/proxy", influx)
2016-10-25 15:20:06 +00:00
// Write proxies line protocol write requests to InfluxDB
router.POST("/chronograf/v1/sources/:id/write", EnsureViewer(service.Write))
// Queries is used to analyze a specific queries and does not create any
// resources. It's a POST because Queries are POSTed to InfluxDB, but this
// only modifies InfluxDB resources with certain metaqueries, e.g. DROP DATABASE.
//
// Admins should ensure that the InfluxDB source as the proper permissions
// intended for Chronograf Users with the Viewer Role type.
router.POST("/chronograf/v1/sources/:id/queries", EnsureViewer(service.Queries))
2018-01-12 23:17:14 +00:00
// Annotations are user-defined events associated with this source
router.GET("/chronograf/v1/sources/:id/annotations", EnsureViewer(service.Annotations))
router.POST("/chronograf/v1/sources/:id/annotations", EnsureEditor(service.NewAnnotation))
router.GET("/chronograf/v1/sources/:id/annotations/:aid", EnsureViewer(service.Annotation))
2018-01-12 23:17:14 +00:00
router.DELETE("/chronograf/v1/sources/:id/annotations/:aid", EnsureEditor(service.RemoveAnnotation))
router.PATCH("/chronograf/v1/sources/:id/annotations/:aid", EnsureEditor(service.UpdateAnnotation))
2018-01-12 23:17:14 +00:00
// All possible permissions for users in this source
router.GET("/chronograf/v1/sources/:id/permissions", EnsureViewer(service.Permissions))
2017-02-18 02:47:23 +00:00
// Users associated with the data source
router.GET("/chronograf/v1/sources/:id/users", EnsureAdmin(service.SourceUsers))
router.POST("/chronograf/v1/sources/:id/users", EnsureAdmin(service.NewSourceUser))
2017-02-18 02:47:23 +00:00
router.GET("/chronograf/v1/sources/:id/users/:uid", EnsureAdmin(service.SourceUserID))
router.DELETE("/chronograf/v1/sources/:id/users/:uid", EnsureAdmin(service.RemoveSourceUser))
router.PATCH("/chronograf/v1/sources/:id/users/:uid", EnsureAdmin(service.UpdateSourceUser))
2017-02-18 02:47:23 +00:00
// Roles associated with the data source
router.GET("/chronograf/v1/sources/:id/roles", EnsureViewer(service.SourceRoles))
router.POST("/chronograf/v1/sources/:id/roles", EnsureEditor(service.NewSourceRole))
router.GET("/chronograf/v1/sources/:id/roles/:rid", EnsureViewer(service.SourceRoleID))
router.DELETE("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.RemoveSourceRole))
router.PATCH("/chronograf/v1/sources/:id/roles/:rid", EnsureEditor(service.UpdateSourceRole))
2016-10-25 15:20:06 +00:00
// Services are resources that chronograf proxies to
router.GET("/chronograf/v1/sources/:id/services", EnsureViewer(service.Services))
router.POST("/chronograf/v1/sources/:id/services", EnsureEditor(service.NewService))
router.GET("/chronograf/v1/sources/:id/services/:kid", EnsureViewer(service.ServiceID))
router.PATCH("/chronograf/v1/sources/:id/services/:kid", EnsureEditor(service.UpdateService))
router.DELETE("/chronograf/v1/sources/:id/services/:kid", EnsureEditor(service.RemoveService))
// Service Proxy
router.GET("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureViewer(service.ProxyGet))
router.POST("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyPost))
router.PATCH("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyPatch))
router.DELETE("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyDelete))
2016-10-25 15:20:06 +00:00
// Kapacitor
//router.GET("/chronograf/v1/sources/:id/kapacitors", EnsureViewer(service.Kapacitors))
//router.POST("/chronograf/v1/sources/:id/kapacitors", EnsureEditor(service.NewKapacitor))
//router.GET("/chronograf/v1/sources/:id/kapacitors/:kid", EnsureViewer(service.KapacitorsID))
//router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid", EnsureEditor(service.UpdateKapacitor))
//router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid", EnsureEditor(service.RemoveKapacitor))
//// Kapacitor rules
//router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/rules", EnsureViewer(service.KapacitorRulesGet))
//router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/rules", EnsureEditor(service.KapacitorRulesPost))
//router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureViewer(service.KapacitorRulesID))
//router.PUT("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesPut))
//router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesStatus))
//router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/rules/:tid", EnsureEditor(service.KapacitorRulesDelete))
//// Kapacitor Proxy
//router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureViewer(service.ProxyGet))
//router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyPost))
//router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyPatch))
//router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", EnsureEditor(service.ProxyDelete))
2016-10-25 15:20:06 +00:00
// Layouts
router.GET("/chronograf/v1/layouts", EnsureViewer(service.Layouts))
router.GET("/chronograf/v1/layouts/:id", EnsureViewer(service.LayoutsID))
2016-10-25 15:20:06 +00:00
// Users associated with Chronograf
router.GET("/chronograf/v1/me", service.Me)
2016-10-25 15:20:06 +00:00
// Set current chronograf organization the user is logged into
2017-11-10 21:17:46 +00:00
router.PUT("/chronograf/v1/me", service.UpdateMe(opts.Auth))
2017-11-01 00:58:40 +00:00
// TODO(desa): what to do about admin's being able to set superadmin
router.GET("/chronograf/v1/organizations/:oid/users", EnsureAdmin(ensureOrgMatches(service.Users)))
router.POST("/chronograf/v1/organizations/:oid/users", EnsureAdmin(ensureOrgMatches(service.NewUser)))
router.GET("/chronograf/v1/organizations/:oid/users/:id", EnsureAdmin(ensureOrgMatches(service.UserID)))
router.DELETE("/chronograf/v1/organizations/:oid/users/:id", EnsureAdmin(ensureOrgMatches(service.RemoveUser)))
router.PATCH("/chronograf/v1/organizations/:oid/users/:id", EnsureAdmin(ensureOrgMatches(service.UpdateUser)))
router.GET("/chronograf/v1/users", EnsureSuperAdmin(rawStoreAccess(service.Users)))
router.POST("/chronograf/v1/users", EnsureSuperAdmin(rawStoreAccess(service.NewUser)))
router.GET("/chronograf/v1/users/:id", EnsureSuperAdmin(rawStoreAccess(service.UserID)))
router.DELETE("/chronograf/v1/users/:id", EnsureSuperAdmin(rawStoreAccess(service.RemoveUser)))
router.PATCH("/chronograf/v1/users/:id", EnsureSuperAdmin(rawStoreAccess(service.UpdateUser)))
// Dashboards
router.GET("/chronograf/v1/dashboards", EnsureViewer(service.Dashboards))
router.POST("/chronograf/v1/dashboards", EnsureEditor(service.NewDashboard))
router.GET("/chronograf/v1/dashboards/:id", EnsureViewer(service.DashboardID))
router.DELETE("/chronograf/v1/dashboards/:id", EnsureEditor(service.RemoveDashboard))
router.PUT("/chronograf/v1/dashboards/:id", EnsureEditor(service.ReplaceDashboard))
router.PATCH("/chronograf/v1/dashboards/:id", EnsureEditor(service.UpdateDashboard))
Introduce ability to edit a dashboard cell * Correct documentation for dashboards * Exclude .git and use 'make run-dev' in 'make continuous' * Fix dashboard deletion bug where id serialization was wrong * Commence creation of overlay technology, add autoRefresh props to DashboardPage * Enhance overlay magnitude of overlay technology * Add confirm buttons to overlay technology * Refactor ResizeContainer to accommodate arbitrary containers * Refactor ResizeContainer to require explicit ResizeTop and ResizeBottom for clarity * Add markup and styles for OverlayControls * CellEditorOverlay needs a larger minimum bottom height to accommodate more things * Revert Visualization to not use ResizeTop or flex-box * Remove TODO and move to issue * Refactor CellEditorOverlay to allow selection of graph type * Style Overlay controls, move confirm buttons to own stylesheet * Fix toggle buttons in overlay so active is actually active * Block user-select on a few UI items * Update cell query shape to support Visualization and LayoutRenderer * Code cleanup * Repair fixture schema; update props for affected components * Wired up selectedGraphType and activeQueryID in CellEditorOverlay * Wire up chooseMeasurements in QueryBuilder Pass queryActions into QueryBuilder so that DataExplorer can provide actionCreators and CellEditorOverlay can provide functions that modify its component state * semicolon cleanup * Bind all queryModifier actions to component state with a stateReducer * Overlay Technologies™ can add and delete a query from a cell * Semicolon cleanup * Add conversion of InfluxQL to QueryConfig for dashboards * Update go deps to add influxdb at af72d9b0e4ebe95be30e89b160f43eabaf0529ed * Updated docs for dashboard query config * Update CHANGELOG to mention InfluxQL to QueryConfig * Make reducer’s name more specific for clarity * Remove 'table' as graphType * Make graph renaming prettier * Remove duplicate DashboardQuery in swagger.json * Fix swagger to include name and links for Cell * Refactor CellEditorOverlay to enable graph type selection * Add link.self to all Dashboard cells; add bolt migrations * Make dash graph names only hover on contents * Consolidate timeRange format patterns, clean up * Add cell endpoints to dashboards * Include Line + Stat in Visualization Type list * Add cell link to dashboards * Enable step plot and stacked graph in Visualization * Overlay Technologies are summonable and dismissable * OverlayTechnologies saves changes to a cell * Convert NameableGraph to createClass for state This was converted from a pure function to encapsulate the state of the buttons. An attempt was made previously to store this state in Redux, but it proved too convoluted with the current state of the reducers for cells and dashboards. Another effort must take place to separate a cell reducer to manage the state of an individual cell in Redux in order for this state to be sanely kept in Redux as well. For the time being, this state is being kept in the component for the sake of expeditiousness, since this is needed for Dashboards to be released. A refactor of this will occur later. * Cells should contain a links key in server response * Clean up console logs * Use live data instead of a cellQuery fixture * Update docs for dashboard creation * DB and RP are already present in the Command field * Fix LayoutRenderer’s understanding of query schema * Return a new object, rather that mutate in place * Visualization doesn’t use activeQueryID * Selected is an object, not a string * QueryBuilder refactored to use query index instead of query id * CellEditorOverlay refactored to use query index instead of query id * ConfirmButtons doesn’t need to act on an item * Rename functions to follow convention * Queries are no longer guaranteed to have ids * Omit WHERE and GROUP BY clauses when saving query * Select new query on add in OverlayTechnologies * Add click outside to dash graph menu, style menu also * Change context menu from ... to a caret More consistent with the rest of the UI, better affordance * Hide graph context menu in presentation mode Don’t want people editing a dashboard from presentation mode * Move graph refreshing spinner so it does not overlap with context menu * Wire up Cell Menu to Overlay Technologies * Correct empty dashboard type * Refactor dashboard spec fixtures * Test syncDashboardCell reducer * Remove Delete button from graph dropdown menu (for now) * Update changelog
2017-03-24 00:12:33 +00:00
// Dashboard Cells
router.GET("/chronograf/v1/dashboards/:id/cells", EnsureViewer(service.DashboardCells))
router.POST("/chronograf/v1/dashboards/:id/cells", EnsureEditor(service.NewDashboardCell))
Introduce ability to edit a dashboard cell * Correct documentation for dashboards * Exclude .git and use 'make run-dev' in 'make continuous' * Fix dashboard deletion bug where id serialization was wrong * Commence creation of overlay technology, add autoRefresh props to DashboardPage * Enhance overlay magnitude of overlay technology * Add confirm buttons to overlay technology * Refactor ResizeContainer to accommodate arbitrary containers * Refactor ResizeContainer to require explicit ResizeTop and ResizeBottom for clarity * Add markup and styles for OverlayControls * CellEditorOverlay needs a larger minimum bottom height to accommodate more things * Revert Visualization to not use ResizeTop or flex-box * Remove TODO and move to issue * Refactor CellEditorOverlay to allow selection of graph type * Style Overlay controls, move confirm buttons to own stylesheet * Fix toggle buttons in overlay so active is actually active * Block user-select on a few UI items * Update cell query shape to support Visualization and LayoutRenderer * Code cleanup * Repair fixture schema; update props for affected components * Wired up selectedGraphType and activeQueryID in CellEditorOverlay * Wire up chooseMeasurements in QueryBuilder Pass queryActions into QueryBuilder so that DataExplorer can provide actionCreators and CellEditorOverlay can provide functions that modify its component state * semicolon cleanup * Bind all queryModifier actions to component state with a stateReducer * Overlay Technologies™ can add and delete a query from a cell * Semicolon cleanup * Add conversion of InfluxQL to QueryConfig for dashboards * Update go deps to add influxdb at af72d9b0e4ebe95be30e89b160f43eabaf0529ed * Updated docs for dashboard query config * Update CHANGELOG to mention InfluxQL to QueryConfig * Make reducer’s name more specific for clarity * Remove 'table' as graphType * Make graph renaming prettier * Remove duplicate DashboardQuery in swagger.json * Fix swagger to include name and links for Cell * Refactor CellEditorOverlay to enable graph type selection * Add link.self to all Dashboard cells; add bolt migrations * Make dash graph names only hover on contents * Consolidate timeRange format patterns, clean up * Add cell endpoints to dashboards * Include Line + Stat in Visualization Type list * Add cell link to dashboards * Enable step plot and stacked graph in Visualization * Overlay Technologies are summonable and dismissable * OverlayTechnologies saves changes to a cell * Convert NameableGraph to createClass for state This was converted from a pure function to encapsulate the state of the buttons. An attempt was made previously to store this state in Redux, but it proved too convoluted with the current state of the reducers for cells and dashboards. Another effort must take place to separate a cell reducer to manage the state of an individual cell in Redux in order for this state to be sanely kept in Redux as well. For the time being, this state is being kept in the component for the sake of expeditiousness, since this is needed for Dashboards to be released. A refactor of this will occur later. * Cells should contain a links key in server response * Clean up console logs * Use live data instead of a cellQuery fixture * Update docs for dashboard creation * DB and RP are already present in the Command field * Fix LayoutRenderer’s understanding of query schema * Return a new object, rather that mutate in place * Visualization doesn’t use activeQueryID * Selected is an object, not a string * QueryBuilder refactored to use query index instead of query id * CellEditorOverlay refactored to use query index instead of query id * ConfirmButtons doesn’t need to act on an item * Rename functions to follow convention * Queries are no longer guaranteed to have ids * Omit WHERE and GROUP BY clauses when saving query * Select new query on add in OverlayTechnologies * Add click outside to dash graph menu, style menu also * Change context menu from ... to a caret More consistent with the rest of the UI, better affordance * Hide graph context menu in presentation mode Don’t want people editing a dashboard from presentation mode * Move graph refreshing spinner so it does not overlap with context menu * Wire up Cell Menu to Overlay Technologies * Correct empty dashboard type * Refactor dashboard spec fixtures * Test syncDashboardCell reducer * Remove Delete button from graph dropdown menu (for now) * Update changelog
2017-03-24 00:12:33 +00:00
router.GET("/chronograf/v1/dashboards/:id/cells/:cid", EnsureViewer(service.DashboardCellID))
router.DELETE("/chronograf/v1/dashboards/:id/cells/:cid", EnsureEditor(service.RemoveDashboardCell))
router.PUT("/chronograf/v1/dashboards/:id/cells/:cid", EnsureEditor(service.ReplaceDashboardCell))
2017-04-20 16:09:56 +00:00
// Dashboard Templates
router.GET("/chronograf/v1/dashboards/:id/templates", EnsureViewer(service.Templates))
router.POST("/chronograf/v1/dashboards/:id/templates", EnsureEditor(service.NewTemplate))
2017-04-20 16:09:56 +00:00
router.GET("/chronograf/v1/dashboards/:id/templates/:tid", EnsureViewer(service.TemplateID))
router.DELETE("/chronograf/v1/dashboards/:id/templates/:tid", EnsureEditor(service.RemoveTemplate))
router.PUT("/chronograf/v1/dashboards/:id/templates/:tid", EnsureEditor(service.ReplaceTemplate))
2017-03-20 21:23:29 +00:00
// Databases
router.GET("/chronograf/v1/sources/:id/dbs", EnsureViewer(service.GetDatabases))
router.POST("/chronograf/v1/sources/:id/dbs", EnsureEditor(service.NewDatabase))
router.DELETE("/chronograf/v1/sources/:id/dbs/:db", EnsureEditor(service.DropDatabase))
2017-03-23 10:06:59 +00:00
// Retention Policies
router.GET("/chronograf/v1/sources/:id/dbs/:db/rps", EnsureViewer(service.RetentionPolicies))
router.POST("/chronograf/v1/sources/:id/dbs/:db/rps", EnsureEditor(service.NewRetentionPolicy))
2017-03-23 11:51:08 +00:00
router.PUT("/chronograf/v1/sources/:id/dbs/:db/rps/:rp", EnsureEditor(service.UpdateRetentionPolicy))
router.DELETE("/chronograf/v1/sources/:id/dbs/:db/rps/:rp", EnsureEditor(service.DropRetentionPolicy))
2018-02-21 02:32:46 +00:00
// Measurements
router.GET("/chronograf/v1/sources/:id/dbs/:db/measurements", EnsureViewer(service.Measurements))
2018-02-21 02:32:46 +00:00
// Global application config for Chronograf
router.GET("/chronograf/v1/config", EnsureSuperAdmin(service.Config))
router.GET("/chronograf/v1/config/auth", EnsureSuperAdmin(service.AuthConfig))
router.PUT("/chronograf/v1/config/auth", EnsureSuperAdmin(service.ReplaceAuthConfig))
// Organization config settings for Chronograf
router.GET("/chronograf/v1/org_config", EnsureViewer(service.OrganizationConfig))
router.GET("/chronograf/v1/org_config/logviewer", EnsureViewer(service.OrganizationLogViewerConfig))
router.PUT("/chronograf/v1/org_config/logviewer", EnsureEditor(service.ReplaceOrganizationLogViewerConfig))
router.GET("/chronograf/v1/env", EnsureViewer(service.Environment))
allRoutes := &AllRoutes{
Logger: opts.Logger,
StatusFeed: opts.StatusFeedURL,
CustomLinks: opts.CustomLinks,
}
getPrincipal := func(r *http.Request) oauth2.Principal {
p, _ := HasAuthorizedToken(opts.Auth, r)
return p
}
allRoutes.GetPrincipal = getPrincipal
router.Handler("GET", "/chronograf/v1/", allRoutes)
var out http.Handler
/* Authentication */
2016-10-25 15:20:06 +00:00
if opts.UseAuth {
// Encapsulate the router with OAuth2
var auth http.Handler
auth, allRoutes.AuthRoutes = AuthAPI(opts, router)
allRoutes.LogoutLink = path.Join(opts.Basepath, "/oauth/logout")
// Create middleware that redirects to the appropriate provider logout
router.GET("/oauth/logout", Logout("/", opts.Basepath, allRoutes.AuthRoutes))
out = Logger(opts.Logger, FlushingHandler(auth))
} else {
out = Logger(opts.Logger, FlushingHandler(router))
2016-10-25 15:20:06 +00:00
}
return out
2016-10-25 15:20:06 +00:00
}
// AuthAPI adds the OAuth routes if auth is enabled.
func AuthAPI(opts MuxOpts, router chronograf.Router) (http.Handler, AuthRoutes) {
routes := AuthRoutes{}
for _, pf := range opts.ProviderFuncs {
pf(func(p oauth2.Provider, m oauth2.Mux) {
2017-04-07 20:32:35 +00:00
urlName := PathEscape(strings.ToLower(p.Name()))
loginPath := path.Join("/oauth", urlName, "login")
logoutPath := path.Join("/oauth", urlName, "logout")
callbackPath := path.Join("/oauth", urlName, "callback")
router.Handler("GET", loginPath, m.Login())
router.Handler("GET", logoutPath, m.Logout())
router.Handler("GET", callbackPath, m.Callback())
routes = append(routes, AuthRoute{
Name: p.Name(),
Label: strings.Title(p.Name()),
// AuthRoutes are content served to the page. When Basepath is set, it
// says that all content served to the page will be prefixed with the
// basepath. Since these routes are consumed by JS, it will need the
// basepath set to traverse a proxy correctly
Login: path.Join(opts.Basepath, loginPath),
Logout: path.Join(opts.Basepath, logoutPath),
Callback: path.Join(opts.Basepath, callbackPath),
})
})
}
rootPath := path.Join(opts.Basepath, "/chronograf/v1")
logoutPath := path.Join(opts.Basepath, "/oauth/logout")
Add new auth duration CLI option; add client heartbeat; fix logout (#1119) * User can now set oauth cookie session duration via the CLI to any duration or to expire on browser close * Refactor GET 'me' into heartbeat at constant interval * Add ping route to all routes * Add /chronograf/v1/ping endpoint for server status * Refactor cookie generation to use an interface * WIP adding refreshable tokens * Add reminder to review index.js Login error handling * Refactor Authenticator interface to accommodate cookie duration and logout delay * Update make run-dev to be more TICKStack compliant * Remove heartbeat/logout duration from authentication * WIP Refactor tests to accommodate cookie and auth refactor * Update oauth2 tests to newly refactored design * Update oauth provider tests * Remove unused oauth2/consts.go * Move authentication middleware to server package * Fix authentication comment * Update authenication documentation to mention AUTH_DURATION * Update /chronograf/v1/ping to simply return 204 * Fix Makefile run-dev target * Remove spurious ping route * Update auth docs to clarify authentication duration * Revert "Refactor GET 'me' into heartbeat at constant interval" This reverts commit 298a8c47e1431720d9bd97a9cb853744f04501a3. Conflicts: ui/src/index.js * Add auth test for JWT signing method * Add comments for why coverage isn't written for some areas of jwt code * Update auth docs to explicitly mention how to require re-auth for all users on server restart * Add Duration to Validation interface for Tokens * Make auth duration of zero yield a everlasting token * Revert "Revert "Refactor GET 'me' into heartbeat at constant interval"" This reverts commit b4773c15afe4fcd227ad88aa9d5686beb6b0a6cd. * Rename http status constants and add FORBIDDEN * Heartbeat only when logged in, notify user if heartbeat fails * Update changelog * Fix minor word semantics * Update oauth2 tests to be in the oauth2_test package * Add check at compile time that JWT implements Tokenizer * Rename CookieMux to AuthMux for consistency with earlier refactor * Fix logout middleware * Fix logout button not showing due to obsolete data shape expectations * Update changelog * Fix proptypes for logout button data shape in SideNav
2017-04-06 18:40:57 +00:00
tokenMiddleware := AuthorizedToken(opts.Auth, opts.Logger, router)
2016-10-25 15:20:06 +00:00
// Wrap the API with token validation middleware.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cleanPath := path.Clean(r.URL.Path) // compare ignoring path garbage, trailing slashes, etc.
if (strings.HasPrefix(cleanPath, rootPath) && len(cleanPath) > len(rootPath)) || cleanPath == logoutPath {
2016-10-25 15:20:06 +00:00
tokenMiddleware.ServeHTTP(w, r)
return
}
router.ServeHTTP(w, r)
}), routes
2016-10-25 15:20:06 +00:00
}
func encodeJSON(w http.ResponseWriter, status int, v interface{}, logger chronograf.Logger) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
2016-11-19 17:41:06 +00:00
unknownErrorWithMessage(w, err, logger)
2016-10-25 15:20:06 +00:00
}
}
// Error writes an JSON message
2016-11-19 17:41:06 +00:00
func Error(w http.ResponseWriter, code int, msg string, logger chronograf.Logger) {
e := ErrorMessage{
2016-10-25 15:20:06 +00:00
Code: code,
Message: msg,
}
b, err := json.Marshal(e)
if err != nil {
code = http.StatusInternalServerError
b = []byte(`{"code": 500, "message":"server_error"}`)
}
2016-11-19 17:41:06 +00:00
logger.
WithField("component", "server").
WithField("http_status ", code).
Error("Error message ", msg)
2016-10-25 15:20:06 +00:00
w.Header().Set("Content-Type", JSONType)
w.WriteHeader(code)
2016-12-20 20:59:56 +00:00
_, _ = w.Write(b)
2016-10-25 15:20:06 +00:00
}
2016-11-19 17:41:06 +00:00
func invalidData(w http.ResponseWriter, err error, logger chronograf.Logger) {
Error(w, http.StatusUnprocessableEntity, fmt.Sprintf("%v", err), logger)
2016-10-25 15:20:06 +00:00
}
2016-11-19 17:41:06 +00:00
func invalidJSON(w http.ResponseWriter, logger chronograf.Logger) {
Error(w, http.StatusBadRequest, "unparsable JSON", logger)
2016-10-25 15:20:06 +00:00
}
2016-11-19 17:41:06 +00:00
func unknownErrorWithMessage(w http.ResponseWriter, err error, logger chronograf.Logger) {
Error(w, http.StatusInternalServerError, fmt.Sprintf("unknown error: %v", err), logger)
2016-10-25 15:20:06 +00:00
}
func notFound(w http.ResponseWriter, id interface{}, logger chronograf.Logger) {
Error(w, http.StatusNotFound, fmt.Sprintf("ID %v not found", id), logger)
2016-10-25 15:20:06 +00:00
}
func paramID(key string, r *http.Request) (int, error) {
ctx := r.Context()
param := jhttprouter.ParamsFromContext(ctx).ByName(key)
2016-10-25 15:20:06 +00:00
id, err := strconv.Atoi(param)
if err != nil {
return -1, fmt.Errorf("error converting ID %s", param)
2016-10-25 15:20:06 +00:00
}
return id, nil
}
func paramStr(key string, r *http.Request) (string, error) {
ctx := r.Context()
param := jhttprouter.ParamsFromContext(ctx).ByName(key)
return param, nil
}