Files
Gabriel MABILLE f3433fd472 RBAC: Remove accessControlOnCall feature toggle (#101222)
* RBAC: Remove accessControlOnCall feature toggle

* Leave the other one in place

* Tests

* frontend

* Readd empty ft to frontend test

* Remove legacy RBAC check

* Fix test

* no need for context

* Remove unused variable

* Remove unecessary param

* remove unecessary param from tests

* More tests :D
2025-02-25 13:44:40 +01:00

219 lines
7.3 KiB
Go

package api
import (
"encoding/json"
"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
}
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"}},
},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
acSvc := actest.FakeService{ExpectedUsersPermissions: tt.permissions}
accessControl := actest.FakeAccessControl{ExpectedEvaluate: true} // Always allow access to the endpoint
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()
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)
}
})
}
}