| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | package accesscontrol | 
					
						
							| 
									
										
										
										
											2021-03-22 20:22:48 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-08-24 19:29:17 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-05-13 05:00:27 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-03-22 20:22:48 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2022-08-24 19:29:17 +08:00
										 |  |  | 	"text/template" | 
					
						
							| 
									
										
										
										
											2021-05-13 05:00:27 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-22 20:22:48 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/models" | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/user" | 
					
						
							| 
									
										
										
										
											2021-11-04 16:59:52 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2021-08-24 17:36:28 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util" | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/web" | 
					
						
							| 
									
										
										
										
											2021-03-22 20:22:48 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | func Middleware(ac AccessControl) func(web.Handler, Evaluator) web.Handler { | 
					
						
							|  |  |  | 	return func(fallback web.Handler, evaluator Evaluator) web.Handler { | 
					
						
							|  |  |  | 		if ac.IsDisabled() { | 
					
						
							|  |  |  | 			return fallback | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return func(c *models.ReqContext) { | 
					
						
							|  |  |  | 			authorize(c, ac, c.SignedInUser, evaluator) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | func authorize(c *models.ReqContext, ac AccessControl, user *user.SignedInUser, evaluator Evaluator) { | 
					
						
							| 
									
										
										
										
											2022-08-24 19:29:17 +08:00
										 |  |  | 	injected, err := evaluator.MutateScopes(c.Req.Context(), scopeInjector(scopeParams{ | 
					
						
							| 
									
										
										
										
											2022-08-11 19:28:55 +08:00
										 |  |  | 		OrgID:     c.OrgID, | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 		URLParams: web.Params(c.Req), | 
					
						
							|  |  |  | 	})) | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		c.JsonApiErr(http.StatusInternalServerError, "Internal server error", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hasAccess, err := ac.Evaluate(c.Req.Context(), user, injected) | 
					
						
							|  |  |  | 	if !hasAccess || err != nil { | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 		deny(c, injected, err) | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | func deny(c *models.ReqContext, evaluator Evaluator, err error) { | 
					
						
							| 
									
										
										
										
											2021-05-13 05:00:27 +08:00
										 |  |  | 	id := newID() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		c.Logger.Error("Error from access control system", "error", err, "accessErrorID", id) | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2021-08-24 17:36:28 +08:00
										 |  |  | 		c.Logger.Info( | 
					
						
							|  |  |  | 			"Access denied", | 
					
						
							| 
									
										
										
										
											2022-08-11 19:28:55 +08:00
										 |  |  | 			"userID", c.UserID, | 
					
						
							| 
									
										
										
										
											2021-08-24 17:36:28 +08:00
										 |  |  | 			"accessErrorID", id, | 
					
						
							| 
									
										
										
										
											2022-01-28 19:17:24 +08:00
										 |  |  | 			"permissions", evaluator.GoString(), | 
					
						
							| 
									
										
										
										
											2021-08-24 17:36:28 +08:00
										 |  |  | 		) | 
					
						
							| 
									
										
										
										
											2021-05-13 05:00:27 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-04 16:59:52 +08:00
										 |  |  | 	if !c.IsApiRequest() { | 
					
						
							|  |  |  | 		// TODO(emil): I'd like to show a message after this redirect, not sure how that can be done?
 | 
					
						
							|  |  |  | 		c.Redirect(setting.AppSubUrl + "/") | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 	message := "" | 
					
						
							|  |  |  | 	if evaluator != nil { | 
					
						
							|  |  |  | 		message = evaluator.String() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 05:00:27 +08:00
										 |  |  | 	// If the user triggers an error in the access control system, we
 | 
					
						
							|  |  |  | 	// don't want the user to be aware of that, so the user gets the
 | 
					
						
							|  |  |  | 	// same information from the system regardless of if it's an
 | 
					
						
							|  |  |  | 	// internal server error or access denied.
 | 
					
						
							|  |  |  | 	c.JSON(http.StatusForbidden, map[string]string{ | 
					
						
							|  |  |  | 		"title":         "Access denied", // the component needs to pick this up
 | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 		"message":       fmt.Sprintf("You'll need additional permissions to perform this action. Permissions needed: %s", message), | 
					
						
							| 
									
										
										
										
											2021-05-13 05:00:27 +08:00
										 |  |  | 		"accessErrorId": id, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func newID() string { | 
					
						
							|  |  |  | 	// Less ambiguity than alphanumerical.
 | 
					
						
							|  |  |  | 	numerical := []byte("0123456789") | 
					
						
							|  |  |  | 	id, err := util.GetRandomString(10, numerical...) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		// this should not happen, but if it does, a timestamp is as
 | 
					
						
							|  |  |  | 		// useful as anything.
 | 
					
						
							|  |  |  | 		id = fmt.Sprintf("%d", time.Now().UnixNano()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return "ACE" + id | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-10-06 19:15:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | type OrgIDGetter func(c *models.ReqContext) (int64, error) | 
					
						
							| 
									
										
										
										
											2022-10-04 18:17:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | type userCache interface { | 
					
						
							| 
									
										
										
										
											2022-08-16 22:08:59 +08:00
										 |  |  | 	GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:07:45 +08:00
										 |  |  | func AuthorizeInOrgMiddleware(ac AccessControl, service Service, cache userCache) func(web.Handler, OrgIDGetter, Evaluator) web.Handler { | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 	return func(fallback web.Handler, getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler { | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 		if ac.IsDisabled() { | 
					
						
							|  |  |  | 			return fallback | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return func(c *models.ReqContext) { | 
					
						
							|  |  |  | 			// using a copy of the user not to modify the signedInUser, yet perform the permission evaluation in another org
 | 
					
						
							|  |  |  | 			userCopy := *(c.SignedInUser) | 
					
						
							|  |  |  | 			orgID, err := getTargetOrg(c) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 				deny(c, nil, fmt.Errorf("failed to get target org: %w", err)) | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 			if orgID == GlobalOrgID { | 
					
						
							| 
									
										
										
										
											2022-08-11 19:28:55 +08:00
										 |  |  | 				userCopy.OrgID = orgID | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 				userCopy.OrgName = "" | 
					
						
							|  |  |  | 				userCopy.OrgRole = "" | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2022-08-16 22:08:59 +08:00
										 |  |  | 				query := user.GetSignedInUserQuery{UserID: c.UserID, OrgID: orgID} | 
					
						
							|  |  |  | 				queryResult, err := cache.GetSignedInUserWithCacheCtx(c.Req.Context(), &query) | 
					
						
							| 
									
										
										
										
											2022-08-11 19:28:55 +08:00
										 |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 					deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err)) | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 					return | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-08-16 22:08:59 +08:00
										 |  |  | 				userCopy.OrgID = queryResult.OrgID | 
					
						
							|  |  |  | 				userCopy.OrgName = queryResult.OrgName | 
					
						
							|  |  |  | 				userCopy.OrgRole = queryResult.OrgRole | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:07:45 +08:00
										 |  |  | 			if userCopy.Permissions[userCopy.OrgID] == nil { | 
					
						
							|  |  |  | 				permissions, err := service.GetUserPermissions(c.Req.Context(), &userCopy, Options{}) | 
					
						
							|  |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				userCopy.Permissions[userCopy.OrgID] = GroupScopesByAction(permissions) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 			authorize(c, ac, &userCopy, evaluator) | 
					
						
							| 
									
										
										
										
											2022-03-24 15:58:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 			// Set the sign-ed in user permissions in that org
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:07:45 +08:00
										 |  |  | 			c.SignedInUser.Permissions[userCopy.OrgID] = userCopy.Permissions[userCopy.OrgID] | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func UseOrgFromContextParams(c *models.ReqContext) (int64, error) { | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 	orgID, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 	// Special case of macaron handling invalid params
 | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 	if orgID == 0 || err != nil { | 
					
						
							| 
									
										
										
										
											2021-11-17 17:12:28 +08:00
										 |  |  | 		return 0, models.ErrOrgNotFound | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return orgID, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func UseGlobalOrg(c *models.ReqContext) (int64, error) { | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 	return GlobalOrgID, nil | 
					
						
							| 
									
										
										
										
											2021-12-20 16:52:24 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 19:29:17 +08:00
										 |  |  | func LoadPermissionsMiddleware(service Service) web.Handler { | 
					
						
							| 
									
										
										
										
											2021-12-14 23:05:59 +08:00
										 |  |  | 	return func(c *models.ReqContext) { | 
					
						
							| 
									
										
										
										
											2022-08-24 19:29:17 +08:00
										 |  |  | 		if service.IsDisabled() { | 
					
						
							| 
									
										
										
										
											2021-12-14 23:05:59 +08:00
										 |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 19:29:17 +08:00
										 |  |  | 		permissions, err := service.GetUserPermissions(c.Req.Context(), c.SignedInUser, | 
					
						
							| 
									
										
										
										
											2022-04-28 16:46:18 +08:00
										 |  |  | 			Options{ReloadCache: false}) | 
					
						
							| 
									
										
										
										
											2021-12-14 23:05:59 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			c.JsonApiErr(http.StatusForbidden, "", err) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if c.SignedInUser.Permissions == nil { | 
					
						
							|  |  |  | 			c.SignedInUser.Permissions = make(map[int64]map[string][]string) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-11 19:28:55 +08:00
										 |  |  | 		c.SignedInUser.Permissions[c.OrgID] = GroupScopesByAction(permissions) | 
					
						
							| 
									
										
										
										
											2021-12-14 23:05:59 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-08-24 19:29:17 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // scopeParams holds the parameters used to fill in scope templates
 | 
					
						
							|  |  |  | type scopeParams struct { | 
					
						
							|  |  |  | 	OrgID     int64 | 
					
						
							|  |  |  | 	URLParams map[string]string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // scopeInjector inject request params into the templated scopes. e.g. "settings:" + eval.Parameters(":id")
 | 
					
						
							|  |  |  | func scopeInjector(params scopeParams) ScopeAttributeMutator { | 
					
						
							|  |  |  | 	return func(_ context.Context, scope string) ([]string, error) { | 
					
						
							|  |  |  | 		tmpl, err := template.New("scope").Parse(scope) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		var buf bytes.Buffer | 
					
						
							|  |  |  | 		if err = tmpl.Execute(&buf, params); err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return []string{buf.String()}, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |