2025-04-01 10:38:02 +09:00

181 lines
4.9 KiB
Go

package contextmodel
import (
"errors"
"net/http"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
type ReqContext struct {
*web.Context
*user.SignedInUser
UserToken *usertoken.UserToken
IsSignedIn bool
IsRenderCall bool
AllowAnonymous bool
SkipDSCache bool
SkipQueryCache bool
Logger log.Logger
Error error
// RequestNonce is a cryptographic request identifier for use with Content Security Policy.
RequestNonce string
PublicDashboardAccessToken string
PerfmonTimer prometheus.Summary
LookupTokenErr error
// FIXME: Remove this temporary flag after the rollout of FlagUseSessionStorageForRedirection feature flag
// Tracking issue for cleaning up this flag: https://github.com/grafana/identity-access-team/issues/908
// UseSessionStorageRedirect is introduced to simplify the rollout of the new redirect logic
UseSessionStorageRedirect bool
}
// Handle handles and logs error by given status.
func (ctx *ReqContext) Handle(cfg *setting.Cfg, status int, title string, err error) {
data := struct {
Title string
AppTitle string
AppSubUrl string
ThemeType string
ErrorMsg error
}{title, "Grafana", cfg.AppSubURL, "dark", nil}
if err != nil {
ctx.Logger.Error(title, "error", err)
}
ctx.HTML(status, cfg.ErrTemplateName, data)
}
func (ctx *ReqContext) IsApiRequest() bool {
return strings.HasPrefix(ctx.Req.URL.Path, "/api")
}
func (ctx *ReqContext) IsPublicDashboardView() bool {
return ctx.PublicDashboardAccessToken != ""
}
func (ctx *ReqContext) JsonApiErr(status int, message string, err error) {
resp := make(map[string]interface{})
traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false)
if err != nil {
resp["traceID"] = traceID
if status == http.StatusInternalServerError {
ctx.Logger.Error(message, "error", err, "traceID", traceID)
} else {
ctx.Logger.Warn(message, "error", err, "traceID", traceID)
}
}
switch status {
case http.StatusNotFound:
resp["message"] = "Not Found"
case http.StatusInternalServerError:
resp["message"] = "Internal Server Error"
}
if message != "" {
resp["message"] = message
}
ctx.JSON(status, resp)
}
// WriteErr writes an error response based on errutil.Error.
// If provided error is not errutil.Error a 500 response is written.
func (ctx *ReqContext) WriteErr(err error) {
ctx.writeErrOrFallback(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), err)
}
// WriteErrOrFallback uses the information in an errutil.Error if available
// and otherwise falls back to the status and message provided as arguments.
func (ctx *ReqContext) WriteErrOrFallback(status int, message string, err error) {
ctx.writeErrOrFallback(status, message, err)
}
func (ctx *ReqContext) writeErrOrFallback(status int, message string, err error) {
data := make(map[string]interface{})
statusResponse := status
traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false)
if err != nil {
data["traceID"] = traceID
var logMessage string
logger := ctx.Logger.Warn
gfErr := errutil.Error{}
if errors.As(err, &gfErr) {
logger = gfErr.LogLevel.LogFunc(ctx.Logger)
publicErr := gfErr.Public()
// need to manually set these fields because we want to include the trace id
data["extra"] = publicErr.Extra
data["message"] = publicErr.Message
data["messageId"] = publicErr.MessageID
data["statusCode"] = publicErr.StatusCode
statusResponse = publicErr.StatusCode
} else {
if message != "" {
logMessage = message
} else {
logMessage = http.StatusText(status)
data["message"] = logMessage
}
if status == http.StatusInternalServerError {
logger = ctx.Logger.Error
}
}
if errutil.HasUnifiedLogging(ctx.Req.Context()) {
ctx.Error = err
} else {
logger(logMessage, "error", err, "remote_addr", ctx.RemoteAddr(), "traceID", traceID)
}
}
if _, ok := data["message"]; !ok && message != "" {
data["message"] = message
}
ctx.JSON(statusResponse, data)
}
func (ctx *ReqContext) HasUserRole(role org.RoleType) bool {
return ctx.SignedInUser.GetOrgRole().Includes(role)
}
func (ctx *ReqContext) HasHelpFlag(flag user.HelpFlags1) bool {
return ctx.HelpFlags1.HasFlag(flag)
}
func (ctx *ReqContext) TimeRequest(timer prometheus.Summary) {
ctx.PerfmonTimer = timer
}
// QueryBoolWithDefault extracts a value from the request query params and applies a bool default if not present.
func (ctx *ReqContext) QueryBoolWithDefault(field string, d bool) bool {
f := ctx.Query(field)
if f == "" {
return d
}
return ctx.QueryBool(field)
}