2021-03-22 07:22:48 -05:00
package middleware
import (
2021-05-12 16:00:27 -05:00
"fmt"
2021-03-22 07:22:48 -05:00
"net/http"
2021-05-12 16:00:27 -05:00
"time"
2021-03-22 07:22:48 -05:00
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
2021-11-17 03:12:28 -06:00
"github.com/grafana/grafana/pkg/services/sqlstore"
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
)
2021-11-17 03:12:28 -06:00
func authorize ( c * models . ReqContext , ac accesscontrol . AccessControl , user * models . SignedInUser , evaluator accesscontrol . Evaluator ) {
injected , err := evaluator . Inject ( buildScopeParams ( c ) )
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 {
Deny ( c , injected , err )
return
}
}
2021-10-11 07:30:59 -05:00
func Middleware ( ac accesscontrol . AccessControl ) func ( web . Handler , accesscontrol . Evaluator ) web . Handler {
return func ( fallback web . Handler , evaluator accesscontrol . Evaluator ) web . Handler {
2021-04-06 08:49:09 -05:00
if ac . IsDisabled ( ) {
return fallback
}
2021-03-22 07:22:48 -05:00
return func ( c * models . ReqContext ) {
2021-11-17 03:12:28 -06:00
authorize ( c , ac , c . SignedInUser , evaluator )
2021-03-22 07:22:48 -05:00
}
}
}
2021-05-12 16:00:27 -05:00
2021-08-24 04:36:28 -05:00
func Deny ( c * models . ReqContext , evaluator accesscontrol . 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 {
2021-08-24 04:36:28 -05:00
c . Logger . Info (
"Access denied" ,
2021-05-12 16:00:27 -05:00
"userID" , c . UserId ,
2021-08-24 04:36:28 -05:00
"accessErrorID" , id ,
"permissions" , evaluator . String ( ) ,
)
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?
c . Redirect ( setting . AppSubUrl + "/" )
return
}
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-01-04 02:28:55 -06:00
"message" : fmt . Sprintf ( "You'll need additional permissions to perform this action. Refer your administrator to a Grafana log with the reference %s to identify which permissions to add." , id ) ,
2021-05-12 16:00:27 -05: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 06:15:09 -05:00
func buildScopeParams ( c * models . ReqContext ) accesscontrol . ScopeParams {
return accesscontrol . ScopeParams {
OrgID : c . OrgId ,
2021-10-11 07:30:59 -05:00
URLParams : web . Params ( c . Req ) ,
2021-10-06 06:15:09 -05:00
}
}
2021-11-17 03:12:28 -06:00
type OrgIDGetter func ( c * models . ReqContext ) ( int64 , error )
func AuthorizeInOrgMiddleware ( ac accesscontrol . AccessControl , db * sqlstore . SQLStore ) func ( web . Handler , OrgIDGetter , accesscontrol . Evaluator ) web . Handler {
return func ( fallback web . Handler , getTargetOrg OrgIDGetter , evaluator accesscontrol . Evaluator ) web . Handler {
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 {
Deny ( c , nil , fmt . Errorf ( "failed to get target org: %w" , err ) )
return
}
if orgID == accesscontrol . GlobalOrgID {
userCopy . OrgId = orgID
userCopy . OrgName = ""
userCopy . OrgRole = ""
} else {
query := models . GetSignedInUserQuery { UserId : c . UserId , OrgId : orgID }
if err := db . GetSignedInUserWithCacheCtx ( c . Req . Context ( ) , & query ) ; err != nil {
Deny ( c , nil , fmt . Errorf ( "failed to authenticate user in target org: %w" , err ) )
return
}
userCopy . OrgId = query . Result . OrgId
userCopy . OrgName = query . Result . OrgName
userCopy . OrgRole = query . Result . OrgRole
}
authorize ( c , ac , & userCopy , evaluator )
}
}
}
func UseOrgFromContextParams ( c * models . ReqContext ) ( int64 , error ) {
orgID := c . ParamsInt64 ( ":orgId" )
// Special case of macaron handling invalid params
if orgID == 0 {
return 0 , models . ErrOrgNotFound
}
return orgID , nil
}
func UseGlobalOrg ( c * models . ReqContext ) ( int64 , error ) {
return accesscontrol . GlobalOrgID , nil
}
2021-12-14 09:05:59 -06:00
2021-12-20 02:52:24 -06:00
// Disable returns http 404 if shouldDisable is set to true
func Disable ( shouldDisable bool ) web . Handler {
return func ( c * models . ReqContext ) {
if shouldDisable {
c . Resp . WriteHeader ( http . StatusNotFound )
return
}
}
}
2021-12-14 09:05:59 -06:00
func LoadPermissionsMiddleware ( ac accesscontrol . AccessControl ) web . Handler {
return func ( c * models . ReqContext ) {
if ac . IsDisabled ( ) {
return
}
permissions , err := ac . GetUserPermissions ( c . Req . Context ( ) , c . SignedInUser )
if err != nil {
c . JsonApiErr ( http . StatusForbidden , "" , err )
return
}
if c . SignedInUser . Permissions == nil {
c . SignedInUser . Permissions = make ( map [ int64 ] map [ string ] [ ] string )
}
c . SignedInUser . Permissions [ c . OrgId ] = accesscontrol . GroupScopesByAction ( permissions )
}
}