diff --git a/pkg/api/api.go b/pkg/api/api.go index 55d218a1d06..dee4f01541e 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -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 diff --git a/pkg/api/org_test.go b/pkg/api/org_test.go index 48ec23aab37..39acc89cbd2 100644 --- a/pkg/api/org_test.go +++ b/pkg/api/org_test.go @@ -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)) diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 59209f44910..d7bc8f1fea2 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -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) diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 9fdcf916ebc..4731b2fb032 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -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}, diff --git a/pkg/api/quota_test.go b/pkg/api/quota_test.go index b5ea62293f0..09609b127cf 100644 --- a/pkg/api/quota_test.go +++ b/pkg/api/quota_test.go @@ -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{} } diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index 97672df2c9b..1c5a241a5a1 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -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 } diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index 3a20e60f79c..0e094ac5340 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -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)) } diff --git a/pkg/services/accesscontrol/acimpl/service_test.go b/pkg/services/accesscontrol/acimpl/service_test.go index bce637ef848..3e9a1a0fc34 100644 --- a/pkg/services/accesscontrol/acimpl/service_test.go +++ b/pkg/services/accesscontrol/acimpl/service_test.go @@ -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) - }) - } -} diff --git a/pkg/services/accesscontrol/actest/fake.go b/pkg/services/accesscontrol/actest/fake.go index 21698bd410a..12e4330fee8 100644 --- a/pkg/services/accesscontrol/actest/fake.go +++ b/pkg/services/accesscontrol/actest/fake.go @@ -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 } diff --git a/pkg/services/accesscontrol/authorize_in_org_test.go b/pkg/services/accesscontrol/authorize_in_org_test.go index 8a9f6d2b814..81d53dab2b8 100644 --- a/pkg/services/accesscontrol/authorize_in_org_test.go +++ b/pkg/services/accesscontrol/authorize_in_org_test.go @@ -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() diff --git a/pkg/services/accesscontrol/middleware.go b/pkg/services/accesscontrol/middleware.go index bcc588fb4ae..e50787f941b 100644 --- a/pkg/services/accesscontrol/middleware.go +++ b/pkg/services/accesscontrol/middleware.go @@ -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 { diff --git a/pkg/services/accesscontrol/mock/mock.go b/pkg/services/accesscontrol/mock/mock.go index b3b3452d94f..2390df1f1d7 100644 --- a/pkg/services/accesscontrol/mock/mock.go +++ b/pkg/services/accesscontrol/mock/mock.go @@ -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 diff --git a/pkg/services/authn/authntest/fake.go b/pkg/services/authn/authntest/fake.go index c83154a9d8c..02bc1f11944 100644 --- a/pkg/services/authn/authntest/fake.go +++ b/pkg/services/authn/authntest/fake.go @@ -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) {} diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index 8d3b73dfef6..5dad84f681a 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -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 diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index b2bdbb30b22..504e5d3af31 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -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,