grafana/pkg/middleware/auth.go
Kristin Laemmert 2edfbb7767
sqlstore split: dashboard permissions (#49962)
* backend/sqlstore split: remove unused GetDashboardPermissionsForUser from sqlstore
* remove debugging line
* backend/sqlstore: move dashboard permission related functions to dashboard service
2022-06-01 14:16:26 -04:00

222 lines
5.3 KiB
Go

package middleware
import (
"errors"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/grafana/grafana/pkg/middleware/cookies"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
type AuthOptions struct {
ReqGrafanaAdmin bool
ReqSignedIn bool
ReqNoAnonynmous bool
}
func accessForbidden(c *models.ReqContext) {
if c.IsApiRequest() {
c.JsonApiErr(403, "Permission denied", nil)
return
}
c.Redirect(setting.AppSubUrl + "/")
}
func notAuthorized(c *models.ReqContext) {
if c.IsApiRequest() {
c.JsonApiErr(401, "Unauthorized", nil)
return
}
writeRedirectCookie(c)
c.Redirect(setting.AppSubUrl + "/login")
}
func tokenRevoked(c *models.ReqContext, err *models.TokenRevokedError) {
if c.IsApiRequest() {
c.JSON(401, map[string]interface{}{
"message": "Token revoked",
"error": map[string]interface{}{
"id": "ERR_TOKEN_REVOKED",
"maxConcurrentSessions": err.MaxConcurrentSessions,
},
})
return
}
writeRedirectCookie(c)
c.Redirect(setting.AppSubUrl + "/login")
}
func writeRedirectCookie(c *models.ReqContext) {
redirectTo := c.Req.RequestURI
if setting.AppSubUrl != "" && !strings.HasPrefix(redirectTo, setting.AppSubUrl) {
redirectTo = setting.AppSubUrl + c.Req.RequestURI
}
// remove any forceLogin=true params
redirectTo = removeForceLoginParams(redirectTo)
cookies.WriteCookie(c.Resp, "redirect_to", url.QueryEscape(redirectTo), 0, nil)
}
var forceLoginParamsRegexp = regexp.MustCompile(`&?forceLogin=true`)
func removeForceLoginParams(str string) string {
return forceLoginParamsRegexp.ReplaceAllString(str, "")
}
func EnsureEditorOrViewerCanEdit(c *models.ReqContext) {
if !c.SignedInUser.HasRole(models.ROLE_EDITOR) && !setting.ViewersCanEdit {
accessForbidden(c)
}
}
func RoleAuth(roles ...models.RoleType) web.Handler {
return func(c *models.ReqContext) {
ok := false
for _, role := range roles {
if role == c.OrgRole {
ok = true
break
}
}
if !ok {
accessForbidden(c)
}
}
}
func Auth(options *AuthOptions) web.Handler {
return func(c *models.ReqContext) {
forceLogin := false
if c.AllowAnonymous {
forceLogin = shouldForceLogin(c)
if !forceLogin {
orgIDValue := c.Req.URL.Query().Get("orgId")
orgID, err := strconv.ParseInt(orgIDValue, 10, 64)
if err == nil && orgID > 0 && orgID != c.OrgId {
forceLogin = true
}
}
}
requireLogin := !c.AllowAnonymous || forceLogin || options.ReqNoAnonynmous
if !c.IsSignedIn && options.ReqSignedIn && requireLogin {
var revokedErr *models.TokenRevokedError
if errors.As(c.LookupTokenErr, &revokedErr) {
tokenRevoked(c, revokedErr)
return
}
notAuthorized(c)
return
}
if !c.IsGrafanaAdmin && options.ReqGrafanaAdmin {
accessForbidden(c)
return
}
}
}
// AdminOrEditorAndFeatureEnabled creates a middleware that allows
// access if the signed in user is either an Org Admin or if they
// are an Org Editor and the feature flag is enabled.
// Intended for when feature flags open up access to APIs that
// are otherwise only available to admins.
func AdminOrEditorAndFeatureEnabled(enabled bool) web.Handler {
return func(c *models.ReqContext) {
if c.OrgRole == models.ROLE_ADMIN {
return
}
if c.OrgRole == models.ROLE_EDITOR && enabled {
return
}
accessForbidden(c)
}
}
// SnapshotPublicModeOrSignedIn creates a middleware that allows access
// if snapshot public mode is enabled or if user is signed in.
func SnapshotPublicModeOrSignedIn(cfg *setting.Cfg) web.Handler {
return func(c *models.ReqContext) {
if cfg.SnapshotPublicMode {
return
}
if !c.IsSignedIn {
notAuthorized(c)
return
}
}
}
func ReqNotSignedIn(c *models.ReqContext) {
if c.IsSignedIn {
c.Redirect(setting.AppSubUrl + "/")
}
}
// NoAuth creates a middleware that doesn't require any authentication.
// If forceLogin param is set it will redirect the user to the login page.
func NoAuth() web.Handler {
return func(c *models.ReqContext) {
if shouldForceLogin(c) {
notAuthorized(c)
return
}
}
}
// shouldForceLogin checks if user should be enforced to login.
// Returns true if forceLogin parameter is set.
func shouldForceLogin(c *models.ReqContext) bool {
forceLogin := false
forceLoginParam, err := strconv.ParseBool(c.Req.URL.Query().Get("forceLogin"))
if err == nil {
forceLogin = forceLoginParam
}
return forceLogin
}
func OrgAdminFolderAdminOrTeamAdmin(ss sqlstore.Store, ds dashboards.DashboardService) func(c *models.ReqContext) {
return func(c *models.ReqContext) {
if c.OrgRole == models.ROLE_ADMIN {
return
}
hasAdminPermissionInFoldersQuery := models.HasAdminPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
if err := ds.HasAdminPermissionInFolders(c.Req.Context(), &hasAdminPermissionInFoldersQuery); err != nil {
c.JsonApiErr(500, "Failed to check if user is a folder admin", err)
}
if hasAdminPermissionInFoldersQuery.Result {
return
}
isAdminOfTeamsQuery := models.IsAdminOfTeamsQuery{SignedInUser: c.SignedInUser}
if err := ss.IsAdminOfTeams(c.Req.Context(), &isAdminOfTeamsQuery); err != nil {
c.JsonApiErr(500, "Failed to check if user is a team admin", err)
}
if isAdminOfTeamsQuery.Result {
return
}
accessForbidden(c)
}
}