mirror of
https://github.com/grafana/grafana.git
synced 2024-12-02 05:29:42 -06:00
605d056136
* * Teams: Appropriately apply user id filter in /api/teams/:id and /api/teams/search * Teams: Ensure that users searching for teams are only able see teams they have access to * Teams: Require teamGuardian admin privileges to list team members * Teams: Prevent org viewers from administering teams * Teams: Add org_id condition to team count query * Teams: clarify permission requirements in teams api docs * Teams: expand scenarios for team search tests * Teams: mock teamGuardian in tests Co-authored-by: Dan Cech <dcech@grafana.com> * remove duplicate WHERE statement * Fix for CVE-2022-21702 (cherry picked from commit 202d7c190082c094bc1dc13f7fe9464746c37f9e) * Lint and test fixes (cherry picked from commit 3e6b67d5504abf4a1d7b8d621f04d062c048e981) * check content type properly (cherry picked from commit 70b4458892bf2f776302720c10d24c9ff34edd98) * basic csrf origin check (cherry picked from commit 3adaa5ff39832364f6390881fb5b42ad47df92e1) * compare origin to host (cherry picked from commit 5443892699e8ed42836bb2b9a44744ff3e970f42) * simplify url parsing (cherry picked from commit b2ffbc9513fed75468628370a48b929d30af2b1d) * check csrf for GET requests, only compare origin (cherry picked from commit 8b81dc12d8f8a1f07852809c5b4d44f0f0b1d709) * parse content type properly (cherry picked from commit 16f76f4902e6f2188bea9606c68b551af186bdc0) * mentioned get in the comment (cherry picked from commit a7e61811ef8ae558ce721e2e3fed04ce7a5a5345) * add content-type: application/json to test HTTP requests * fix pluginproxy test * Fix linter when comparing errors Co-authored-by: Kevin Minehart <kmineh0151@gmail.com> Co-authored-by: Dan Cech <dcech@grafana.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com> Co-authored-by: Vardan Torosyan <vardants@gmail.com>
219 lines
5.2 KiB
Go
219 lines
5.2 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/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(c *models.ReqContext) {
|
|
if c.OrgRole == models.ROLE_ADMIN {
|
|
return
|
|
}
|
|
|
|
hasAdminPermissionInFoldersQuery := models.HasAdminPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
|
|
if err := sqlstore.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 := sqlstore.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)
|
|
}
|