/
githubmirror
/
grafana
Обзор
Документация
Войти
/
githubmirror
/
grafana
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
pkg/services/accesscontrol/api/api_test.go
281 строка
10 KB
Georges Chaudy
RBAC: Merge permissions from Zanzana (#125614)
04 июн 2026, 16:53
Не верифицирован
04 июн 2026, 16:53
29f7b14
Код
Авторство
О чём код?
package api import ( "encoding/json" "errors" "net/http" "testing" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/api/routing" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/usertest" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web/webtest" ) func TestAPI_getUserActions(t *testing.T) { type testCase struct { desc string permissions []ac.Permission expectedOutput util.DynMap expectedCode int } tests := []testCase{ { desc: "Should be able to get actions", permissions: []ac.Permission{ {Action: datasources.ActionRead, Scope: datasources.ScopeAll}, {Action: datasources.ActionRead, Scope: datasources.ScopeProvider.GetResourceScope("aabbccdd")}, }, expectedOutput: util.DynMap{datasources.ActionRead: true}, expectedCode: http.StatusOK, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { acSvc := actest.FakeService{ExpectedPermissions: tt.permissions} api := NewAccessControlAPI(routing.NewRouteRegister(), actest.FakeAccessControl{}, acSvc, &usertest.FakeUserService{}) api.RegisterAPIEndpoints() server := webtest.NewServer(t, api.RouteRegister) url := "/api/access-control/user/actions" req := server.NewGetRequest(url) webtest.RequestWithSignedInUser(req, &user.SignedInUser{ OrgID: 1, Permissions: map[int64]map[string][]string{}, }) res, err := server.Send(req) defer func() { require.NoError(t, res.Body.Close()) }() require.NoError(t, err) require.Equal(t, tt.expectedCode, res.StatusCode) if tt.expectedCode == http.StatusOK { var output util.DynMap err := json.NewDecoder(res.Body).Decode(&output) require.NoError(t, err) require.Equal(t, tt.expectedOutput, output) } }) } } func TestAPI_getUserPermissions(t *testing.T) { type testCase struct { desc string permissions []ac.Permission expectedOutput util.DynMap expectedCode int } tests := []testCase{ { desc: "Should be able to get permissions with scope", permissions: []ac.Permission{ {Action: datasources.ActionRead, Scope: datasources.ScopeAll}, {Action: datasources.ActionRead, Scope: datasources.ScopeProvider.GetResourceScope("aabbccdd")}, }, expectedOutput: util.DynMap{ datasources.ActionRead: []any{ datasources.ScopeAll, datasources.ScopeProvider.GetResourceScope("aabbccdd"), }}, expectedCode: http.StatusOK, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { acSvc := actest.FakeService{ExpectedPermissions: tt.permissions} api := NewAccessControlAPI(routing.NewRouteRegister(), actest.FakeAccessControl{}, acSvc, &usertest.FakeUserService{}) api.RegisterAPIEndpoints() server := webtest.NewServer(t, api.RouteRegister) url := "/api/access-control/user/permissions" req := server.NewGetRequest(url) webtest.RequestWithSignedInUser(req, &user.SignedInUser{ OrgID: 1, Permissions: map[int64]map[string][]string{}, }) res, err := server.Send(req) defer func() { require.NoError(t, res.Body.Close()) }() require.NoError(t, err) require.Equal(t, tt.expectedCode, res.StatusCode) if tt.expectedCode == http.StatusOK { var output util.DynMap err := json.NewDecoder(res.Body).Decode(&output) require.NoError(t, err) for k, v := range output { scopes, ok := tt.expectedOutput[k] require.True(t, ok) require.ElementsMatch(t, scopes, v) } } }) } } func TestAccessControlAPI_searchUsersPermissions(t *testing.T) { type testCase struct { desc string permissions map[int64][]ac.Permission filters string expectedOutput map[int64]map[string][]string expectedCode int serviceErr error } tests := []testCase{ { desc: "Should reject if no filter is provided", expectedCode: http.StatusBadRequest, }, { desc: "Should reject if conflicting action filters are provided", filters: "?actionPrefix=grafana-test-app&action=grafana-test-app.projects:read", expectedCode: http.StatusBadRequest, }, { desc: "Should work with valid namespacedId filter provided", filters: "?namespacedId=service-account:2", permissions: map[int64][]ac.Permission{2: {{Action: "users:read", Scope: "users:*"}}}, expectedCode: http.StatusOK, expectedOutput: map[int64]map[string][]string{2: {"users:read": {"users:*"}}}, }, { desc: "Should resolve UID based identifier to the corresponding ID", filters: "?namespacedId=user:user_2_uid", permissions: map[int64][]ac.Permission{2: {{Action: "users:read", Scope: "users:*"}}}, expectedCode: http.StatusOK, expectedOutput: map[int64]map[string][]string{2: {"users:read": {"users:*"}}}, }, { desc: "Should fail if cannot resolve UID based identifier", filters: "?namespacedId=user:non_existent_uid", permissions: map[int64][]ac.Permission{2: {{Action: "users:read", Scope: "users:*"}}}, expectedCode: http.StatusBadRequest, }, { desc: "Should reduce permissions", filters: "?namespacedId=service-account:2", permissions: map[int64][]ac.Permission{2: {{Action: "users:read", Scope: "users:id:1"}, {Action: "users:read", Scope: "users:*"}}}, expectedCode: http.StatusOK, expectedOutput: map[int64]map[string][]string{2: {"users:read": {"users:*"}}}, }, { desc: "Should work with valid action prefix filter", filters: "?actionPrefix=users:", permissions: map[int64][]ac.Permission{ 1: {{Action: "users:write", Scope: "users:id:1"}}, 2: {{Action: "users:read", Scope: "users:id:2"}}, }, expectedCode: http.StatusOK, expectedOutput: map[int64]map[string][]string{ 1: {"users:write": {"users:id:1"}}, 2: {"users:read": {"users:id:2"}}, }, }, { desc: "Should return 500 for invalid namespacedId format", filters: "?namespacedId=invalid_format", expectedCode: http.StatusInternalServerError, }, { desc: "Should return 500 for unsupported identity type", filters: "?namespacedId=team:1", expectedCode: http.StatusInternalServerError, }, { desc: "Should accept exact action filter", filters: "?action=dashboards:read", permissions: map[int64][]ac.Permission{ 1: {{Action: "dashboards:read", Scope: "dashboards:*"}}, 2: {{Action: "dashboards:write", Scope: "dashboards:*"}}, }, expectedCode: http.StatusOK, expectedOutput: map[int64]map[string][]string{ 1: {"dashboards:read": {"dashboards:*"}}, 2: {"dashboards:write": {"dashboards:*"}}, }, }, { desc: "Should accept scope filter", filters: "?actionPrefix=teams:&scope=teams:id:1", permissions: map[int64][]ac.Permission{ 1: {{Action: "teams:read", Scope: "teams:id:1"}}, 2: {{Action: "teams:read", Scope: "teams:id:2"}}, }, expectedCode: http.StatusOK, expectedOutput: map[int64]map[string][]string{ 1: {"teams:read": {"teams:id:1"}}, 2: {"teams:read": {"teams:id:2"}}, }, }, { desc: "Should return 500 when service returns error", filters: "?actionPrefix=users:", serviceErr: errors.New("database connection failed"), expectedCode: http.StatusInternalServerError, }, { desc: "Should return empty map when no users match", filters: "?actionPrefix=nonexistent:", permissions: map[int64][]ac.Permission{}, expectedCode: http.StatusOK, expectedOutput: map[int64]map[string][]string{}, }, { desc: "Should resolve service-account UID", filters: "?namespacedId=service-account:sa_abc123", permissions: map[int64][]ac.Permission{3: {{Action: "users:read", Scope: "users:*"}}}, expectedCode: http.StatusOK, expectedOutput: map[int64]map[string][]string{3: {"users:read": {"users:*"}}}, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { acSvc := actest.FakeService{ ExpectedUsersPermissions: tt.permissions, ExpectedErr: tt.serviceErr, } accessControl := actest.FakeAccessControl{ExpectedEvaluate: true} mockUserSvc := usertest.NewMockService(t) mockUserSvc.On("GetByUID", mock.Anything, &user.GetUserByUIDQuery{UID: "user_2_uid"}).Return(&user.User{ID: 2}, nil).Maybe() mockUserSvc.On("GetByUID", mock.Anything, &user.GetUserByUIDQuery{UID: "non_existent_uid"}).Return(nil, user.ErrUserNotFound).Maybe() mockUserSvc.On("GetByUID", mock.Anything, &user.GetUserByUIDQuery{UID: "sa_abc123"}).Return(&user.User{ID: 3, IsServiceAccount: true}, nil).Maybe() api := NewAccessControlAPI(routing.NewRouteRegister(), accessControl, acSvc, mockUserSvc) api.RegisterAPIEndpoints() server := webtest.NewServer(t, api.RouteRegister) url := "/api/access-control/users/permissions/search" + tt.filters req := server.NewGetRequest(url) webtest.RequestWithSignedInUser(req, &user.SignedInUser{ OrgID: 1, Permissions: map[int64]map[string][]string{}, }) res, err := server.Send(req) defer func() { require.NoError(t, res.Body.Close()) }() require.NoError(t, err) require.Equal(t, tt.expectedCode, res.StatusCode) if tt.expectedCode == http.StatusOK { var output map[int64]map[string][]string err := json.NewDecoder(res.Body).Decode(&output) require.NoError(t, err) require.Equal(t, tt.expectedOutput, output) } }) } }