mirror of
https://github.com/grafana/grafana.git
synced 2025-01-02 12:17:01 -06:00
RBAC: Fix authorize in org (#81552)
* RBAC: Fix authorize in org * Implement option 2 * Fix typo * Fix alerting test * Add test to cover the not member case
This commit is contained in:
parent
0f1ba3a9fe
commit
3df0611f81
@ -39,13 +39,19 @@ func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace, identifier := user.GetNamespacedID()
|
namespace, identifier := user.GetNamespacedID()
|
||||||
if len(user.GetPermissions()) == 0 {
|
|
||||||
|
// If the user is in no organization, then the evaluation must happen based on the user's global permissions
|
||||||
|
permissions := user.GetPermissions()
|
||||||
|
if user.GetOrgID() == accesscontrol.NoOrgID {
|
||||||
|
permissions = user.GetGlobalPermissions()
|
||||||
|
}
|
||||||
|
if len(permissions) == 0 {
|
||||||
a.log.Debug("No permissions set for entity", "namespace", namespace, "id", identifier, "orgID", user.GetOrgID(), "login", user.GetLogin())
|
a.log.Debug("No permissions set for entity", "namespace", namespace, "id", identifier, "orgID", user.GetOrgID(), "login", user.GetLogin())
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test evaluation without scope resolver first, this will prevent 403 for wildcard scopes when resource does not exist
|
// Test evaluation without scope resolver first, this will prevent 403 for wildcard scopes when resource does not exist
|
||||||
if evaluator.Evaluate(user.GetPermissions()) {
|
if evaluator.Evaluate(permissions) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +63,7 @@ func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, e
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvedEvaluator.Evaluate(user.GetPermissions()), nil
|
return resolvedEvaluator.Evaluate(permissions), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver accesscontrol.ScopeAttributeResolver) {
|
func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver accesscontrol.ScopeAttributeResolver) {
|
||||||
|
@ -202,6 +202,24 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
|
|||||||
teamService: &teamtest.FakeService{},
|
teamService: &teamtest.FakeService{},
|
||||||
expectedStatus: http.StatusForbidden,
|
expectedStatus: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "should fetch global user permissions when user is not a member of the target org",
|
||||||
|
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||||
|
return 2, nil
|
||||||
|
},
|
||||||
|
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||||
|
accessControl: ac,
|
||||||
|
acService: &actest.FakeService{
|
||||||
|
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||||
|
},
|
||||||
|
userCache: &usertest.FakeUserService{
|
||||||
|
GetSignedInUserFn: func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) {
|
||||||
|
return &user.SignedInUser{UserID: 1, OrgID: -1, Permissions: map[int64]map[string][]string{}}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:write": {"users:*"}}}},
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run test cases
|
// Run test cases
|
||||||
|
@ -260,6 +260,8 @@ func makeTmpUser(ctx context.Context, service Service, cache userCache,
|
|||||||
tmpUser.OrgName = queryResult.OrgName
|
tmpUser.OrgName = queryResult.OrgName
|
||||||
tmpUser.OrgRole = queryResult.OrgRole
|
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 {
|
if teamService != nil {
|
||||||
teamIDs, err := teamService.GetTeamIDsByUser(ctx, &team.GetTeamIDsByUserQuery{OrgID: targetOrgID, UserID: tmpUser.UserID})
|
teamIDs, err := teamService.GetTeamIDsByUser(ctx, &team.GetTeamIDsByUserQuery{OrgID: targetOrgID, UserID: tmpUser.UserID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -269,14 +271,21 @@ func makeTmpUser(ctx context.Context, service Service, cache userCache,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if tmpUser.Permissions[targetOrgID] == nil || len(tmpUser.Permissions[targetOrgID]) == 0 {
|
// 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{})
|
permissions, err := service.GetUserPermissions(ctx, tmpUser, Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpUser.Permissions[targetOrgID] = GroupScopesByAction(permissions)
|
tmpUser.Permissions[evaluationOrg] = GroupScopesByAction(permissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tmpUser, nil
|
return tmpUser, nil
|
||||||
|
@ -330,6 +330,7 @@ func (cmd *SaveExternalServiceRoleCommand) Validate() error {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
GlobalOrgID = 0
|
GlobalOrgID = 0
|
||||||
|
NoOrgID = int64(-1)
|
||||||
GeneralFolderUID = "general"
|
GeneralFolderUID = "general"
|
||||||
RoleGrafanaAdmin = "Grafana Admin"
|
RoleGrafanaAdmin = "Grafana Admin"
|
||||||
|
|
||||||
|
@ -40,6 +40,8 @@ type Requester interface {
|
|||||||
GetOrgRole() roletype.RoleType
|
GetOrgRole() roletype.RoleType
|
||||||
// GetPermissions returns the permissions of the active entity.
|
// GetPermissions returns the permissions of the active entity.
|
||||||
GetPermissions() map[string][]string
|
GetPermissions() map[string][]string
|
||||||
|
// GetGlobalPermissions returns the permissions of the active entity that are available across all organizations.
|
||||||
|
GetGlobalPermissions() map[string][]string
|
||||||
// DEPRECATED: GetTeams returns the teams the entity is a member of.
|
// DEPRECATED: GetTeams returns the teams the entity is a member of.
|
||||||
// Retrieve the teams from the team service instead of using this method.
|
// Retrieve the teams from the team service instead of using this method.
|
||||||
GetTeams() []int64
|
GetTeams() []int64
|
||||||
|
@ -31,6 +31,7 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
AnonymousNamespaceID = NamespaceAnonymous + ":0"
|
AnonymousNamespaceID = NamespaceAnonymous + ":0"
|
||||||
|
GlobalOrgID = int64(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ identity.Requester = (*Identity)(nil)
|
var _ identity.Requester = (*Identity)(nil)
|
||||||
@ -164,6 +165,19 @@ func (i *Identity) GetPermissions() map[string][]string {
|
|||||||
return i.Permissions[i.GetOrgID()]
|
return i.Permissions[i.GetOrgID()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGlobalPermissions returns the permissions of the active entity that are available across all organizations
|
||||||
|
func (i *Identity) GetGlobalPermissions() map[string][]string {
|
||||||
|
if i.Permissions == nil {
|
||||||
|
return make(map[string][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Permissions[GlobalOrgID] == nil {
|
||||||
|
return make(map[string][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.Permissions[GlobalOrgID]
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Identity) GetTeams() []int64 {
|
func (i *Identity) GetTeams() []int64 {
|
||||||
return i.Teams
|
return i.Teams
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,10 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GlobalOrgID = int64(0)
|
||||||
|
)
|
||||||
|
|
||||||
type SignedInUser struct {
|
type SignedInUser struct {
|
||||||
UserID int64 `xorm:"user_id"`
|
UserID int64 `xorm:"user_id"`
|
||||||
OrgID int64 `xorm:"org_id"`
|
OrgID int64 `xorm:"org_id"`
|
||||||
@ -159,6 +163,19 @@ func (u *SignedInUser) GetPermissions() map[string][]string {
|
|||||||
return u.Permissions[u.GetOrgID()]
|
return u.Permissions[u.GetOrgID()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGlobalPermissions returns the permissions of the active entity that are available across all organizations
|
||||||
|
func (u *SignedInUser) GetGlobalPermissions() map[string][]string {
|
||||||
|
if u.Permissions == nil {
|
||||||
|
return make(map[string][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Permissions[GlobalOrgID] == nil {
|
||||||
|
return make(map[string][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Permissions[GlobalOrgID]
|
||||||
|
}
|
||||||
|
|
||||||
// DEPRECATED: GetTeams returns the teams the entity is a member of
|
// DEPRECATED: GetTeams returns the teams the entity is a member of
|
||||||
// Retrieve the teams from the team service instead of using this method.
|
// Retrieve the teams from the team service instead of using this method.
|
||||||
func (u *SignedInUser) GetTeams() []int64 {
|
func (u *SignedInUser) GetTeams() []int64 {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/models/roletype"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
@ -92,9 +93,10 @@ func TestBacktesting(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("if user does not have permissions", func(t *testing.T) {
|
t.Run("if user does not have permissions", func(t *testing.T) {
|
||||||
testUserId := createUser(t, env.SQLStore, user.CreateUserCommand{
|
testUserId := createUser(t, env.SQLStore, user.CreateUserCommand{
|
||||||
DefaultOrgRole: "",
|
DefaultOrgRole: string(roletype.RoleNone),
|
||||||
Password: "test",
|
Password: "test",
|
||||||
Login: "test",
|
Login: "test",
|
||||||
|
OrgID: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
testUserApiCli := newAlertingApiClient(grafanaListedAddr, "test", "test")
|
testUserApiCli := newAlertingApiClient(grafanaListedAddr, "test", "test")
|
||||||
|
Loading…
Reference in New Issue
Block a user