2014-10-06 03:13:01 +08:00
|
|
|
package middleware
|
|
|
|
|
|
|
|
import (
|
2021-03-17 00:44:02 +08:00
|
|
|
"errors"
|
2023-01-26 17:50:44 +08:00
|
|
|
"net/http"
|
2015-01-27 19:05:23 +08:00
|
|
|
"net/url"
|
2023-12-18 23:12:46 +08:00
|
|
|
"path/filepath"
|
2020-11-21 02:30:37 +08:00
|
|
|
"regexp"
|
2020-07-06 20:59:00 +08:00
|
|
|
"strconv"
|
2015-01-14 16:33:34 +08:00
|
|
|
"strings"
|
2014-11-20 22:19:44 +08:00
|
|
|
|
2023-12-18 23:12:46 +08:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2020-12-11 18:44:44 +08:00
|
|
|
"github.com/grafana/grafana/pkg/middleware/cookies"
|
2023-10-12 16:46:43 +08:00
|
|
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
2022-11-18 16:56:06 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/auth"
|
2023-03-23 21:39:04 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/authn"
|
2023-01-27 15:50:36 +08:00
|
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
2024-11-26 20:13:17 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
2022-08-10 17:56:48 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/org"
|
2023-03-27 17:15:37 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
2023-12-18 23:12:46 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
2015-02-05 17:37:13 +08:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2021-10-11 20:30:59 +08:00
|
|
|
"github.com/grafana/grafana/pkg/web"
|
2014-10-06 03:13:01 +08:00
|
|
|
)
|
|
|
|
|
2015-01-15 19:16:54 +08:00
|
|
|
type AuthOptions struct {
|
2015-01-16 21:32:18 +08:00
|
|
|
ReqGrafanaAdmin bool
|
2021-02-28 01:04:28 +08:00
|
|
|
ReqNoAnonynmous bool
|
2023-07-06 21:40:06 +08:00
|
|
|
ReqSignedIn bool
|
2015-01-15 19:16:54 +08:00
|
|
|
}
|
|
|
|
|
2023-01-27 15:50:36 +08:00
|
|
|
func accessForbidden(c *contextmodel.ReqContext) {
|
2015-01-14 21:25:12 +08:00
|
|
|
if c.IsApiRequest() {
|
2015-09-08 16:46:31 +08:00
|
|
|
c.JsonApiErr(403, "Permission denied", nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-06 01:17:47 +08:00
|
|
|
c.Redirect(setting.AppSubUrl + "/")
|
2015-09-08 16:46:31 +08:00
|
|
|
}
|
|
|
|
|
2023-01-27 15:50:36 +08:00
|
|
|
func notAuthorized(c *contextmodel.ReqContext) {
|
2015-09-08 16:46:31 +08:00
|
|
|
if c.IsApiRequest() {
|
2023-01-26 17:50:44 +08:00
|
|
|
c.WriteErrOrFallback(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), c.LookupTokenErr)
|
2015-01-27 19:05:23 +08:00
|
|
|
return
|
2015-01-14 21:25:12 +08:00
|
|
|
}
|
|
|
|
|
2024-09-25 00:38:09 +08:00
|
|
|
if !c.UseSessionStorageRedirect {
|
|
|
|
writeRedirectCookie(c)
|
|
|
|
}
|
2023-03-23 21:39:04 +08:00
|
|
|
|
|
|
|
if errors.Is(c.LookupTokenErr, authn.ErrTokenNeedsRotation) {
|
2024-09-25 00:38:09 +08:00
|
|
|
if !c.UseSessionStorageRedirect {
|
|
|
|
c.Redirect(setting.AppSubUrl + "/user/auth-tokens/rotate")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Redirect(setting.AppSubUrl + "/user/auth-tokens/rotate" + getRedirectToQueryParam(c))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !c.UseSessionStorageRedirect {
|
|
|
|
c.Redirect(setting.AppSubUrl + "/login")
|
2023-03-23 21:39:04 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-25 00:38:09 +08:00
|
|
|
c.Redirect(setting.AppSubUrl + "/login" + getRedirectToQueryParam(c))
|
2021-03-17 00:44:02 +08:00
|
|
|
}
|
|
|
|
|
2023-01-27 15:50:36 +08:00
|
|
|
func tokenRevoked(c *contextmodel.ReqContext, err *auth.TokenRevokedError) {
|
2021-03-17 00:44:02 +08:00
|
|
|
if c.IsApiRequest() {
|
2024-02-28 00:39:51 +08:00
|
|
|
c.JSON(http.StatusUnauthorized, map[string]any{
|
2021-03-17 00:44:02 +08:00
|
|
|
"message": "Token revoked",
|
2023-08-30 23:46:47 +08:00
|
|
|
"error": map[string]any{
|
2021-03-17 00:44:02 +08:00
|
|
|
"id": "ERR_TOKEN_REVOKED",
|
|
|
|
"maxConcurrentSessions": err.MaxConcurrentSessions,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-09-25 00:38:09 +08:00
|
|
|
if !c.UseSessionStorageRedirect {
|
|
|
|
writeRedirectCookie(c)
|
|
|
|
c.Redirect(setting.AppSubUrl + "/login")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Redirect(setting.AppSubUrl + "/login" + getRedirectToQueryParam(c))
|
2021-03-17 00:44:02 +08:00
|
|
|
}
|
|
|
|
|
2023-01-27 15:50:36 +08:00
|
|
|
func writeRedirectCookie(c *contextmodel.ReqContext) {
|
2020-03-11 17:04:48 +08:00
|
|
|
redirectTo := c.Req.RequestURI
|
|
|
|
if setting.AppSubUrl != "" && !strings.HasPrefix(redirectTo, setting.AppSubUrl) {
|
|
|
|
redirectTo = setting.AppSubUrl + c.Req.RequestURI
|
|
|
|
}
|
2018-02-15 17:56:29 +08:00
|
|
|
|
2022-10-21 22:53:17 +08:00
|
|
|
if redirectTo == "/" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-11-21 02:30:37 +08:00
|
|
|
// remove any forceLogin=true params
|
2024-10-07 20:59:00 +08:00
|
|
|
redirectTo = RemoveForceLoginParams(redirectTo)
|
2020-12-11 18:44:44 +08:00
|
|
|
cookies.WriteCookie(c.Resp, "redirect_to", url.QueryEscape(redirectTo), 0, nil)
|
2014-10-06 03:13:01 +08:00
|
|
|
}
|
|
|
|
|
2024-09-25 00:38:09 +08:00
|
|
|
func getRedirectToQueryParam(c *contextmodel.ReqContext) string {
|
|
|
|
redirectTo := c.Req.RequestURI
|
|
|
|
if setting.AppSubUrl != "" && strings.HasPrefix(redirectTo, setting.AppSubUrl) {
|
|
|
|
redirectTo = strings.TrimPrefix(redirectTo, setting.AppSubUrl)
|
|
|
|
}
|
|
|
|
|
|
|
|
if redirectTo == "/" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove any forceLogin=true params
|
2024-10-07 20:59:00 +08:00
|
|
|
redirectTo = RemoveForceLoginParams(redirectTo)
|
2024-09-25 00:38:09 +08:00
|
|
|
return "?redirectTo=" + url.QueryEscape(redirectTo)
|
|
|
|
}
|
|
|
|
|
2020-11-21 02:30:37 +08:00
|
|
|
var forceLoginParamsRegexp = regexp.MustCompile(`&?forceLogin=true`)
|
|
|
|
|
2024-10-07 20:59:00 +08:00
|
|
|
func RemoveForceLoginParams(str string) string {
|
2020-11-21 02:30:37 +08:00
|
|
|
return forceLoginParamsRegexp.ReplaceAllString(str, "")
|
|
|
|
}
|
|
|
|
|
2023-10-12 16:46:43 +08:00
|
|
|
func CanAdminPlugins(cfg *setting.Cfg, accessControl ac.AccessControl) func(c *contextmodel.ReqContext) {
|
2023-01-27 15:50:36 +08:00
|
|
|
return func(c *contextmodel.ReqContext) {
|
2023-10-12 16:46:43 +08:00
|
|
|
hasAccess := ac.HasAccess(accessControl, c)
|
|
|
|
if !pluginaccesscontrol.ReqCanAdminPlugins(cfg)(c) && !hasAccess(pluginaccesscontrol.AdminAccessEvaluator) {
|
2022-11-30 16:41:28 +08:00
|
|
|
accessForbidden(c)
|
|
|
|
return
|
|
|
|
}
|
2024-09-20 21:35:58 +08:00
|
|
|
if c.AllowAnonymous && !c.IsSignedIn && shouldForceLogin(c) {
|
|
|
|
notAuthorized(c)
|
|
|
|
return
|
|
|
|
}
|
2022-11-30 16:41:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-25 20:44:40 +08:00
|
|
|
func RoleAppPluginAuth(accessControl ac.AccessControl, ps pluginstore.Store, logger log.Logger) func(c *contextmodel.ReqContext) {
|
2023-12-18 23:12:46 +08:00
|
|
|
return func(c *contextmodel.ReqContext) {
|
|
|
|
pluginID := web.Params(c.Req)[":id"]
|
|
|
|
p, exists := ps.Plugin(c.Req.Context(), pluginID)
|
|
|
|
if !exists {
|
|
|
|
// The frontend will handle app not found appropriately
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
permitted := true
|
|
|
|
path := normalizeIncludePath(c.Req.URL.Path)
|
|
|
|
hasAccess := ac.HasAccess(accessControl, c)
|
|
|
|
for _, i := range p.Includes {
|
|
|
|
if i.Type != "page" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := url.Parse(i.Path)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error("failed to parse include path", "pluginId", pluginID, "include", i.Name, "err", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if normalizeIncludePath(u.Path) == path {
|
2025-02-25 20:44:40 +08:00
|
|
|
if i.RequiresRBACAction() && !hasAccess(pluginaccesscontrol.GetPluginRouteEvaluator(pluginID, i.Action)) {
|
2023-12-18 23:12:46 +08:00
|
|
|
logger.Debug("Plugin include is covered by RBAC, user doesn't have access", "plugin", pluginID, "include", i.Name)
|
|
|
|
permitted = false
|
|
|
|
break
|
2025-02-25 20:44:40 +08:00
|
|
|
} else if !i.RequiresRBACAction() && !c.HasUserRole(i.Role) {
|
2023-12-18 23:12:46 +08:00
|
|
|
permitted = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !permitted {
|
|
|
|
accessForbidden(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func normalizeIncludePath(p string) string {
|
|
|
|
return strings.TrimPrefix(filepath.Clean(p), "/")
|
|
|
|
}
|
|
|
|
|
2022-08-10 17:56:48 +08:00
|
|
|
func RoleAuth(roles ...org.RoleType) web.Handler {
|
2023-01-27 15:50:36 +08:00
|
|
|
return func(c *contextmodel.ReqContext) {
|
2015-01-16 22:28:44 +08:00
|
|
|
ok := false
|
|
|
|
for _, role := range roles {
|
2015-02-24 03:07:49 +08:00
|
|
|
if role == c.OrgRole {
|
2015-01-16 22:28:44 +08:00
|
|
|
ok = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !ok {
|
2015-09-08 16:46:31 +08:00
|
|
|
accessForbidden(c)
|
2015-01-16 22:28:44 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-11 20:30:59 +08:00
|
|
|
func Auth(options *AuthOptions) web.Handler {
|
2023-01-27 15:50:36 +08:00
|
|
|
return func(c *contextmodel.ReqContext) {
|
2020-07-06 20:59:00 +08:00
|
|
|
forceLogin := false
|
|
|
|
if c.AllowAnonymous {
|
2021-03-16 23:46:34 +08:00
|
|
|
forceLogin = shouldForceLogin(c)
|
2020-10-23 22:34:35 +08:00
|
|
|
if !forceLogin {
|
|
|
|
orgIDValue := c.Req.URL.Query().Get("orgId")
|
|
|
|
orgID, err := strconv.ParseInt(orgIDValue, 10, 64)
|
2025-04-10 20:42:23 +08:00
|
|
|
if err == nil && orgID > 0 && orgID != c.GetOrgID() {
|
2020-10-23 22:34:35 +08:00
|
|
|
forceLogin = true
|
|
|
|
}
|
|
|
|
}
|
2020-07-06 20:59:00 +08:00
|
|
|
}
|
2021-02-28 01:04:28 +08:00
|
|
|
|
|
|
|
requireLogin := !c.AllowAnonymous || forceLogin || options.ReqNoAnonynmous
|
|
|
|
|
2020-06-16 21:33:44 +08:00
|
|
|
if !c.IsSignedIn && options.ReqSignedIn && requireLogin {
|
2022-11-18 16:56:06 +08:00
|
|
|
var revokedErr *auth.TokenRevokedError
|
2021-09-13 21:41:03 +08:00
|
|
|
if errors.As(c.LookupTokenErr, &revokedErr) {
|
2021-03-17 00:44:02 +08:00
|
|
|
tokenRevoked(c, revokedErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-09-08 16:46:31 +08:00
|
|
|
notAuthorized(c)
|
2015-01-15 19:16:54 +08:00
|
|
|
return
|
|
|
|
}
|
2015-01-14 16:33:34 +08:00
|
|
|
|
2015-09-08 16:46:31 +08:00
|
|
|
if !c.IsGrafanaAdmin && options.ReqGrafanaAdmin {
|
|
|
|
accessForbidden(c)
|
2015-01-15 19:16:54 +08:00
|
|
|
return
|
2015-01-14 16:33:34 +08:00
|
|
|
}
|
2014-10-06 03:13:01 +08:00
|
|
|
}
|
|
|
|
}
|
2019-03-06 17:27:38 +08:00
|
|
|
|
2024-11-26 20:13:17 +08:00
|
|
|
// SnapshotPublicModeOrCreate creates a middleware that allows access
|
|
|
|
// if snapshot public mode is enabled or if user has creation permission.
|
|
|
|
func SnapshotPublicModeOrCreate(cfg *setting.Cfg, ac2 ac.AccessControl) web.Handler {
|
2023-01-27 15:50:36 +08:00
|
|
|
return func(c *contextmodel.ReqContext) {
|
2020-12-11 18:44:44 +08:00
|
|
|
if cfg.SnapshotPublicMode {
|
2019-09-02 21:15:46 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-02-17 16:51:50 +08:00
|
|
|
if !c.IsSignedIn {
|
|
|
|
notAuthorized(c)
|
|
|
|
return
|
2019-09-02 21:15:46 +08:00
|
|
|
}
|
2024-11-26 20:13:17 +08:00
|
|
|
|
|
|
|
ac.Middleware(ac2)(ac.EvalPermission(dashboards.ActionSnapshotsCreate))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SnapshotPublicModeOrDelete creates a middleware that allows access
|
|
|
|
// if snapshot public mode is enabled or if user has delete permission.
|
|
|
|
func SnapshotPublicModeOrDelete(cfg *setting.Cfg, ac2 ac.AccessControl) web.Handler {
|
|
|
|
return func(c *contextmodel.ReqContext) {
|
|
|
|
if cfg.SnapshotPublicMode {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !c.IsSignedIn {
|
|
|
|
notAuthorized(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ac.Middleware(ac2)(ac.EvalPermission(dashboards.ActionSnapshotsDelete))
|
2019-09-02 21:15:46 +08:00
|
|
|
}
|
|
|
|
}
|
2021-03-16 23:46:34 +08:00
|
|
|
|
2023-01-27 15:50:36 +08:00
|
|
|
func ReqNotSignedIn(c *contextmodel.ReqContext) {
|
2021-06-15 00:02:05 +08:00
|
|
|
if c.IsSignedIn {
|
|
|
|
c.Redirect(setting.AppSubUrl + "/")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-16 23:46:34 +08:00
|
|
|
// NoAuth creates a middleware that doesn't require any authentication.
|
|
|
|
// If forceLogin param is set it will redirect the user to the login page.
|
2021-10-11 20:30:59 +08:00
|
|
|
func NoAuth() web.Handler {
|
2023-01-27 15:50:36 +08:00
|
|
|
return func(c *contextmodel.ReqContext) {
|
2021-03-16 23:46:34 +08:00
|
|
|
if shouldForceLogin(c) {
|
|
|
|
notAuthorized(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// shouldForceLogin checks if user should be enforced to login.
|
|
|
|
// Returns true if forceLogin parameter is set.
|
2023-01-27 15:50:36 +08:00
|
|
|
func shouldForceLogin(c *contextmodel.ReqContext) bool {
|
2021-03-16 23:46:34 +08:00
|
|
|
forceLogin := false
|
|
|
|
forceLoginParam, err := strconv.ParseBool(c.Req.URL.Query().Get("forceLogin"))
|
|
|
|
if err == nil {
|
|
|
|
forceLogin = forceLoginParam
|
|
|
|
}
|
|
|
|
|
|
|
|
return forceLogin
|
|
|
|
}
|