mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
ebb4bb859e
commit
3127566a20
@ -60,7 +60,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
|
||||
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
|
||||
authorize := ac.Middleware(hs.AccessControl)
|
||||
authorizeInOrg := ac.AuthorizeInOrgMiddleware(hs.AccessControl, hs.accesscontrolService, hs.userService, hs.teamService)
|
||||
authorizeInOrg := ac.AuthorizeInOrgMiddleware(hs.AccessControl, hs.authnService)
|
||||
quota := middleware.Quota(hs.QuotaService)
|
||||
|
||||
r := hs.RouteRegister
|
||||
|
@ -5,6 +5,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -136,6 +139,11 @@ func TestAPIEndpoint_UpdateOrg(t *testing.T) {
|
||||
ExpectedSignedInUser: &user.SignedInUser{OrgID: tt.targetOrgID},
|
||||
}
|
||||
hs.accesscontrolService = actest.FakeService{}
|
||||
hs.authnService = &authntest.FakeService{
|
||||
ExpectedIdentity: &authn.Identity{
|
||||
OrgID: tt.targetOrgID,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, tt.path, strings.NewReader(tt.body)), userWithPermissions(1, tt.permission))
|
||||
@ -209,11 +217,22 @@ func TestAPIEndpoint_DeleteOrgs(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
expectedIdentity := &authn.Identity{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: accesscontrol.GroupScopesByAction(tt.permission),
|
||||
},
|
||||
}
|
||||
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
|
||||
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}}
|
||||
hs.accesscontrolService = actest.FakeService{ExpectedPermissions: tt.permission}
|
||||
hs.authnService = &authntest.FakeService{}
|
||||
hs.authnService = &authntest.FakeService{
|
||||
ExpectedIdentity: expectedIdentity,
|
||||
}
|
||||
})
|
||||
|
||||
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodDelete, "/api/orgs/1", nil), userWithPermissions(2, nil))
|
||||
@ -246,11 +265,23 @@ func TestAPIEndpoint_GetOrg(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
expectedIdentity := &authn.Identity{
|
||||
ID: "user:1",
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
0: accesscontrol.GroupScopesByAction(tt.permissions),
|
||||
1: accesscontrol.GroupScopesByAction(tt.permissions),
|
||||
},
|
||||
}
|
||||
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
|
||||
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}}
|
||||
hs.accesscontrolService = &actest.FakeService{ExpectedPermissions: tt.permissions}
|
||||
hs.authnService = &authntest.FakeService{
|
||||
ExpectedIdentity: expectedIdentity,
|
||||
}
|
||||
})
|
||||
verify := func(path string) {
|
||||
req := webtest.RequestWithSignedInUser(server.NewGetRequest(path), authedUserWithPermissions(1, 1, tt.permissions))
|
||||
|
@ -539,7 +539,7 @@ func (hs *HTTPServer) hasPluginRequestedPermissions(c *contextmodel.ReqContext,
|
||||
|
||||
hs.log.Debug("check installer's permissions, plugin wants to register an external service")
|
||||
evaluator := evalAllPermissions(plugin.JSONData.IAM.Permissions)
|
||||
hasAccess := ac.HasGlobalAccess(hs.AccessControl, hs.accesscontrolService, c)
|
||||
hasAccess := ac.HasGlobalAccess(hs.AccessControl, hs.authnService, c)
|
||||
if hs.Cfg.RBACSingleOrganization {
|
||||
// In a single organization setup, no need for a global check
|
||||
hasAccess = ac.HasAccess(hs.AccessControl, c)
|
||||
|
@ -12,12 +12,12 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
||||
@ -32,6 +32,8 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||
@ -94,6 +96,16 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
ID: pluginID,
|
||||
},
|
||||
})
|
||||
|
||||
expectedIdentity := &authn.Identity{
|
||||
OrgID: tc.permissionOrg,
|
||||
Permissions: map[int64]map[string][]string{},
|
||||
OrgRoles: map[int64]org.RoleType{},
|
||||
}
|
||||
expectedIdentity.Permissions[tc.permissionOrg] = ac.GroupScopesByAction(tc.permissions)
|
||||
hs.authnService = &authntest.FakeService{
|
||||
ExpectedIdentity: expectedIdentity,
|
||||
}
|
||||
})
|
||||
|
||||
t.Run(testName("Install", tc), func(t *testing.T) {
|
||||
@ -734,6 +746,14 @@ func TestHTTPServer_hasPluginRequestedPermissions(t *testing.T) {
|
||||
hs.accesscontrolService = actest.FakeService{}
|
||||
hs.AccessControl = acimpl.ProvideAccessControl(hs.Cfg)
|
||||
|
||||
expectedIdentity := &authn.Identity{
|
||||
OrgID: tt.orgID,
|
||||
Permissions: tt.permissions,
|
||||
}
|
||||
hs.authnService = &authntest.FakeService{
|
||||
ExpectedIdentity: expectedIdentity,
|
||||
}
|
||||
|
||||
c := &contextmodel.ReqContext{
|
||||
Context: &web.Context{Req: httpReq},
|
||||
SignedInUser: &user.SignedInUser{OrgID: tt.orgID, Permissions: tt.permissions},
|
||||
|
@ -6,6 +6,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -67,6 +70,9 @@ func TestAPIEndpoint_GetOrgQuotas(t *testing.T) {
|
||||
hs.userService = &usertest.FakeUserService{
|
||||
ExpectedSignedInUser: &user.SignedInUser{OrgID: 2},
|
||||
}
|
||||
hs.authnService = &authntest.FakeService{
|
||||
ExpectedIdentity: &authn.Identity{OrgID: 1},
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("AccessControl allows viewing another org quotas with correct permissions", func(t *testing.T) {
|
||||
@ -96,60 +102,88 @@ func TestAPIEndpoint_GetOrgQuotas(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutOrgQuotas(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Quota = setting.QuotaSettings{
|
||||
Enabled: true,
|
||||
Global: setting.GlobalQuota{
|
||||
Org: 5,
|
||||
type testCase struct {
|
||||
desc string
|
||||
userOrg int64
|
||||
targetOrg int64
|
||||
permissions map[int64][]accesscontrol.Permission
|
||||
expectedCode int
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
desc: "AccessControl allows updating another org quotas with correct permissions",
|
||||
userOrg: 1,
|
||||
targetOrg: 2,
|
||||
permissions: map[int64][]accesscontrol.Permission{2: {{Action: accesscontrol.ActionOrgsQuotasWrite}}},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
Org: setting.OrgQuota{
|
||||
User: 5,
|
||||
{
|
||||
desc: "AccessControl prevents updating another org quotas with correct permissions in another org",
|
||||
userOrg: 1,
|
||||
targetOrg: 2,
|
||||
permissions: map[int64][]accesscontrol.Permission{1: {{Action: accesscontrol.ActionOrgsQuotasWrite}}},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
User: setting.UserQuota{
|
||||
Org: 5,
|
||||
{
|
||||
desc: "AccessControl prevents updating another org quotas with incorrect permissions",
|
||||
userOrg: 2,
|
||||
targetOrg: 2,
|
||||
permissions: map[int64][]accesscontrol.Permission{2: {{Action: "orgs:invalid"}}},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
fakeACService := &actest.FakeService{}
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = cfg
|
||||
hs.accesscontrolService = fakeACService
|
||||
hs.userService = &usertest.FakeUserService{
|
||||
ExpectedSignedInUser: &user.SignedInUser{OrgID: 2},
|
||||
}
|
||||
})
|
||||
|
||||
input := strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
t.Run("AccessControl allows updating another org quotas with correct permissions", func(t *testing.T) {
|
||||
user := userWithPermissions(2, []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}})
|
||||
user.OrgID = 1
|
||||
fakeACService.ExpectedPermissions = []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}}
|
||||
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input), user)
|
||||
response, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, response.StatusCode)
|
||||
require.NoError(t, response.Body.Close())
|
||||
})
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Quota = setting.QuotaSettings{
|
||||
Enabled: true,
|
||||
Global: setting.GlobalQuota{
|
||||
Org: 5,
|
||||
},
|
||||
Org: setting.OrgQuota{
|
||||
User: 5,
|
||||
},
|
||||
User: setting.UserQuota{
|
||||
Org: 5,
|
||||
},
|
||||
}
|
||||
fakeACService := &actest.FakeService{}
|
||||
input := strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
expectedIdentity := &authn.Identity{
|
||||
OrgID: tt.userOrg,
|
||||
Permissions: map[int64]map[string][]string{},
|
||||
}
|
||||
for orgID, permissions := range tt.permissions {
|
||||
expectedIdentity.Permissions[orgID] = accesscontrol.GroupScopesByAction(permissions)
|
||||
}
|
||||
|
||||
input = strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
t.Run("AccessControl prevents updating another org quotas with correct permissions in another org", func(t *testing.T) {
|
||||
user := userWithPermissions(1, []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}})
|
||||
user.Permissions[2] = nil
|
||||
fakeACService.ExpectedPermissions = []accesscontrol.Permission{}
|
||||
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input), user)
|
||||
response, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, response.StatusCode)
|
||||
require.NoError(t, response.Body.Close())
|
||||
})
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = cfg
|
||||
hs.accesscontrolService = fakeACService
|
||||
hs.userService = &usertest.FakeUserService{
|
||||
ExpectedSignedInUser: &user.SignedInUser{OrgID: tt.userOrg},
|
||||
}
|
||||
hs.authnService = &authntest.FakeService{
|
||||
ExpectedIdentity: expectedIdentity,
|
||||
}
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
t.Run("AccessControl prevents updating another org quotas with incorrect permissions", func(t *testing.T) {
|
||||
user := userWithPermissions(2, []accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
fakeACService.ExpectedPermissions = []accesscontrol.Permission{}
|
||||
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input), user)
|
||||
response, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, response.StatusCode)
|
||||
require.NoError(t, response.Body.Close())
|
||||
})
|
||||
user := userWithPermissions(tt.userOrg, getFirstOrgPermissions(tt.permissions))
|
||||
fakeACService.ExpectedPermissions = []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}}
|
||||
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, tt.targetOrg, "org_user"), input), user)
|
||||
response, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedCode, response.StatusCode)
|
||||
require.NoError(t, response.Body.Close())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getFirstOrgPermissions(p map[int64][]accesscontrol.Permission) []accesscontrol.Permission {
|
||||
for _, permissions := range p {
|
||||
return permissions
|
||||
}
|
||||
return []accesscontrol.Permission{}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"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/user"
|
||||
@ -28,8 +29,6 @@ type Service interface {
|
||||
GetRoleByName(ctx context.Context, orgID int64, roleName string) (*RoleDTO, error)
|
||||
// GetUserPermissions returns user permissions with only action and scope fields set.
|
||||
GetUserPermissions(ctx context.Context, user identity.Requester, options Options) ([]Permission, error)
|
||||
// GetUserPermissionsInOrg return user permission in a specific organization
|
||||
GetUserPermissionsInOrg(ctx context.Context, user identity.Requester, orgID int64) ([]Permission, error)
|
||||
// SearchUsersPermissions returns all users' permissions filtered by an action prefix
|
||||
SearchUsersPermissions(ctx context.Context, user identity.Requester, options SearchOptions) (map[int64][]Permission, error)
|
||||
// ClearUserPermissionCache removes the permission cache entry for the given user
|
||||
@ -171,22 +170,26 @@ type User struct {
|
||||
}
|
||||
|
||||
// HasGlobalAccess checks user access with globally assigned permissions only
|
||||
func HasGlobalAccess(ac AccessControl, service Service, c *contextmodel.ReqContext) func(evaluator Evaluator) bool {
|
||||
func HasGlobalAccess(ac AccessControl, authnService authn.Service, c *contextmodel.ReqContext) func(evaluator Evaluator) bool {
|
||||
return func(evaluator Evaluator) bool {
|
||||
var targetOrgID int64 = GlobalOrgID
|
||||
tmpUser, err := makeTmpUser(c.Req.Context(), service, nil, nil, c.SignedInUser, targetOrgID)
|
||||
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))
|
||||
}
|
||||
|
||||
hasAccess, err := ac.Evaluate(c.Req.Context(), tmpUser, evaluator)
|
||||
hasAccess, err := ac.Evaluate(c.Req.Context(), orgUser, evaluator)
|
||||
if err != nil {
|
||||
c.Logger.Error("Error from access control system", "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// guard against nil map
|
||||
if c.SignedInUser.Permissions == nil {
|
||||
c.SignedInUser.Permissions = make(map[int64]map[string][]string)
|
||||
}
|
||||
// set on user so we don't fetch global permissions every time this is called
|
||||
c.SignedInUser.Permissions[tmpUser.GetOrgID()] = tmpUser.GetPermissions()
|
||||
c.SignedInUser.Permissions[orgUser.GetOrgID()] = orgUser.GetPermissions()
|
||||
|
||||
return hasAccess
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
@ -161,48 +160,6 @@ func (s *Service) getCachedUserPermissions(ctx context.Context, user identity.Re
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetUserPermissionsInOrg(ctx context.Context, user identity.Requester, orgID int64) ([]accesscontrol.Permission, error) {
|
||||
permissions := make([]accesscontrol.Permission, 0)
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
|
||||
permissions = append(permissions, SharedWithMeFolderPermission)
|
||||
}
|
||||
|
||||
namespace, id := user.GetNamespacedID()
|
||||
userID, err := identity.UserIdentifier(namespace, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get permissions for user's basic roles from RAM
|
||||
roleList, err := s.store.GetUsersBasicRoles(ctx, []int64{userID}, orgID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not fetch basic roles for the user: %w", err)
|
||||
}
|
||||
var roles []string
|
||||
var ok bool
|
||||
if roles, ok = roleList[userID]; !ok {
|
||||
return nil, fmt.Errorf("found no basic roles for user %d in organisation %d", userID, orgID)
|
||||
}
|
||||
for _, builtin := range roles {
|
||||
if basicRole, ok := s.roles[builtin]; ok {
|
||||
permissions = append(permissions, basicRole.Permissions...)
|
||||
}
|
||||
}
|
||||
|
||||
dbPermissions, err := s.store.SearchUsersPermissions(ctx, orgID, accesscontrol.SearchOptions{
|
||||
NamespacedID: authn.NamespacedID(namespace, userID),
|
||||
// Query only basic, managed and plugin roles in OSS
|
||||
RolePrefixes: OSSRolesPrefixes,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userPermissions := dbPermissions[userID]
|
||||
return append(permissions, userPermissions...), nil
|
||||
}
|
||||
|
||||
func (s *Service) ClearUserPermissionCache(user identity.Requester) {
|
||||
s.cache.Delete(permissionCacheKey(user))
|
||||
}
|
||||
|
@ -943,59 +943,3 @@ func TestService_DeleteExternalServiceRole(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_GetUserPermissionsInOrg(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
orgID int64
|
||||
ramRoles map[string]*accesscontrol.RoleDTO // BasicRole => RBAC BasicRole
|
||||
storedPerms map[int64][]accesscontrol.Permission // UserID => Permissions
|
||||
storedRoles map[int64][]string // UserID => Roles
|
||||
want []accesscontrol.Permission
|
||||
}{
|
||||
{
|
||||
name: "should get correct permissions from another org",
|
||||
orgID: 2,
|
||||
ramRoles: map[string]*accesscontrol.RoleDTO{
|
||||
string(roletype.RoleEditor): {Permissions: []accesscontrol.Permission{}},
|
||||
string(roletype.RoleAdmin): {Permissions: []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
|
||||
}},
|
||||
},
|
||||
storedPerms: map[int64][]accesscontrol.Permission{
|
||||
1: {
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
|
||||
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"},
|
||||
},
|
||||
2: {
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:2"},
|
||||
},
|
||||
},
|
||||
storedRoles: map[int64][]string{
|
||||
1: {string(roletype.RoleAdmin)},
|
||||
2: {string(roletype.RoleEditor)},
|
||||
},
|
||||
want: []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ac := setupTestEnv(t)
|
||||
|
||||
ac.roles = tt.ramRoles
|
||||
ac.store = actest.FakeStore{
|
||||
ExpectedUsersPermissions: tt.storedPerms,
|
||||
ExpectedUsersRoles: tt.storedRoles,
|
||||
}
|
||||
user := &user.SignedInUser{OrgID: 1, UserID: 2}
|
||||
|
||||
got, err := ac.GetUserPermissionsInOrg(ctx, user, 2)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.ElementsMatch(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -27,10 +27,6 @@ func (f FakeService) GetUserPermissions(ctx context.Context, user identity.Reque
|
||||
return f.ExpectedPermissions, f.ExpectedErr
|
||||
}
|
||||
|
||||
func (f FakeService) GetUserPermissionsInOrg(ctx context.Context, user identity.Requester, orgID int64) ([]accesscontrol.Permission, error) {
|
||||
return f.ExpectedPermissions, f.ExpectedErr
|
||||
}
|
||||
|
||||
func (f FakeService) SearchUsersPermissions(ctx context.Context, user identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
|
||||
return f.ExpectedUsersPermissions, f.ExpectedErr
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamtest"
|
||||
@ -27,141 +29,102 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
|
||||
|
||||
// Define test cases
|
||||
testCases := []struct {
|
||||
name string
|
||||
orgIDGetter accesscontrol.OrgIDGetter
|
||||
evaluator accesscontrol.Evaluator
|
||||
accessControl accesscontrol.AccessControl
|
||||
acService accesscontrol.Service
|
||||
userCache user.Service
|
||||
ctxSignedInUser *user.SignedInUser
|
||||
teamService team.Service
|
||||
expectedStatus int
|
||||
name string
|
||||
targetOrgId int64
|
||||
targerOrgPermissions []accesscontrol.Permission
|
||||
orgIDGetter accesscontrol.OrgIDGetter
|
||||
evaluator accesscontrol.Evaluator
|
||||
accessControl accesscontrol.AccessControl
|
||||
acService accesscontrol.Service
|
||||
userCache user.Service
|
||||
ctxSignedInUser *user.SignedInUser
|
||||
teamService team.Service
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "should authorize user with global org ID - fetch",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return accesscontrol.GlobalOrgID, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
acService: &actest.FakeService{
|
||||
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusOK,
|
||||
name: "should authorize user with global org ID - fetch",
|
||||
targetOrgId: accesscontrol.GlobalOrgID,
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "should authorize user with non-global org ID - no fetch",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return 1, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
acService: &actest.FakeService{},
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusOK,
|
||||
name: "should authorize user with non-global org ID - no fetch",
|
||||
targetOrgId: 1,
|
||||
targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "should return 403 when user has no permissions for the org",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return 1, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
acService: &actest.FakeService{},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
name: "should return 403 when user has no permissions for the org",
|
||||
targetOrgId: 1,
|
||||
targerOrgPermissions: []accesscontrol.Permission{},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "should return 200 when user has permissions for a global org",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return accesscontrol.GlobalOrgID, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
acService: &actest.FakeService{
|
||||
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
name: "should return 200 when user has permissions for a global org",
|
||||
targetOrgId: accesscontrol.GlobalOrgID,
|
||||
targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "should return 403 when user has no permissions for a global org",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return accesscontrol.GlobalOrgID, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
acService: &actest.FakeService{},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
name: "should return 403 when user has no permissions for a global org",
|
||||
targetOrgId: accesscontrol.GlobalOrgID,
|
||||
targerOrgPermissions: []accesscontrol.Permission{},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "should return 403 when user org ID doesn't match and user does not exist in org 2",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return 2, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{ExpectedError: fmt.Errorf("user not found")},
|
||||
acService: &actest.FakeService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
name: "should return 403 when user org ID doesn't match and user does not exist in org 2",
|
||||
targetOrgId: 2,
|
||||
targerOrgPermissions: []accesscontrol.Permission{},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{ExpectedError: fmt.Errorf("user not found")},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "should return 403 early when api key org ID doesn't match",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return 2, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
acService: &actest.FakeService{},
|
||||
ctxSignedInUser: &user.SignedInUser{ApiKeyID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
name: "should return 403 early when api key org ID doesn't match",
|
||||
targetOrgId: 2,
|
||||
targerOrgPermissions: []accesscontrol.Permission{},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{ApiKeyID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "should fetch user permissions when they are empty",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return 1, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
acService: &actest.FakeService{
|
||||
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
},
|
||||
teamService: &teamtest.FakeService{},
|
||||
userCache: &usertest.FakeUserService{
|
||||
GetSignedInUserFn: func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) {
|
||||
return &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: nil}, nil
|
||||
},
|
||||
},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: nil},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "should fetch user permissions when org ID doesn't match",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return 2, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
teamService: &teamtest.FakeService{},
|
||||
acService: &actest.FakeService{
|
||||
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
},
|
||||
name: "should fetch user permissions when org ID doesn't match",
|
||||
targetOrgId: 2,
|
||||
targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
teamService: &teamtest.FakeService{},
|
||||
userCache: &usertest.FakeUserService{
|
||||
GetSignedInUserFn: func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) {
|
||||
return &user.SignedInUser{UserID: 1, OrgID: 2, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, nil
|
||||
@ -171,13 +134,12 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "fails to fetch user permissions when org ID doesn't match",
|
||||
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return 2, nil
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
teamService: &teamtest.FakeService{},
|
||||
name: "fails to fetch user permissions when org ID doesn't match",
|
||||
targetOrgId: 2,
|
||||
targerOrgPermissions: []accesscontrol.Permission{},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
teamService: &teamtest.FakeService{},
|
||||
acService: &actest.FakeService{
|
||||
ExpectedErr: fmt.Errorf("failed to get user permissions"),
|
||||
},
|
||||
@ -196,22 +158,17 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
|
||||
},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
acService: &actest.FakeService{},
|
||||
userCache: &usertest.FakeUserService{},
|
||||
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
|
||||
teamService: &teamtest.FakeService{},
|
||||
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:*"}},
|
||||
},
|
||||
name: "should fetch global user permissions when user is not a member of the target org",
|
||||
targetOrgId: 2,
|
||||
targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
|
||||
evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
|
||||
accessControl: ac,
|
||||
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
|
||||
@ -228,15 +185,28 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
|
||||
// Create test context
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/endpoint", nil)
|
||||
|
||||
service := tc.acService
|
||||
expectedIdentity := &authn.Identity{
|
||||
ID: fmt.Sprintf("user:%v", tc.ctxSignedInUser.UserID),
|
||||
OrgID: tc.targetOrgId,
|
||||
Permissions: map[int64]map[string][]string{},
|
||||
}
|
||||
expectedIdentity.Permissions[tc.targetOrgId] = accesscontrol.GroupScopesByAction(tc.targerOrgPermissions)
|
||||
|
||||
authnService := &authntest.FakeService{
|
||||
ExpectedIdentity: expectedIdentity,
|
||||
}
|
||||
|
||||
var orgIDGetter accesscontrol.OrgIDGetter
|
||||
if tc.orgIDGetter != nil {
|
||||
orgIDGetter = tc.orgIDGetter
|
||||
} else {
|
||||
orgIDGetter = func(c *contextmodel.ReqContext) (int64, error) {
|
||||
return tc.targetOrgId, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create middleware
|
||||
middleware := accesscontrol.AuthorizeInOrgMiddleware(
|
||||
tc.accessControl,
|
||||
service,
|
||||
tc.userCache,
|
||||
tc.teamService,
|
||||
)(tc.orgIDGetter, tc.evaluator)
|
||||
middleware := accesscontrol.AuthorizeInOrgMiddleware(tc.accessControl, authnService)(orgIDGetter, tc.evaluator)
|
||||
|
||||
// Create test server
|
||||
server := web.New()
|
||||
|
@ -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 {
|
||||
|
@ -22,7 +22,6 @@ type Calls struct {
|
||||
Evaluate []interface{}
|
||||
GetRoleByName []interface{}
|
||||
GetUserPermissions []interface{}
|
||||
GetUserPermissionsInOrg []interface{}
|
||||
ClearUserPermissionCache []interface{}
|
||||
DeclareFixedRoles []interface{}
|
||||
DeclarePluginRoles []interface{}
|
||||
@ -50,7 +49,6 @@ type Mock struct {
|
||||
EvaluateFunc func(context.Context, identity.Requester, accesscontrol.Evaluator) (bool, error)
|
||||
GetRoleByNameFunc func(context.Context, int64, string) (*accesscontrol.RoleDTO, error)
|
||||
GetUserPermissionsFunc func(context.Context, identity.Requester, accesscontrol.Options) ([]accesscontrol.Permission, error)
|
||||
GetUserPermissionsInOrgFunc func(context.Context, identity.Requester, int64) ([]accesscontrol.Permission, error)
|
||||
ClearUserPermissionCacheFunc func(identity.Requester)
|
||||
DeclareFixedRolesFunc func(...accesscontrol.RoleRegistration) error
|
||||
DeclarePluginRolesFunc func(context.Context, string, string, []plugins.RoleRegistration) error
|
||||
@ -152,16 +150,6 @@ func (m *Mock) GetUserPermissions(ctx context.Context, user identity.Requester,
|
||||
return m.permissions, nil
|
||||
}
|
||||
|
||||
func (m *Mock) GetUserPermissionsInOrg(ctx context.Context, user identity.Requester, orgID int64) ([]accesscontrol.Permission, error) {
|
||||
m.Calls.GetUserPermissionsInOrg = append(m.Calls.GetUserPermissionsInOrg, []interface{}{ctx, user, orgID})
|
||||
// Use override if provided
|
||||
if m.GetUserPermissionsInOrgFunc != nil {
|
||||
return m.GetUserPermissionsInOrgFunc(ctx, user, orgID)
|
||||
}
|
||||
// Otherwise return the Permissions list
|
||||
return m.permissions, nil
|
||||
}
|
||||
|
||||
func (m *Mock) ClearUserPermissionCache(user identity.Requester) {
|
||||
m.Calls.ClearUserPermissionCache = append(m.Calls.ClearUserPermissionCache, []interface{}{user})
|
||||
// Use override if provided
|
||||
|
@ -73,7 +73,25 @@ func (f *FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertok
|
||||
}
|
||||
|
||||
func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*authn.Identity, error) {
|
||||
panic("unimplemented")
|
||||
if f.ExpectedIdentities != nil {
|
||||
if f.CurrentIndex >= len(f.ExpectedIdentities) {
|
||||
panic("ExpectedIdentities is empty")
|
||||
}
|
||||
if f.CurrentIndex >= len(f.ExpectedErrs) {
|
||||
panic("ExpectedErrs is empty")
|
||||
}
|
||||
|
||||
identity := f.ExpectedIdentities[f.CurrentIndex]
|
||||
err := f.ExpectedErrs[f.CurrentIndex]
|
||||
|
||||
f.CurrentIndex += 1
|
||||
|
||||
return identity, err
|
||||
}
|
||||
|
||||
identity := f.ExpectedIdentity
|
||||
identity.OrgID = orgID
|
||||
return identity, f.ExpectedErr
|
||||
}
|
||||
|
||||
func (f *FakeService) RegisterClient(c authn.Client) {}
|
||||
|
@ -17,7 +17,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
var configNodes []*navtree.NavLink
|
||||
ctx := c.Req.Context()
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
hasGlobalAccess := ac.HasGlobalAccess(s.accessControl, s.accesscontrolService, c)
|
||||
hasGlobalAccess := ac.HasGlobalAccess(s.accessControl, s.authnService, c)
|
||||
orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead)
|
||||
authConfigUIAvailable := s.license.FeatureEnabled(social.SAMLProviderName) || s.cfg.LDAPAuthEnabled
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
@ -28,6 +29,7 @@ type ServiceImpl struct {
|
||||
cfg *setting.Cfg
|
||||
log log.Logger
|
||||
accessControl ac.AccessControl
|
||||
authnService authn.Service
|
||||
pluginStore pluginstore.Store
|
||||
pluginSettings pluginsettings.Service
|
||||
starService star.Service
|
||||
@ -50,11 +52,14 @@ type NavigationAppConfig struct {
|
||||
Icon string
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStore pluginstore.Store, pluginSettings pluginsettings.Service, starService star.Service, features featuremgmt.FeatureToggles, dashboardService dashboards.DashboardService, accesscontrolService ac.Service, kvStore kvstore.KVStore, apiKeyService apikey.Service, license licensing.Licensing) navtree.Service {
|
||||
func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStore pluginstore.Store, pluginSettings pluginsettings.Service, starService star.Service,
|
||||
features featuremgmt.FeatureToggles, dashboardService dashboards.DashboardService, accesscontrolService ac.Service, kvStore kvstore.KVStore, apiKeyService apikey.Service,
|
||||
license licensing.Licensing, authnService authn.Service) navtree.Service {
|
||||
service := &ServiceImpl{
|
||||
cfg: cfg,
|
||||
log: log.New("navtree service"),
|
||||
accessControl: accessControl,
|
||||
authnService: authnService,
|
||||
pluginStore: pluginStore,
|
||||
pluginSettings: pluginSettings,
|
||||
starService: starService,
|
||||
|
Loading…
Reference in New Issue
Block a user