2022-04-28 03:46:18 -05:00
package accesscontrol
2021-03-22 07:22:48 -05:00
import (
2022-08-24 06:29:17 -05:00
"bytes"
2022-04-28 03:46:18 -05:00
"context"
2022-10-07 01:18:56 -05:00
"errors"
2021-05-12 16:00:27 -05:00
"fmt"
2024-03-13 11:05:03 -05:00
"io"
2021-03-22 07:22:48 -05:00
"net/http"
2022-10-07 01:18:56 -05:00
"net/url"
"regexp"
2022-01-14 10:55:57 -06:00
"strconv"
2022-10-07 01:18:56 -05:00
"strings"
2022-08-24 06:29:17 -05:00
"text/template"
2021-05-12 16:00:27 -05:00
"time"
2022-10-07 01:18:56 -05:00
"github.com/grafana/grafana/pkg/middleware/cookies"
2022-11-18 02:56:06 -06:00
"github.com/grafana/grafana/pkg/models/usertoken"
2023-08-18 05:42:18 -05:00
"github.com/grafana/grafana/pkg/services/auth/identity"
2023-03-23 08:39:04 -05:00
"github.com/grafana/grafana/pkg/services/authn"
2023-01-27 01:50:36 -06:00
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2023-01-04 09:20:26 -06:00
"github.com/grafana/grafana/pkg/services/org"
2021-11-04 03:59:52 -05:00
"github.com/grafana/grafana/pkg/setting"
2021-08-24 04:36:28 -05:00
"github.com/grafana/grafana/pkg/util"
2021-10-11 07:30:59 -05:00
"github.com/grafana/grafana/pkg/web"
2021-03-22 07:22:48 -05:00
)
2023-05-24 03:49:42 -05:00
func Middleware ( ac AccessControl ) func ( Evaluator ) web . Handler {
return func ( evaluator Evaluator ) web . Handler {
2023-01-27 01:50:36 -06:00
return func ( c * contextmodel . ReqContext ) {
2022-10-07 01:18:56 -05:00
if c . AllowAnonymous {
forceLogin , _ := strconv . ParseBool ( c . Req . URL . Query ( ) . Get ( "forceLogin" ) ) // ignoring error, assuming false for non-true values is ok.
orgID , err := strconv . ParseInt ( c . Req . URL . Query ( ) . Get ( "orgId" ) , 10 , 64 )
2023-08-18 05:42:18 -05:00
if err == nil && orgID > 0 && orgID != c . SignedInUser . GetOrgID ( ) {
2022-10-07 01:18:56 -05:00
forceLogin = true
}
if ! c . IsSignedIn && forceLogin {
2024-03-01 05:08:00 -06:00
unauthorized ( c )
2023-03-23 08:39:04 -05:00
return
2022-10-07 01:18:56 -05:00
}
}
2023-03-23 08:39:04 -05:00
if c . LookupTokenErr != nil {
var revokedErr * usertoken . TokenRevokedError
if errors . As ( c . LookupTokenErr , & revokedErr ) {
tokenRevoked ( c , revokedErr )
return
}
2024-03-01 05:08:00 -06:00
unauthorized ( c )
2022-10-07 01:18:56 -05:00
return
}
2022-04-28 03:46:18 -05:00
authorize ( c , ac , c . SignedInUser , evaluator )
}
}
}
2023-08-18 05:42:18 -05:00
func authorize ( c * contextmodel . ReqContext , ac AccessControl , user identity . Requester , evaluator Evaluator ) {
2022-08-24 06:29:17 -05:00
injected , err := evaluator . MutateScopes ( c . Req . Context ( ) , scopeInjector ( scopeParams {
2023-08-18 05:42:18 -05:00
OrgID : user . GetOrgID ( ) ,
2022-04-28 03:46:18 -05:00
URLParams : web . Params ( c . Req ) ,
} ) )
2021-11-17 03:12:28 -06: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 03:46:18 -05:00
deny ( c , injected , err )
2021-11-17 03:12:28 -06:00
return
}
}
2023-01-27 01:50:36 -06:00
func deny ( c * contextmodel . ReqContext , evaluator Evaluator , err error ) {
2021-05-12 16:00:27 -05:00
id := newID ( )
if err != nil {
c . Logger . Error ( "Error from access control system" , "error" , err , "accessErrorID" , id )
} else {
2023-08-18 05:42:18 -05:00
namespace , identifier := c . SignedInUser . GetNamespacedID ( )
2021-08-24 04:36:28 -05:00
c . Logger . Info (
"Access denied" ,
2023-08-18 05:42:18 -05:00
"namespace" , namespace ,
"userID" , identifier ,
2021-08-24 04:36:28 -05:00
"accessErrorID" , id ,
2022-01-28 05:17:24 -06:00
"permissions" , evaluator . GoString ( ) ,
2021-08-24 04:36:28 -05:00
)
2021-05-12 16:00:27 -05:00
}
2021-11-04 03:59:52 -05:00
if ! c . IsApiRequest ( ) {
// TODO(emil): I'd like to show a message after this redirect, not sure how that can be done?
2022-10-21 09:53:17 -05:00
writeRedirectCookie ( c )
2021-11-04 03:59:52 -05:00
c . Redirect ( setting . AppSubUrl + "/" )
return
}
2022-04-28 03:46:18 -05:00
message := ""
if evaluator != nil {
message = evaluator . String ( )
}
2021-05-12 16:00:27 -05: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 03:46:18 -05:00
"message" : fmt . Sprintf ( "You'll need additional permissions to perform this action. Permissions needed: %s" , message ) ,
2021-05-12 16:00:27 -05:00
"accessErrorId" : id ,
} )
}
2024-03-01 05:08:00 -06:00
func unauthorized ( c * contextmodel . ReqContext ) {
2022-10-07 01:18:56 -05:00
if c . IsApiRequest ( ) {
2023-03-23 08:39:04 -05:00
c . WriteErrOrFallback ( http . StatusUnauthorized , http . StatusText ( http . StatusUnauthorized ) , c . LookupTokenErr )
return
}
2022-10-07 01:18:56 -05:00
2023-03-23 08:39:04 -05:00
writeRedirectCookie ( c )
if errors . Is ( c . LookupTokenErr , authn . ErrTokenNeedsRotation ) {
c . Redirect ( setting . AppSubUrl + "/user/auth-tokens/rotate" )
return
}
c . Redirect ( setting . AppSubUrl + "/login" )
}
2022-10-07 01:18:56 -05:00
2023-03-23 08:39:04 -05:00
func tokenRevoked ( c * contextmodel . ReqContext , err * usertoken . TokenRevokedError ) {
if c . IsApiRequest ( ) {
2023-08-30 10:46:47 -05:00
c . JSON ( http . StatusUnauthorized , map [ string ] any {
2023-03-23 08:39:04 -05:00
"message" : "Token revoked" ,
2023-08-30 10:46:47 -05:00
"error" : map [ string ] any {
2023-03-23 08:39:04 -05:00
"id" : "ERR_TOKEN_REVOKED" ,
"maxConcurrentSessions" : err . MaxConcurrentSessions ,
} ,
} )
2022-10-07 01:18:56 -05:00
return
}
writeRedirectCookie ( c )
c . Redirect ( setting . AppSubUrl + "/login" )
}
2023-01-27 01:50:36 -06:00
func writeRedirectCookie ( c * contextmodel . ReqContext ) {
2022-10-07 01:18:56 -05:00
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 , "" )
}
2021-05-12 16:00:27 -05:00
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 06:15:09 -05:00
2023-01-27 01:50:36 -06:00
type OrgIDGetter func ( c * contextmodel . ReqContext ) ( int64 , error )
2022-10-04 05:17:55 -05:00
2024-04-10 05:42:13 -05:00
func AuthorizeInOrgMiddleware ( ac AccessControl , authnService authn . Service ) func ( OrgIDGetter , Evaluator ) web . Handler {
2023-05-24 03:49:42 -05:00
return func ( getTargetOrg OrgIDGetter , evaluator Evaluator ) web . Handler {
2023-01-27 01:50:36 -06:00
return func ( c * contextmodel . ReqContext ) {
2023-07-12 05:28:04 -05:00
targetOrgID , err := getTargetOrg ( c )
2021-11-17 03:12:28 -06:00
if err != nil {
2022-04-28 03:46:18 -05:00
deny ( c , nil , fmt . Errorf ( "failed to get target org: %w" , err ) )
2021-11-17 03:12:28 -06:00
return
}
2023-07-12 05:28:04 -05:00
2024-04-10 05:42:13 -05:00
var orgUser identity . Requester = c . SignedInUser
if targetOrgID != c . SignedInUser . GetOrgID ( ) {
orgUser , err = authnService . ResolveIdentity ( c . Req . Context ( ) , targetOrgID , c . SignedInUser . GetID ( ) )
if err != nil {
deny ( c , nil , fmt . Errorf ( "failed to authenticate user in target org: %w" , err ) )
return
}
2022-09-09 02:07:45 -05:00
}
2024-04-10 05:42:13 -05:00
authorize ( c , ac , orgUser , evaluator )
2022-03-24 02:58:10 -05:00
2023-07-12 05:28:04 -05:00
// guard against nil map
if c . SignedInUser . Permissions == nil {
c . SignedInUser . Permissions = make ( map [ int64 ] map [ string ] [ ] string )
}
2024-04-10 05:42:13 -05:00
c . SignedInUser . Permissions [ orgUser . GetOrgID ( ) ] = orgUser . GetPermissions ( )
2023-11-22 07:20:22 -06:00
}
}
2021-11-17 03:12:28 -06:00
}
2023-01-27 01:50:36 -06:00
func UseOrgFromContextParams ( c * contextmodel . ReqContext ) ( int64 , error ) {
2022-01-14 10:55:57 -06:00
orgID , err := strconv . ParseInt ( web . Params ( c . Req ) [ ":orgId" ] , 10 , 64 )
2021-11-17 03:12:28 -06:00
// Special case of macaron handling invalid params
2023-03-06 01:57:46 -06:00
if err != nil {
return 0 , org . ErrOrgNotFound . Errorf ( "failed to get organization from context: %w" , err )
}
if orgID == 0 {
return 0 , org . ErrOrgNotFound . Errorf ( "empty org ID" )
2021-11-17 03:12:28 -06:00
}
return orgID , nil
}
2023-01-27 01:50:36 -06:00
func UseGlobalOrg ( c * contextmodel . ReqContext ) ( int64 , error ) {
2022-04-28 03:46:18 -05:00
return GlobalOrgID , nil
2021-12-20 02:52:24 -06:00
}
2023-11-21 08:09:43 -06:00
// UseGlobalOrSingleOrg returns the global organization or the current organization in a single organization setup
func UseGlobalOrSingleOrg ( cfg * setting . Cfg ) OrgIDGetter {
return func ( c * contextmodel . ReqContext ) ( int64 , error ) {
if cfg . RBACSingleOrganization {
return c . GetOrgID ( ) , nil
}
return GlobalOrgID , nil
}
}
2024-03-13 11:05:03 -05:00
// UseOrgFromRequestData returns the organization from the request data.
// If no org is specified, then the org where user is logged in is returned.
func UseOrgFromRequestData ( c * contextmodel . ReqContext ) ( int64 , error ) {
query , err := getOrgQueryFromRequest ( c )
if err != nil {
// Special case of macaron handling invalid params
return NoOrgID , org . ErrOrgNotFound . Errorf ( "failed to get organization from context: %w" , err )
}
if query . OrgId == nil {
return c . SignedInUser . GetOrgID ( ) , nil
}
return * query . OrgId , nil
}
// UseGlobalOrgFromRequestData returns global org if `global` flag is set or the org where user is logged in.
2024-04-03 05:44:16 -05:00
// If RBACSingleOrganization is set, the org where user is logged in is returned - this is intended only for cloud workflows, where instances are limited to a single organization.
2024-04-10 05:42:13 -05:00
func UseGlobalOrgFromRequestData ( cfg * setting . Cfg ) OrgIDGetter {
2024-04-03 05:44:16 -05:00
return func ( c * contextmodel . ReqContext ) ( int64 , error ) {
query , err := getOrgQueryFromRequest ( c )
if err != nil {
// Special case of macaron handling invalid params
return NoOrgID , org . ErrOrgNotFound . Errorf ( "failed to get organization from context: %w" , err )
}
2024-03-13 11:05:03 -05:00
2024-04-03 05:44:16 -05:00
// We only check permissions in the global organization if we are not running a SingleOrganization setup
// That allows Organization Admins to modify global roles and make global assignments.
if query . Global && ! cfg . RBACSingleOrganization {
return GlobalOrgID , nil
}
2024-03-13 11:05:03 -05:00
2024-04-03 05:44:16 -05:00
return c . SignedInUser . GetOrgID ( ) , nil
}
2024-03-13 11:05:03 -05:00
}
2024-03-14 10:17:24 -05:00
// UseGlobalOrgFromRequestParams returns global org if `global` flag is set or the org where user is logged in.
func UseGlobalOrgFromRequestParams ( c * contextmodel . ReqContext ) ( int64 , error ) {
if c . QueryBool ( "global" ) {
return GlobalOrgID , nil
}
return c . SignedInUser . GetOrgID ( ) , nil
}
2024-03-13 11:05:03 -05:00
func getOrgQueryFromRequest ( c * contextmodel . ReqContext ) ( * QueryWithOrg , error ) {
query := & QueryWithOrg { }
req , err := CloneRequest ( c . Req )
if err != nil {
return nil , err
}
if err := web . Bind ( req , query ) ; err != nil {
// Special case of macaron handling invalid params
return nil , err
}
return query , nil
}
// CloneRequest creates request copy including request body
func CloneRequest ( req * http . Request ) ( * http . Request , error ) {
// Get copy of body to prevent error when reading closed body in request handler
bodyCopy , err := CopyRequestBody ( req )
if err != nil {
return nil , err
}
reqCopy := req . Clone ( req . Context ( ) )
reqCopy . Body = bodyCopy
return reqCopy , nil
}
// CopyRequestBody returns copy of request body and keeps the original one to prevent error when reading closed body
func CopyRequestBody ( req * http . Request ) ( io . ReadCloser , error ) {
if req . Body == nil {
return nil , nil
}
body := req . Body
var buf bytes . Buffer
if _ , err := buf . ReadFrom ( body ) ; err != nil {
return nil , err
}
if err := body . Close ( ) ; err != nil {
return nil , err
}
req . Body = io . NopCloser ( & buf )
return io . NopCloser ( bytes . NewReader ( buf . Bytes ( ) ) ) , nil
}
2022-08-24 06:29:17 -05: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
}
}