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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 296 additions and 419 deletions

View File

@ -60,7 +60,7 @@ func (hs *HTTPServer) registerRoutes() {
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg) reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg) redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
authorize := ac.Middleware(hs.AccessControl) 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) quota := middleware.Quota(hs.QuotaService)
r := hs.RouteRegister r := hs.RouteRegister

View File

@ -5,6 +5,9 @@ import (
"strings" "strings"
"testing" "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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -136,6 +139,11 @@ func TestAPIEndpoint_UpdateOrg(t *testing.T) {
ExpectedSignedInUser: &user.SignedInUser{OrgID: tt.targetOrgID}, ExpectedSignedInUser: &user.SignedInUser{OrgID: tt.targetOrgID},
} }
hs.accesscontrolService = actest.FakeService{} 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)) 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 { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { 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) { server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg() hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}} hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}} hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}}
hs.accesscontrolService = actest.FakeService{ExpectedPermissions: tt.permission} 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)) 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 { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { 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) { server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg() hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}} hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}}
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}} hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: &user.SignedInUser{OrgID: 1}}
hs.accesscontrolService = &actest.FakeService{ExpectedPermissions: tt.permissions} hs.accesscontrolService = &actest.FakeService{ExpectedPermissions: tt.permissions}
hs.authnService = &authntest.FakeService{
ExpectedIdentity: expectedIdentity,
}
}) })
verify := func(path string) { verify := func(path string) {
req := webtest.RequestWithSignedInUser(server.NewGetRequest(path), authedUserWithPermissions(1, 1, tt.permissions)) req := webtest.RequestWithSignedInUser(server.NewGetRequest(path), authedUserWithPermissions(1, 1, tt.permissions))

View File

@ -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") hs.log.Debug("check installer's permissions, plugin wants to register an external service")
evaluator := evalAllPermissions(plugin.JSONData.IAM.Permissions) 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 { if hs.Cfg.RBACSingleOrganization {
// In a single organization setup, no need for a global check // In a single organization setup, no need for a global check
hasAccess = ac.HasAccess(hs.AccessControl, c) hasAccess = ac.HasAccess(hs.AccessControl, c)

View File

@ -12,12 +12,12 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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/api/dtos"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/log/logtest" "github.com/grafana/grafana/pkg/infra/log/logtest"
@ -32,6 +32,8 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest" "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" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/org/orgtest"
@ -94,6 +96,16 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
ID: pluginID, 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) { t.Run(testName("Install", tc), func(t *testing.T) {
@ -734,6 +746,14 @@ func TestHTTPServer_hasPluginRequestedPermissions(t *testing.T) {
hs.accesscontrolService = actest.FakeService{} hs.accesscontrolService = actest.FakeService{}
hs.AccessControl = acimpl.ProvideAccessControl(hs.Cfg) hs.AccessControl = acimpl.ProvideAccessControl(hs.Cfg)
expectedIdentity := &authn.Identity{
OrgID: tt.orgID,
Permissions: tt.permissions,
}
hs.authnService = &authntest.FakeService{
ExpectedIdentity: expectedIdentity,
}
c := &contextmodel.ReqContext{ c := &contextmodel.ReqContext{
Context: &web.Context{Req: httpReq}, Context: &web.Context{Req: httpReq},
SignedInUser: &user.SignedInUser{OrgID: tt.orgID, Permissions: tt.permissions}, SignedInUser: &user.SignedInUser{OrgID: tt.orgID, Permissions: tt.permissions},

View File

@ -6,6 +6,9 @@ import (
"strings" "strings"
"testing" "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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -67,6 +70,9 @@ func TestAPIEndpoint_GetOrgQuotas(t *testing.T) {
hs.userService = &usertest.FakeUserService{ hs.userService = &usertest.FakeUserService{
ExpectedSignedInUser: &user.SignedInUser{OrgID: 2}, 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) { 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) { func TestAPIEndpoint_PutOrgQuotas(t *testing.T) {
cfg := setting.NewCfg() type testCase struct {
cfg.Quota = setting.QuotaSettings{ desc string
Enabled: true, userOrg int64
Global: setting.GlobalQuota{ targetOrg int64
Org: 5, 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) for _, tt := range tests {
t.Run("AccessControl allows updating another org quotas with correct permissions", func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
user := userWithPermissions(2, []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}}) cfg := setting.NewCfg()
user.OrgID = 1 cfg.Quota = setting.QuotaSettings{
fakeACService.ExpectedPermissions = []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}} Enabled: true,
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input), user) Global: setting.GlobalQuota{
response, err := server.SendJSON(req) Org: 5,
require.NoError(t, err) },
assert.Equal(t, http.StatusOK, response.StatusCode) Org: setting.OrgQuota{
require.NoError(t, response.Body.Close()) 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) server := SetupAPITestServer(t, func(hs *HTTPServer) {
t.Run("AccessControl prevents updating another org quotas with correct permissions in another org", func(t *testing.T) { hs.Cfg = cfg
user := userWithPermissions(1, []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}}) hs.accesscontrolService = fakeACService
user.Permissions[2] = nil hs.userService = &usertest.FakeUserService{
fakeACService.ExpectedPermissions = []accesscontrol.Permission{} ExpectedSignedInUser: &user.SignedInUser{OrgID: tt.userOrg},
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input), user) }
response, err := server.SendJSON(req) hs.authnService = &authntest.FakeService{
require.NoError(t, err) ExpectedIdentity: expectedIdentity,
assert.Equal(t, http.StatusForbidden, response.StatusCode) }
require.NoError(t, response.Body.Close()) })
})
input = strings.NewReader(testUpdateOrgQuotaCmd) user := userWithPermissions(tt.userOrg, getFirstOrgPermissions(tt.permissions))
t.Run("AccessControl prevents updating another org quotas with incorrect permissions", func(t *testing.T) { fakeACService.ExpectedPermissions = []accesscontrol.Permission{{Action: accesscontrol.ActionOrgsQuotasWrite}}
user := userWithPermissions(2, []accesscontrol.Permission{{Action: "orgs:invalid"}}) req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, tt.targetOrg, "org_user"), input), user)
fakeACService.ExpectedPermissions = []accesscontrol.Permission{} response, err := server.SendJSON(req)
req := webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input), user) require.NoError(t, err)
response, err := server.SendJSON(req) assert.Equal(t, tt.expectedCode, response.StatusCode)
require.NoError(t, err) require.NoError(t, response.Body.Close())
assert.Equal(t, http.StatusForbidden, 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{}
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@ -28,8 +29,6 @@ type Service interface {
GetRoleByName(ctx context.Context, orgID int64, roleName string) (*RoleDTO, error) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*RoleDTO, error)
// GetUserPermissions returns user permissions with only action and scope fields set. // GetUserPermissions returns user permissions with only action and scope fields set.
GetUserPermissions(ctx context.Context, user identity.Requester, options Options) ([]Permission, error) 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 returns all users' permissions filtered by an action prefix
SearchUsersPermissions(ctx context.Context, user identity.Requester, options SearchOptions) (map[int64][]Permission, error) SearchUsersPermissions(ctx context.Context, user identity.Requester, options SearchOptions) (map[int64][]Permission, error)
// ClearUserPermissionCache removes the permission cache entry for the given user // 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 // 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 { return func(evaluator Evaluator) bool {
var targetOrgID int64 = GlobalOrgID 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 { if err != nil {
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err)) 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 { if err != nil {
c.Logger.Error("Error from access control system", "error", err) c.Logger.Error("Error from access control system", "error", err)
return false 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 // 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 return hasAccess
} }

View File

@ -23,7 +23,6 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator" "github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils" "github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
"github.com/grafana/grafana/pkg/services/auth/identity" "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/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
@ -161,48 +160,6 @@ func (s *Service) getCachedUserPermissions(ctx context.Context, user identity.Re
return permissions, nil 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) { func (s *Service) ClearUserPermissionCache(user identity.Requester) {
s.cache.Delete(permissionCacheKey(user)) s.cache.Delete(permissionCacheKey(user))
} }

View File

@ -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)
})
}
}

View File

@ -27,10 +27,6 @@ func (f FakeService) GetUserPermissions(ctx context.Context, user identity.Reque
return f.ExpectedPermissions, f.ExpectedErr 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) { func (f FakeService) SearchUsersPermissions(ctx context.Context, user identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
return f.ExpectedUsersPermissions, f.ExpectedErr return f.ExpectedUsersPermissions, f.ExpectedErr
} }

View File

@ -12,6 +12,8 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest" "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" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/team/teamtest" "github.com/grafana/grafana/pkg/services/team/teamtest"
@ -27,141 +29,102 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
// Define test cases // Define test cases
testCases := []struct { testCases := []struct {
name string name string
orgIDGetter accesscontrol.OrgIDGetter targetOrgId int64
evaluator accesscontrol.Evaluator targerOrgPermissions []accesscontrol.Permission
accessControl accesscontrol.AccessControl orgIDGetter accesscontrol.OrgIDGetter
acService accesscontrol.Service evaluator accesscontrol.Evaluator
userCache user.Service accessControl accesscontrol.AccessControl
ctxSignedInUser *user.SignedInUser acService accesscontrol.Service
teamService team.Service userCache user.Service
expectedStatus int ctxSignedInUser *user.SignedInUser
teamService team.Service
expectedStatus int
}{ }{
{ {
name: "should authorize user with global org ID - fetch", name: "should authorize user with global org ID - fetch",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) { targetOrgId: accesscontrol.GlobalOrgID,
return accesscontrol.GlobalOrgID, nil evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
}, accessControl: ac,
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), userCache: &usertest.FakeUserService{},
accessControl: ac, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
userCache: &usertest.FakeUserService{}, targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
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.StatusOK,
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
},
teamService: &teamtest.FakeService{},
expectedStatus: http.StatusOK,
}, },
{ {
name: "should authorize user with non-global org ID - no fetch", name: "should authorize user with non-global org ID - no fetch",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) { targetOrgId: 1,
return 1, nil targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac,
accessControl: ac, userCache: &usertest.FakeUserService{},
acService: &actest.FakeService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
userCache: &usertest.FakeUserService{}, teamService: &teamtest.FakeService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, expectedStatus: http.StatusOK,
teamService: &teamtest.FakeService{},
expectedStatus: http.StatusOK,
}, },
{ {
name: "should return 403 when user has no permissions for the org", name: "should return 403 when user has no permissions for the org",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) { targetOrgId: 1,
return 1, nil targerOrgPermissions: []accesscontrol.Permission{},
}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac,
accessControl: ac, userCache: &usertest.FakeUserService{},
userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{}},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{}}, teamService: &teamtest.FakeService{},
teamService: &teamtest.FakeService{}, expectedStatus: http.StatusForbidden,
acService: &actest.FakeService{},
expectedStatus: http.StatusForbidden,
}, },
{ {
name: "should return 200 when user has permissions for a global org", name: "should return 200 when user has permissions for a global org",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) { targetOrgId: accesscontrol.GlobalOrgID,
return accesscontrol.GlobalOrgID, nil targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac,
accessControl: ac, userCache: &usertest.FakeUserService{},
userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, teamService: &teamtest.FakeService{},
teamService: &teamtest.FakeService{}, expectedStatus: http.StatusOK,
acService: &actest.FakeService{
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
},
expectedStatus: http.StatusOK,
}, },
{ {
name: "should return 403 when user has no permissions for a global org", name: "should return 403 when user has no permissions for a global org",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) { targetOrgId: accesscontrol.GlobalOrgID,
return accesscontrol.GlobalOrgID, nil targerOrgPermissions: []accesscontrol.Permission{},
}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac,
accessControl: ac, userCache: &usertest.FakeUserService{},
userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, teamService: &teamtest.FakeService{},
teamService: &teamtest.FakeService{}, expectedStatus: http.StatusForbidden,
acService: &actest.FakeService{},
expectedStatus: http.StatusForbidden,
}, },
{ {
name: "should return 403 when user org ID doesn't match and user does not exist in org 2", 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) { targetOrgId: 2,
return 2, nil targerOrgPermissions: []accesscontrol.Permission{},
}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac,
accessControl: ac, userCache: &usertest.FakeUserService{ExpectedError: fmt.Errorf("user not found")},
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:*"}}}},
acService: &actest.FakeService{}, teamService: &teamtest.FakeService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, expectedStatus: http.StatusForbidden,
teamService: &teamtest.FakeService{},
expectedStatus: http.StatusForbidden,
}, },
{ {
name: "should return 403 early when api key org ID doesn't match", name: "should return 403 early when api key org ID doesn't match",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) { targetOrgId: 2,
return 2, nil targerOrgPermissions: []accesscontrol.Permission{},
}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac,
accessControl: ac, userCache: &usertest.FakeUserService{},
userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{ApiKeyID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
acService: &actest.FakeService{}, teamService: &teamtest.FakeService{},
ctxSignedInUser: &user.SignedInUser{ApiKeyID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, expectedStatus: http.StatusForbidden,
teamService: &teamtest.FakeService{},
expectedStatus: http.StatusForbidden,
}, },
{ {
name: "should fetch user permissions when they are empty", name: "should fetch user permissions when org ID doesn't match",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) { targetOrgId: 2,
return 1, nil targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac,
accessControl: ac, teamService: &teamtest.FakeService{},
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:*"}},
},
userCache: &usertest.FakeUserService{ userCache: &usertest.FakeUserService{
GetSignedInUserFn: func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { 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 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, expectedStatus: http.StatusOK,
}, },
{ {
name: "fails to fetch user permissions when org ID doesn't match", name: "fails to fetch user permissions when org ID doesn't match",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) { targetOrgId: 2,
return 2, nil targerOrgPermissions: []accesscontrol.Permission{},
}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac,
accessControl: ac, teamService: &teamtest.FakeService{},
teamService: &teamtest.FakeService{},
acService: &actest.FakeService{ acService: &actest.FakeService{
ExpectedErr: fmt.Errorf("failed to get user permissions"), ExpectedErr: fmt.Errorf("failed to get user permissions"),
}, },
@ -196,22 +158,17 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) {
}, },
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
accessControl: ac, accessControl: ac,
acService: &actest.FakeService{},
userCache: &usertest.FakeUserService{}, userCache: &usertest.FakeUserService{},
ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}},
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", name: "should fetch global user permissions when user is not a member of the target org",
orgIDGetter: func(c *contextmodel.ReqContext) (int64, error) { targetOrgId: 2,
return 2, nil targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"),
evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac,
accessControl: ac,
acService: &actest.FakeService{
ExpectedPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}},
},
userCache: &usertest.FakeUserService{ userCache: &usertest.FakeUserService{
GetSignedInUserFn: func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { 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 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 // Create test context
req := httptest.NewRequest(http.MethodGet, "/api/endpoint", nil) 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 // Create middleware
middleware := accesscontrol.AuthorizeInOrgMiddleware( middleware := accesscontrol.AuthorizeInOrgMiddleware(tc.accessControl, authnService)(orgIDGetter, tc.evaluator)
tc.accessControl,
service,
tc.userCache,
tc.teamService,
)(tc.orgIDGetter, tc.evaluator)
// Create test server // Create test server
server := web.New() server := web.New()

View File

@ -20,8 +20,6 @@ import (
"github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/org" "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/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
@ -177,15 +175,7 @@ func newID() string {
type OrgIDGetter func(c *contextmodel.ReqContext) (int64, error) type OrgIDGetter func(c *contextmodel.ReqContext) (int64, error)
type userCache interface { func AuthorizeInOrgMiddleware(ac AccessControl, authnService authn.Service) func(OrgIDGetter, Evaluator) web.Handler {
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 {
return func(getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler { return func(getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler {
return func(c *contextmodel.ReqContext) { return func(c *contextmodel.ReqContext) {
targetOrgID, err := getTargetOrg(c) targetOrgID, err := getTargetOrg(c)
@ -194,104 +184,25 @@ func AuthorizeInOrgMiddleware(ac AccessControl, service Service, userService use
return return
} }
tmpUser, err := makeTmpUser(c.Req.Context(), service, userService, teamService, c.SignedInUser, targetOrgID) var orgUser identity.Requester = c.SignedInUser
if err != nil { if targetOrgID != c.SignedInUser.GetOrgID() {
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err)) orgUser, err = authnService.ResolveIdentity(c.Req.Context(), targetOrgID, c.SignedInUser.GetID())
return if err != nil {
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
return
}
} }
authorize(c, ac, orgUser, evaluator)
authorize(c, ac, tmpUser, evaluator)
// guard against nil map // guard against nil map
if c.SignedInUser.Permissions == nil { if c.SignedInUser.Permissions == nil {
c.SignedInUser.Permissions = make(map[int64]map[string][]string) 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) { func UseOrgFromContextParams(c *contextmodel.ReqContext) (int64, error) {
orgID, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64) 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. // 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. // 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) { return func(c *contextmodel.ReqContext) (int64, error) {
query, err := getOrgQueryFromRequest(c) query, err := getOrgQueryFromRequest(c)
if err != nil { if err != nil {

View File

@ -22,7 +22,6 @@ type Calls struct {
Evaluate []interface{} Evaluate []interface{}
GetRoleByName []interface{} GetRoleByName []interface{}
GetUserPermissions []interface{} GetUserPermissions []interface{}
GetUserPermissionsInOrg []interface{}
ClearUserPermissionCache []interface{} ClearUserPermissionCache []interface{}
DeclareFixedRoles []interface{} DeclareFixedRoles []interface{}
DeclarePluginRoles []interface{} DeclarePluginRoles []interface{}
@ -50,7 +49,6 @@ type Mock struct {
EvaluateFunc func(context.Context, identity.Requester, accesscontrol.Evaluator) (bool, error) EvaluateFunc func(context.Context, identity.Requester, accesscontrol.Evaluator) (bool, error)
GetRoleByNameFunc func(context.Context, int64, string) (*accesscontrol.RoleDTO, error) GetRoleByNameFunc func(context.Context, int64, string) (*accesscontrol.RoleDTO, error)
GetUserPermissionsFunc func(context.Context, identity.Requester, accesscontrol.Options) ([]accesscontrol.Permission, 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) ClearUserPermissionCacheFunc func(identity.Requester)
DeclareFixedRolesFunc func(...accesscontrol.RoleRegistration) error DeclareFixedRolesFunc func(...accesscontrol.RoleRegistration) error
DeclarePluginRolesFunc func(context.Context, string, string, []plugins.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 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) { func (m *Mock) ClearUserPermissionCache(user identity.Requester) {
m.Calls.ClearUserPermissionCache = append(m.Calls.ClearUserPermissionCache, []interface{}{user}) m.Calls.ClearUserPermissionCache = append(m.Calls.ClearUserPermissionCache, []interface{}{user})
// Use override if provided // Use override if provided

View File

@ -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) { 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) {} func (f *FakeService) RegisterClient(c authn.Client) {}

View File

@ -17,7 +17,7 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
var configNodes []*navtree.NavLink var configNodes []*navtree.NavLink
ctx := c.Req.Context() ctx := c.Req.Context()
hasAccess := ac.HasAccess(s.accessControl, c) 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) orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead)
authConfigUIAvailable := s.license.FeatureEnabled(social.SAMLProviderName) || s.cfg.LDAPAuthEnabled authConfigUIAvailable := s.license.FeatureEnabled(social.SAMLProviderName) || s.cfg.LDAPAuthEnabled

View File

@ -9,6 +9,7 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol" ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/auth/identity" "github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/authn"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
@ -28,6 +29,7 @@ type ServiceImpl struct {
cfg *setting.Cfg cfg *setting.Cfg
log log.Logger log log.Logger
accessControl ac.AccessControl accessControl ac.AccessControl
authnService authn.Service
pluginStore pluginstore.Store pluginStore pluginstore.Store
pluginSettings pluginsettings.Service pluginSettings pluginsettings.Service
starService star.Service starService star.Service
@ -50,11 +52,14 @@ type NavigationAppConfig struct {
Icon string 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{ service := &ServiceImpl{
cfg: cfg, cfg: cfg,
log: log.New("navtree service"), log: log.New("navtree service"),
accessControl: accessControl, accessControl: accessControl,
authnService: authnService,
pluginStore: pluginStore, pluginStore: pluginStore,
pluginSettings: pluginSettings, pluginSettings: pluginSettings,
starService: starService, starService: starService,