Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: Add method restrictions to routes, fix response codes #4243

Merged
merged 7 commits into from
Feb 11, 2025
78 changes: 49 additions & 29 deletions app/inithttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,43 +121,44 @@ func (app *App) initHTTP(ctx context.Context) error {
UserStore: app.UserStore,
})

mux.Handle("/api/graphql", app.graphql2.Handler())
mux.Handle("POST /api/graphql", app.graphql2.Handler())

mux.HandleFunc("/api/v2/config", app.ConfigStore.ServeConfig)
mux.HandleFunc("GET /api/v2/config", app.ConfigStore.ServeConfig)
mux.HandleFunc("PUT /api/v2/config", app.ConfigStore.ServeConfig)

mux.HandleFunc("/api/v2/identity/providers", app.AuthHandler.ServeProviders)
mux.HandleFunc("/api/v2/identity/logout", app.AuthHandler.ServeLogout)
mux.HandleFunc("GET /api/v2/identity/providers", app.AuthHandler.ServeProviders)
mux.HandleFunc("POST /api/v2/identity/logout", app.AuthHandler.ServeLogout)

basicAuth := app.AuthHandler.IdentityProviderHandler("basic")
mux.HandleFunc("/api/v2/identity/providers/basic", basicAuth)
mux.HandleFunc("POST /api/v2/identity/providers/basic", basicAuth)

githubAuth := app.AuthHandler.IdentityProviderHandler("github")
mux.HandleFunc("/api/v2/identity/providers/github", githubAuth)
mux.HandleFunc("/api/v2/identity/providers/github/callback", githubAuth)
mux.HandleFunc("POST /api/v2/identity/providers/github", githubAuth)
mux.HandleFunc("GET /api/v2/identity/providers/github/callback", githubAuth)

oidcAuth := app.AuthHandler.IdentityProviderHandler("oidc")
mux.HandleFunc("/api/v2/identity/providers/oidc", oidcAuth)
mux.HandleFunc("/api/v2/identity/providers/oidc/callback", oidcAuth)
mux.HandleFunc("POST /api/v2/identity/providers/oidc", oidcAuth)
mux.HandleFunc("GET /api/v2/identity/providers/oidc/callback", oidcAuth)

if expflag.ContextHas(ctx, expflag.UnivKeys) {
mux.HandleFunc("POST /api/v2/uik", app.UIKHandler.ServeHTTP)
}
mux.HandleFunc("/api/v2/mailgun/incoming", mailgun.IngressWebhooks(app.AlertStore, app.IntegrationKeyStore))
mux.HandleFunc("/api/v2/grafana/incoming", grafana.GrafanaToEventsAPI(app.AlertStore, app.IntegrationKeyStore))
mux.HandleFunc("/api/v2/site24x7/incoming", site24x7.Site24x7ToEventsAPI(app.AlertStore, app.IntegrationKeyStore))
mux.HandleFunc("/api/v2/prometheusalertmanager/incoming", prometheus.PrometheusAlertmanagerEventsAPI(app.AlertStore, app.IntegrationKeyStore))
mux.HandleFunc("POST /api/v2/mailgun/incoming", mailgun.IngressWebhooks(app.AlertStore, app.IntegrationKeyStore))
mux.HandleFunc("POST /api/v2/grafana/incoming", grafana.GrafanaToEventsAPI(app.AlertStore, app.IntegrationKeyStore))
mux.HandleFunc("POST /api/v2/site24x7/incoming", site24x7.Site24x7ToEventsAPI(app.AlertStore, app.IntegrationKeyStore))
mux.HandleFunc("POST /api/v2/prometheusalertmanager/incoming", prometheus.PrometheusAlertmanagerEventsAPI(app.AlertStore, app.IntegrationKeyStore))

mux.HandleFunc("/api/v2/generic/incoming", generic.ServeCreateAlert)
mux.HandleFunc("/api/v2/heartbeat/", generic.ServeHeartbeatCheck)
mux.HandleFunc("/api/v2/user-avatar/", generic.ServeUserAvatar)
mux.HandleFunc("/api/v2/calendar", app.CalSubStore.ServeICalData)
mux.HandleFunc("POST /api/v2/generic/incoming", generic.ServeCreateAlert)
mux.HandleFunc("POST /api/v2/heartbeat/{heartbeatID}", generic.ServeHeartbeatCheck)
mux.HandleFunc("GET /api/v2/user-avatar/{userID}", generic.ServeUserAvatar)
mux.HandleFunc("GET /api/v2/calendar", app.CalSubStore.ServeICalData)

mux.HandleFunc("/api/v2/twilio/message", app.twilioSMS.ServeMessage)
mux.HandleFunc("/api/v2/twilio/message/status", app.twilioSMS.ServeStatusCallback)
mux.HandleFunc("/api/v2/twilio/call", app.twilioVoice.ServeCall)
mux.HandleFunc("/api/v2/twilio/call/status", app.twilioVoice.ServeStatusCallback)
mux.HandleFunc("POST /api/v2/twilio/message", app.twilioSMS.ServeMessage)
mux.HandleFunc("POST /api/v2/twilio/message/status", app.twilioSMS.ServeStatusCallback)
mux.HandleFunc("POST /api/v2/twilio/call", app.twilioVoice.ServeCall)
mux.HandleFunc("POST /api/v2/twilio/call/status", app.twilioVoice.ServeStatusCallback)

