1package middleware
2
3import (
4	"errors"
5	"net/url"
6	"regexp"
7	"strconv"
8	"strings"
9
10	"github.com/grafana/grafana/pkg/middleware/cookies"
11	"github.com/grafana/grafana/pkg/models"
12	"github.com/grafana/grafana/pkg/services/sqlstore"
13	"github.com/grafana/grafana/pkg/setting"
14	"github.com/grafana/grafana/pkg/web"
15)
16
17type AuthOptions struct {
18	ReqGrafanaAdmin bool
19	ReqSignedIn     bool
20	ReqNoAnonynmous bool
21}
22
23func accessForbidden(c *models.ReqContext) {
24	if c.IsApiRequest() {
25		c.JsonApiErr(403, "Permission denied", nil)
26		return
27	}
28
29	c.Redirect(setting.AppSubUrl + "/")
30}
31
32func notAuthorized(c *models.ReqContext) {
33	if c.IsApiRequest() {
34		c.JsonApiErr(401, "Unauthorized", nil)
35		return
36	}
37
38	writeRedirectCookie(c)
39	c.Redirect(setting.AppSubUrl + "/login")
40}
41
42func tokenRevoked(c *models.ReqContext, err *models.TokenRevokedError) {
43	if c.IsApiRequest() {
44		c.JSON(401, map[string]interface{}{
45			"message": "Token revoked",
46			"error": map[string]interface{}{
47				"id":                    "ERR_TOKEN_REVOKED",
48				"maxConcurrentSessions": err.MaxConcurrentSessions,
49			},
50		})
51		return
52	}
53
54	writeRedirectCookie(c)
55	c.Redirect(setting.AppSubUrl + "/login")
56}
57
58func writeRedirectCookie(c *models.ReqContext) {
59	redirectTo := c.Req.RequestURI
60	if setting.AppSubUrl != "" && !strings.HasPrefix(redirectTo, setting.AppSubUrl) {
61		redirectTo = setting.AppSubUrl + c.Req.RequestURI
62	}
63
64	// remove any forceLogin=true params
65	redirectTo = removeForceLoginParams(redirectTo)
66
67	cookies.WriteCookie(c.Resp, "redirect_to", url.QueryEscape(redirectTo), 0, nil)
68}
69
70var forceLoginParamsRegexp = regexp.MustCompile(`&?forceLogin=true`)
71
72func removeForceLoginParams(str string) string {
73	return forceLoginParamsRegexp.ReplaceAllString(str, "")
74}
75
76func EnsureEditorOrViewerCanEdit(c *models.ReqContext) {
77	if !c.SignedInUser.HasRole(models.ROLE_EDITOR) && !setting.ViewersCanEdit {
78		accessForbidden(c)
79	}
80}
81
82func RoleAuth(roles ...models.RoleType) web.Handler {
83	return func(c *models.ReqContext) {
84		ok := false
85		for _, role := range roles {
86			if role == c.OrgRole {
87				ok = true
88				break
89			}
90		}
91		if !ok {
92			accessForbidden(c)
93		}
94	}
95}
96
97func Auth(options *AuthOptions) web.Handler {
98	return func(c *models.ReqContext) {
99		forceLogin := false
100		if c.AllowAnonymous {
101			forceLogin = shouldForceLogin(c)
102			if !forceLogin {
103				orgIDValue := c.Req.URL.Query().Get("orgId")
104				orgID, err := strconv.ParseInt(orgIDValue, 10, 64)
105				if err == nil && orgID > 0 && orgID != c.OrgId {
106					forceLogin = true
107				}
108			}
109		}
110
111		requireLogin := !c.AllowAnonymous || forceLogin || options.ReqNoAnonynmous
112
113		if !c.IsSignedIn && options.ReqSignedIn && requireLogin {
114			var revokedErr *models.TokenRevokedError
115			if errors.As(c.LookupTokenErr, &revokedErr) {
116				tokenRevoked(c, revokedErr)
117				return
118			}
119
120			notAuthorized(c)
121			return
122		}
123
124		if !c.IsGrafanaAdmin && options.ReqGrafanaAdmin {
125			accessForbidden(c)
126			return
127		}
128	}
129}
130
131// AdminOrEditorAndFeatureEnabled creates a middleware that allows
132// access if the signed in user is either an Org Admin or if they
133// are an Org Editor and the feature flag is enabled.
134// Intended for when feature flags open up access to APIs that
135// are otherwise only available to admins.
136func AdminOrEditorAndFeatureEnabled(enabled bool) web.Handler {
137	return func(c *models.ReqContext) {
138		if c.OrgRole == models.ROLE_ADMIN {
139			return
140		}
141
142		if c.OrgRole == models.ROLE_EDITOR && enabled {
143			return
144		}
145
146		accessForbidden(c)
147	}
148}
149
150// SnapshotPublicModeOrSignedIn creates a middleware that allows access
151// if snapshot public mode is enabled or if user is signed in.
152func SnapshotPublicModeOrSignedIn(cfg *setting.Cfg) web.Handler {
153	return func(c *models.ReqContext) {
154		if cfg.SnapshotPublicMode {
155			return
156		}
157
158		if !c.IsSignedIn {
159			notAuthorized(c)
160			return
161		}
162	}
163}
164
165func ReqNotSignedIn(c *models.ReqContext) {
166	if c.IsSignedIn {
167		c.Redirect(setting.AppSubUrl + "/")
168	}
169}
170
171// NoAuth creates a middleware that doesn't require any authentication.
172// If forceLogin param is set it will redirect the user to the login page.
173func NoAuth() web.Handler {
174	return func(c *models.ReqContext) {
175		if shouldForceLogin(c) {
176			notAuthorized(c)
177			return
178		}
179	}
180}
181
182// shouldForceLogin checks if user should be enforced to login.
183// Returns true if forceLogin parameter is set.
184func shouldForceLogin(c *models.ReqContext) bool {
185	forceLogin := false
186	forceLoginParam, err := strconv.ParseBool(c.Req.URL.Query().Get("forceLogin"))
187	if err == nil {
188		forceLogin = forceLoginParam
189	}
190
191	return forceLogin
192}
193
194func OrgAdminFolderAdminOrTeamAdmin(c *models.ReqContext) {
195	if c.OrgRole == models.ROLE_ADMIN {
196		return
197	}
198
199	hasAdminPermissionInFoldersQuery := models.HasAdminPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
200	if err := sqlstore.HasAdminPermissionInFolders(c.Req.Context(), &hasAdminPermissionInFoldersQuery); err != nil {
201		c.JsonApiErr(500, "Failed to check if user is a folder admin", err)
202	}
203
204	if hasAdminPermissionInFoldersQuery.Result {
205		return
206	}
207
208	isAdminOfTeamsQuery := models.IsAdminOfTeamsQuery{SignedInUser: c.SignedInUser}
209	if err := sqlstore.IsAdminOfTeams(c.Req.Context(), &isAdminOfTeamsQuery); err != nil {
210		c.JsonApiErr(500, "Failed to check if user is a team admin", err)
211	}
212
213	if isAdminOfTeamsQuery.Result {
214		return
215	}
216
217	accessForbidden(c)
218}
219