Authz: Remove use of SignedInUser copy for permission evaluation (#78448)

* remove use of SignedInUserCopies

* add extra safety to not cross assign permissions

unwind circular dependency

dashboardacl->dashboardaccess

fix missing import

* correctly set teams for permissions

* fix missing inits

* nit: check err

* exit early for api keys
This commit is contained in:
Jo
2023-11-22 14:20:22 +01:00
committed by GitHub
parent 392a4342a8
commit 0de66a8099
44 changed files with 422 additions and 337 deletions

View File

@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@ -179,60 +180,108 @@ type userCache interface {
GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error)
}
func AuthorizeInOrgMiddleware(ac AccessControl, service Service, cache userCache) func(OrgIDGetter, Evaluator) web.Handler {
type teamService interface {
GetTeamIDsByUser(ctx context.Context, query *team.GetTeamIDsByUserQuery) ([]int64, error)
}
func AuthorizeInOrgMiddleware(ac AccessControl, service Service, userService userCache, teamService teamService) func(OrgIDGetter, Evaluator) web.Handler {
return func(getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler {
return func(c *contextmodel.ReqContext) {
// We need to copy the user here because we're going to mutate it
userCopy := *(c.SignedInUser)
targetOrgID, err := getTargetOrg(c)
if err != nil {
deny(c, nil, fmt.Errorf("failed to get target org: %w", err))
return
}
if userCopy.OrgID != targetOrgID {
switch targetOrgID {
case GlobalOrgID:
userCopy.OrgID = GlobalOrgID
userCopy.OrgRole = org.RoleNone
userCopy.OrgName = ""
default:
query := user.GetSignedInUserQuery{UserID: c.UserID, OrgID: targetOrgID}
queryResult, err := cache.GetSignedInUserWithCacheCtx(c.Req.Context(), &query)
if err != nil {
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
return
}
userCopy.OrgID = queryResult.OrgID
userCopy.OrgName = queryResult.OrgName
userCopy.OrgRole = queryResult.OrgRole
}
tmpUser, err := makeTmpUser(c.Req.Context(), service, userService, teamService, c.SignedInUser, targetOrgID)
if err != nil {
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
return
}
if userCopy.Permissions[targetOrgID] == 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))
}
// guard against nil map
if userCopy.Permissions == nil {
userCopy.Permissions = make(map[int64]map[string][]string)
}
userCopy.Permissions[targetOrgID] = GroupScopesByAction(permissions)
}
authorize(c, ac, &userCopy, evaluator)
authorize(c, ac, tmpUser, evaluator)
// guard against nil map
if c.SignedInUser.Permissions == nil {
c.SignedInUser.Permissions = make(map[int64]map[string][]string)
}
c.SignedInUser.Permissions[targetOrgID] = userCopy.Permissions[targetOrgID]
c.SignedInUser.Permissions[tmpUser.GetOrgID()] = tmpUser.GetPermissions()
}
}
}
// makeTmpUser creates a temporary user that can be used to evaluate access across orgs.
func makeTmpUser(ctx context.Context, service Service, cache userCache,
teamService teamService, reqUser identity.Requester, targetOrgID int64) (identity.Requester, error) {
tmpUser := &user.SignedInUser{
OrgID: reqUser.GetOrgID(),
OrgName: reqUser.GetOrgName(),
OrgRole: reqUser.GetOrgRole(),
IsGrafanaAdmin: reqUser.GetIsGrafanaAdmin(),
Login: reqUser.GetLogin(),
Teams: reqUser.GetTeams(),
Permissions: map[int64]map[string][]string{
reqUser.GetOrgID(): reqUser.GetPermissions(),
},
}
namespace, identifier := reqUser.GetNamespacedID()
id, _ := identity.IntIdentifier(namespace, identifier)
switch namespace {
case identity.NamespaceUser:
tmpUser.UserID = id
case identity.NamespaceAPIKey:
tmpUser.ApiKeyID = id
if tmpUser.OrgID != targetOrgID {
return nil, errors.New("API key does not belong to target org")
}
case identity.NamespaceServiceAccount:
tmpUser.UserID = id
tmpUser.IsServiceAccount = true
}
if tmpUser.OrgID != targetOrgID {
switch targetOrgID {
case GlobalOrgID:
tmpUser.OrgID = GlobalOrgID
tmpUser.OrgRole = org.RoleNone
tmpUser.OrgName = ""
tmpUser.Teams = []int64{}
default:
if cache == nil {
return nil, errors.New("user cache is nil")
}
query := user.GetSignedInUserQuery{UserID: tmpUser.UserID, OrgID: targetOrgID}
queryResult, err := cache.GetSignedInUserWithCacheCtx(ctx, &query)
if err != nil {
return nil, err
}
tmpUser.OrgID = queryResult.OrgID
tmpUser.OrgName = queryResult.OrgName
tmpUser.OrgRole = queryResult.OrgRole
if teamService != nil {
teamIDs, err := teamService.GetTeamIDsByUser(ctx, &team.GetTeamIDsByUserQuery{OrgID: targetOrgID, UserID: tmpUser.UserID})
if err != nil {
return nil, err
}
tmpUser.Teams = teamIDs
}
}
}
if tmpUser.Permissions[targetOrgID] == nil || len(tmpUser.Permissions[targetOrgID]) == 0 {
permissions, err := service.GetUserPermissions(ctx, tmpUser, Options{})
if err != nil {
return nil, err
}
tmpUser.Permissions[targetOrgID] = GroupScopesByAction(permissions)
}
return tmpUser, nil
}
func UseOrgFromContextParams(c *contextmodel.ReqContext) (int64, error) {
orgID, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)