mux.HandleFunc("/api/v2/slack/message-action", app.slackChan.ServeMessageAction)
mux.HandleFunc("POST /api/v2/slack/message-action", app.slackChan.ServeMessageAction)

middleware = append(middleware,
httpRewrite(app.cfg.HTTPPrefix, "/v1/graphql2", "/api/graphql"),
Expand Down Expand Up @@ -204,20 +205,31 @@ func (app *App) initHTTP(ctx context.Context) error {
},
)

mux.HandleFunc("/health", app.healthCheck)
mux.HandleFunc("/health/engine", app.engineStatus)
mux.HandleFunc("/health/engine/cycle", app.engineCycle)
mux.HandleFunc("GET /health", app.healthCheck)
mux.HandleFunc("GET /health/engine", app.engineStatus)
mux.HandleFunc("GET /health/engine/cycle", app.engineCycle)
mux.Handle("GET /health/", http.NotFoundHandler())

webH, err := web.NewHandler(app.cfg.UIDir, app.cfg.HTTPPrefix)
if err != nil {
return err
}
// non-API/404s go to UI handler
mux.Handle("/", webH)

mux.HandleFunc("/admin/riverui/", func(w http.ResponseWriter, r *http.Request) {
// This is necessary so that we can return 404 for invalid/unknown API routes, otherwise it will get caught by the UI handler and incorrectly return the index.html or a 405 (Method Not Allowed) error.
mux.Handle("GET /api/", http.NotFoundHandler())
mux.Handle("POST /api/", http.NotFoundHandler())
mux.Handle("GET /v1/", http.NotFoundHandler())
mux.Handle("POST /v1/", http.NotFoundHandler())

// non-API/404s go to UI handler and return index.html
mux.Handle("GET /", webH)

mux.Handle("GET /api/graphql/explore", webH)
mux.Handle("GET /api/graphql/explore/", webH)

mux.HandleFunc("GET /admin/riverui/", func(w http.ResponseWriter, r *http.Request) {
err := permission.LimitCheckAny(r.Context(), permission.Admin)
if permission.IsUnauthorized(err) && !strings.HasPrefix(r.URL.Path, "/admin/riverui/api") {
if permission.IsUnauthorized(err) {
// render login since we're on a UI route
webH.ServeHTTP(w, r)
return
Expand All @@ -228,6 +240,14 @@ func (app *App) initHTTP(ctx context.Context) error {

app.RiverUI.ServeHTTP(w, r)
})
mux.HandleFunc("POST /admin/riverui/api/", func(w http.ResponseWriter, r *http.Request) {
err := permission.LimitCheckAny(r.Context(), permission.Admin)
if errutil.HTTPError(r.Context(), w, err) {
return
}

app.RiverUI.ServeHTTP(w, r)
})

app.srv = &http.Server{
Handler: applyMiddleware(mux, middleware...),
Expand Down
4 changes: 4 additions & 0 deletions calsub/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func (s *Store) userNameMap(ctx context.Context, shifts []oncall.Shift) (map[str
func (s *Store) ServeICalData(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
src := permission.Source(ctx)
if src == nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
cfg := config.FromContext(ctx)
if src.Type != permission.SourceTypeCalendarSubscription || cfg.General.DisableCalendarSubscriptions {
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
Expand Down
15 changes: 2 additions & 13 deletions genericapi/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@ func NewHandler(c Config) *Handler {

// ServeUserAvatar will serve a redirect for a users avatar image.
func (h *Handler) ServeUserAvatar(w http.ResponseWriter, req *http.Request) {
parts := strings.Split(req.URL.Path, "/")
userID := parts[len(parts)-1]

ctx := req.Context()
u, err := h.c.UserStore.FindOne(ctx, userID)
u, err := h.c.UserStore.FindOne(ctx, req.PathValue("userID"))
if errors.Is(err, sql.ErrNoRows) {
http.NotFound(w, req)
return
Expand All @@ -50,16 +47,8 @@ func (h *Handler) ServeUserAvatar(w http.ResponseWriter, req *http.Request) {
// ServeHeartbeatCheck serves the heartbeat check-in endpoint.
func (h *Handler) ServeHeartbeatCheck(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if r.Method != "POST" {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}

parts := strings.Split(r.URL.Path, "/")
monitorID := parts[len(parts)-1]

err := retry.DoTemporaryError(func(_ int) error {
return h.c.HeartbeatStore.RecordHeartbeat(ctx, monitorID)
return h.c.HeartbeatStore.RecordHeartbeat(ctx, r.PathValue("heartbeatID"))
},
retry.Log(ctx),
retry.Limit(12),
Expand Down
Loading