Access control: Use ResolveIdentity() for authorizing in org (#85549)

* Access control: Use ResolveIdentity() for authorizing in org

* Fix tests

* Fix middleware tests

* Use ResolveIdentity in HasGlobalAccess() function

* remove makeTmpUser

* Cleanup

* Fix linter errors

* Fix test build

* Remove GetUserPermissionsInOrg()
This commit is contained in:
Alexander Zobnin
2024-04-10 12:42:13 +02:00
committed by GitHub
parent ebb4bb859e
commit 3127566a20
15 changed files with 296 additions and 419 deletions
+11 -100
View File
@@ -20,8 +20,6 @@ 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"
"github.com/grafana/grafana/pkg/web"
@@ -177,15 +175,7 @@ func newID() string {
type OrgIDGetter func(c *contextmodel.ReqContext) (int64, error)
type userCache interface {
GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error)
}
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 {
func AuthorizeInOrgMiddleware(ac AccessControl, authnService authn.Service) func(OrgIDGetter, Evaluator) web.Handler {
return func(getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler {
return func(c *contextmodel.ReqContext) {
targetOrgID, err := getTargetOrg(c)
@@ -194,104 +184,25 @@ func AuthorizeInOrgMiddleware(ac AccessControl, service Service, userService use
return
}
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
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
}
}
authorize(c, ac, tmpUser, evaluator)
authorize(c, ac, orgUser, evaluator)
// guard against nil map
if c.SignedInUser.Permissions == nil {
c.SignedInUser.Permissions = make(map[int64]map[string][]string)
}
c.SignedInUser.Permissions[tmpUser.GetOrgID()] = tmpUser.GetPermissions()
c.SignedInUser.Permissions[orgUser.GetOrgID()] = orgUser.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
// Only fetch the team membership is the user is a member of the organization
if queryResult.OrgID == targetOrgID {
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 the user is not a member of the organization
// evaluation must happen based on global permissions.
evaluationOrg := targetOrgID
if tmpUser.OrgID == NoOrgID {
evaluationOrg = GlobalOrgID
}
if tmpUser.Permissions[evaluationOrg] == nil || len(tmpUser.Permissions[evaluationOrg]) == 0 {
permissions, err := service.GetUserPermissions(ctx, tmpUser, Options{})
if err != nil {
return nil, err
}
tmpUser.Permissions[evaluationOrg] = GroupScopesByAction(permissions)
}
return tmpUser, nil
}
func UseOrgFromContextParams(c *contextmodel.ReqContext) (int64, error) {
orgID, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)
@@ -339,7 +250,7 @@ func UseOrgFromRequestData(c *contextmodel.ReqContext) (int64, error) {
// UseGlobalOrgFromRequestData returns global org if `global` flag is set or the org where user is logged in.
// 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.
func UseGlobalOrgFromRequestData(cfg *setting.Cfg) func(*contextmodel.ReqContext) (int64, error) {
func UseGlobalOrgFromRequestData(cfg *setting.Cfg) OrgIDGetter {
return func(c *contextmodel.ReqContext) (int64, error) {
query, err := getOrgQueryFromRequest(c)
if err != nil {