Revert "Folders: Use authlib.AccessClient in authorizer" (#110812)
Revert "Folders: Use authlib.AccessClient in authorizer (#110602)"
This reverts commit 0cb52b8be0.
This commit is contained in:
@@ -19,7 +19,7 @@ import (
|
|||||||
"k8s.io/kube-openapi/pkg/spec3"
|
"k8s.io/kube-openapi/pkg/spec3"
|
||||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
|
|
||||||
authlib "github.com/grafana/authlib/types"
|
claims "github.com/grafana/authlib/types"
|
||||||
internal "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard"
|
internal "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard"
|
||||||
dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
|
dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
|
||||||
dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
||||||
@@ -79,7 +79,7 @@ type DashboardsAPIBuilder struct {
|
|||||||
|
|
||||||
authorizer authorizer.Authorizer
|
authorizer authorizer.Authorizer
|
||||||
accessControl accesscontrol.AccessControl
|
accessControl accesscontrol.AccessControl
|
||||||
accessClient authlib.AccessClient
|
accessClient claims.AccessClient
|
||||||
legacy *DashboardStorage
|
legacy *DashboardStorage
|
||||||
unified resource.ResourceClient
|
unified resource.ResourceClient
|
||||||
dashboardProvisioningService dashboards.DashboardProvisioningService
|
dashboardProvisioningService dashboards.DashboardProvisioningService
|
||||||
@@ -113,7 +113,7 @@ func RegisterAPIService(
|
|||||||
dashboardPermissions dashboards.PermissionsRegistrationService,
|
dashboardPermissions dashboards.PermissionsRegistrationService,
|
||||||
dashboardPermissionsSvc accesscontrol.DashboardPermissionsService,
|
dashboardPermissionsSvc accesscontrol.DashboardPermissionsService,
|
||||||
accessControl accesscontrol.AccessControl,
|
accessControl accesscontrol.AccessControl,
|
||||||
accessClient authlib.AccessClient,
|
accessClient claims.AccessClient,
|
||||||
provisioning provisioning.ProvisioningService,
|
provisioning provisioning.ProvisioningService,
|
||||||
dashStore dashboards.Store,
|
dashStore dashboards.Store,
|
||||||
reg prometheus.Registerer,
|
reg prometheus.Registerer,
|
||||||
@@ -167,7 +167,7 @@ func RegisterAPIService(
|
|||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIService(ac authlib.AccessClient, features featuremgmt.FeatureToggles, folderClientProvider client.K8sHandlerProvider, datasourceProvider schemaversion.DataSourceInfoProvider, pluginStore *pluginstore.Service) *DashboardsAPIBuilder {
|
func NewAPIService(ac claims.AccessClient, features featuremgmt.FeatureToggles, folderClientProvider client.K8sHandlerProvider, datasourceProvider schemaversion.DataSourceInfoProvider, pluginStore *pluginstore.Service) *DashboardsAPIBuilder {
|
||||||
// TODO: Plugin store will soon be removed,
|
// TODO: Plugin store will soon be removed,
|
||||||
// as the cases for plugin fetching is not needed. Keeping it now to not break implementation
|
// as the cases for plugin fetching is not needed. Keeping it now to not break implementation
|
||||||
if pluginStore == nil {
|
if pluginStore == nil {
|
||||||
@@ -274,7 +274,7 @@ func (b *DashboardsAPIBuilder) validateDelete(ctx context.Context, a admission.A
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
nsInfo, err := authlib.ParseNamespace(a.GetNamespace())
|
nsInfo, err := claims.ParseNamespace(a.GetNamespace())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%v: %w", "failed to parse namespace", err)
|
return fmt.Errorf("%v: %w", "failed to parse namespace", err)
|
||||||
}
|
}
|
||||||
@@ -380,7 +380,7 @@ func (b *DashboardsAPIBuilder) validateUpdate(ctx context.Context, a admission.A
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse namespace for old dashboard
|
// Parse namespace for old dashboard
|
||||||
nsInfo, err := authlib.ParseNamespace(oldAccessor.GetNamespace())
|
nsInfo, err := claims.ParseNamespace(oldAccessor.GetNamespace())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse namespace: %w", err)
|
return fmt.Errorf("failed to parse namespace: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,89 @@ package folders
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
|
|
||||||
authlib "github.com/grafana/authlib/types"
|
"github.com/grafana/authlib/types"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newAuthorizer(ac authlib.AccessChecker) authorizer.Authorizer {
|
// newLegacyAuthorizer creates an authorizer using legacy access control, this is only usable for single tenant api.
|
||||||
|
func newLegacyAuthorizer(ac accesscontrol.AccessControl) authorizer.Authorizer {
|
||||||
|
return authorizer.AuthorizerFunc(func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
in, err := authorizerFunc(ctx, attr)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, errNoUser) {
|
||||||
|
return authorizer.DecisionDeny, "", nil
|
||||||
|
}
|
||||||
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := ac.Evaluate(ctx, in.user, in.evaluator)
|
||||||
|
if ok {
|
||||||
|
return authorizer.DecisionAllow, "", nil
|
||||||
|
}
|
||||||
|
return authorizer.DecisionDeny, "folder", err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizerFunc(ctx context.Context, attr authorizer.Attributes) (*authorizerParams, error) {
|
||||||
|
allowedVerbs := []string{utils.VerbCreate, utils.VerbDelete, utils.VerbList}
|
||||||
|
verb := attr.GetVerb()
|
||||||
|
name := attr.GetName()
|
||||||
|
if (!attr.IsResourceRequest()) || (name == "" && verb != utils.VerbCreate && slices.Contains(allowedVerbs, verb)) {
|
||||||
|
return nil, errNoResource
|
||||||
|
}
|
||||||
|
|
||||||
|
// require a user
|
||||||
|
user, err := identity.GetRequester(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errNoUser
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(name)
|
||||||
|
var eval accesscontrol.Evaluator
|
||||||
|
|
||||||
|
// "get" is used for sub-resources with GET http (parents, access, count)
|
||||||
|
switch verb {
|
||||||
|
case utils.VerbCreate:
|
||||||
|
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersCreate)
|
||||||
|
case utils.VerbPatch:
|
||||||
|
fallthrough
|
||||||
|
case utils.VerbUpdate:
|
||||||
|
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, scope)
|
||||||
|
case utils.VerbDeleteCollection:
|
||||||
|
fallthrough
|
||||||
|
case utils.VerbDelete:
|
||||||
|
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, scope)
|
||||||
|
case utils.VerbList:
|
||||||
|
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersRead)
|
||||||
|
default:
|
||||||
|
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersRead, scope)
|
||||||
|
}
|
||||||
|
return &authorizerParams{evaluator: eval, user: user}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMultiTenantAuthorizer creates an authorizer suitable to multi-tenant setup.
|
||||||
|
// For now it only allow authorization of access tokens.
|
||||||
|
func newMultiTenantAuthorizer(ac types.AccessClient) authorizer.Authorizer {
|
||||||
return authorizer.AuthorizerFunc(func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
return authorizer.AuthorizerFunc(func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
info, ok := authlib.AuthInfoFrom(ctx)
|
info, ok := types.AuthInfoFrom(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return authorizer.DecisionDeny, "missing auth info", nil
|
return authorizer.DecisionDeny, "missing auth info", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := ac.Check(ctx, info, authlib.CheckRequest{
|
// For now we only allow access policy to authorize with multi-tenant setup
|
||||||
|
if !types.IsIdentityType(info.GetIdentityType(), types.TypeAccessPolicy) {
|
||||||
|
return authorizer.DecisionDeny, "permission denied", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := ac.Check(ctx, info, types.CheckRequest{
|
||||||
Verb: a.GetVerb(),
|
Verb: a.GetVerb(),
|
||||||
Group: a.GetAPIGroup(),
|
Group: a.GetAPIGroup(),
|
||||||
Resource: a.GetResource(),
|
Resource: a.GetResource(),
|
||||||
@@ -25,7 +94,7 @@ func newAuthorizer(ac authlib.AccessChecker) authorizer.Authorizer {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return authorizer.DecisionDeny, "failed to perform authorization", err
|
return authorizer.DecisionDeny, "faild to perform authorization", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !res.Allowed {
|
if !res.Allowed {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package folders
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-jose/go-jose/v3/jwt"
|
"github.com/go-jose/go-jose/v3/jwt"
|
||||||
@@ -11,16 +10,183 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/authlib/authn"
|
"github.com/grafana/authlib/authn"
|
||||||
"github.com/grafana/authlib/authz"
|
"github.com/grafana/authlib/authz"
|
||||||
authlib "github.com/grafana/authlib/types"
|
"github.com/grafana/authlib/types"
|
||||||
|
|
||||||
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||||
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFolderAuthorizer(t *testing.T) {
|
func TestLegacyAuthorizer(t *testing.T) {
|
||||||
|
type input struct {
|
||||||
|
user identity.Requester
|
||||||
|
verb string
|
||||||
|
}
|
||||||
|
type expect struct {
|
||||||
|
authorized authorizer.Decision
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
var orgID int64 = 1
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input input
|
||||||
|
expect expect
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "user with create permissions should be able to create a folder",
|
||||||
|
input: input{
|
||||||
|
user: &user.SignedInUser{
|
||||||
|
UserID: 1,
|
||||||
|
OrgID: orgID,
|
||||||
|
Name: "123",
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verb: string(utils.VerbCreate),
|
||||||
|
},
|
||||||
|
expect: expect{
|
||||||
|
authorized: authorizer.DecisionAllow,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not possible to create a folder without a user",
|
||||||
|
input: input{
|
||||||
|
user: nil,
|
||||||
|
verb: string(utils.VerbCreate),
|
||||||
|
},
|
||||||
|
expect: expect{authorized: authorizer.DecisionDeny},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user without permissions should not be able to create a folder",
|
||||||
|
input: input{
|
||||||
|
user: &user.SignedInUser{},
|
||||||
|
verb: string(utils.VerbCreate),
|
||||||
|
},
|
||||||
|
expect: expect{authorized: authorizer.DecisionDeny},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user in another orgId should not be able to create a folder ",
|
||||||
|
input: input{
|
||||||
|
user: &user.SignedInUser{
|
||||||
|
UserID: 1,
|
||||||
|
OrgID: 2,
|
||||||
|
Name: "123",
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verb: string(utils.VerbCreate),
|
||||||
|
},
|
||||||
|
expect: expect{authorized: authorizer.DecisionDeny},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user with read permissions should be able to list folders",
|
||||||
|
input: input{
|
||||||
|
user: &user.SignedInUser{
|
||||||
|
UserID: 1,
|
||||||
|
OrgID: orgID,
|
||||||
|
Name: "123",
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
orgID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verb: string(utils.VerbList),
|
||||||
|
},
|
||||||
|
expect: expect{authorized: authorizer.DecisionDeny},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user with delete permissions should be able to delete a folder",
|
||||||
|
input: input{
|
||||||
|
user: &user.SignedInUser{
|
||||||
|
UserID: 1,
|
||||||
|
OrgID: orgID,
|
||||||
|
Name: "123",
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
orgID: {dashboards.ActionFoldersDelete: {dashboards.ScopeFoldersAll}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verb: string(utils.VerbDelete),
|
||||||
|
},
|
||||||
|
expect: expect{authorized: authorizer.DecisionAllow},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user without delete permissions should NOT be able to delete a folder",
|
||||||
|
input: input{
|
||||||
|
user: &user.SignedInUser{
|
||||||
|
UserID: 1,
|
||||||
|
OrgID: orgID,
|
||||||
|
Name: "123",
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
orgID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verb: string(utils.VerbDelete),
|
||||||
|
},
|
||||||
|
expect: expect{authorized: authorizer.DecisionDeny},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user with write permissions should be able to update a folder",
|
||||||
|
input: input{
|
||||||
|
user: &user.SignedInUser{
|
||||||
|
UserID: 1,
|
||||||
|
OrgID: orgID,
|
||||||
|
Name: "123",
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
orgID: {dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verb: string(utils.VerbUpdate),
|
||||||
|
},
|
||||||
|
expect: expect{authorized: authorizer.DecisionAllow},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user without write permissions should NOT be able to update a folder",
|
||||||
|
input: input{
|
||||||
|
user: &user.SignedInUser{
|
||||||
|
UserID: 1,
|
||||||
|
OrgID: orgID,
|
||||||
|
Name: "123",
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
orgID: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verb: string(utils.VerbUpdate),
|
||||||
|
},
|
||||||
|
expect: expect{authorized: authorizer.DecisionDeny},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
authz := newLegacyAuthorizer(acimpl.ProvideAccessControl(featuremgmt.WithFeatures()))
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
authorized, _, err := authz.Authorize(
|
||||||
|
identity.WithRequester(context.Background(), tt.input.user),
|
||||||
|
authorizer.AttributesRecord{User: tt.input.user, Verb: tt.input.verb, Resource: "folders", ResourceRequest: true, Name: "123"},
|
||||||
|
)
|
||||||
|
if tt.expect.err != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, authorizer.DecisionDeny, authorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.expect.authorized, authorized)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiTenantAuthorizer(t *testing.T) {
|
||||||
type input struct {
|
type input struct {
|
||||||
verb string
|
verb string
|
||||||
info authlib.AuthInfo
|
info types.AuthInfo
|
||||||
client authlib.AccessChecker
|
client types.AccessClient
|
||||||
}
|
}
|
||||||
|
|
||||||
type expected struct {
|
type expected struct {
|
||||||
@@ -29,17 +195,21 @@ func TestFolderAuthorizer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input input
|
input input
|
||||||
expected expected
|
expeted expected
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing auth info",
|
name: "non access policy idenity should not be able to authorize",
|
||||||
input: input{
|
input: input{
|
||||||
verb: utils.VerbGet,
|
verb: utils.VerbGet,
|
||||||
client: authz.NewClient(nil),
|
info: &identity.StaticRequester{
|
||||||
|
Type: types.TypeUser,
|
||||||
|
UserID: 1,
|
||||||
|
UserUID: "1",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
expected: expected{
|
expeted: expected{
|
||||||
authorized: authorizer.DecisionDeny,
|
authorized: authorizer.DecisionDeny,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -60,7 +230,7 @@ func TestFolderAuthorizer(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
client: authz.NewClient(nil),
|
client: authz.NewClient(nil),
|
||||||
},
|
},
|
||||||
expected: expected{
|
expeted: expected{
|
||||||
authorized: authorizer.DecisionAllow,
|
authorized: authorizer.DecisionAllow,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -81,52 +251,28 @@ func TestFolderAuthorizer(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
client: authz.NewClient(nil),
|
client: authz.NewClient(nil),
|
||||||
},
|
},
|
||||||
expected: expected{
|
expeted: expected{
|
||||||
authorized: authorizer.DecisionDeny,
|
authorized: authorizer.DecisionDeny,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "with client error",
|
|
||||||
input: input{
|
|
||||||
verb: utils.VerbGet,
|
|
||||||
info: authn.NewIDTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{}, &authn.Claims[authn.IDTokenClaims]{}),
|
|
||||||
client: &dummyClient{
|
|
||||||
check: func(ctx context.Context, info authlib.AuthInfo, req authlib.CheckRequest) (authlib.CheckResponse, error) {
|
|
||||||
return authlib.CheckResponse{}, fmt.Errorf("nope")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: expected{
|
|
||||||
authorized: authorizer.DecisionDeny,
|
|
||||||
err: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
authz := newAuthorizer(tt.input.client)
|
authz := newMultiTenantAuthorizer(tt.input.client)
|
||||||
authorized, _, err := authz.Authorize(
|
authorized, _, err := authz.Authorize(
|
||||||
authlib.WithAuthInfo(context.Background(), tt.input.info),
|
types.WithAuthInfo(context.Background(), tt.input.info),
|
||||||
authorizer.AttributesRecord{User: tt.input.info, Verb: tt.input.verb, APIGroup: folders.GROUP, Resource: "folders", ResourceRequest: true, Name: "123", Namespace: "stacks-1"},
|
authorizer.AttributesRecord{User: tt.input.info, Verb: tt.input.verb, APIGroup: folders.GROUP, Resource: "folders", ResourceRequest: true, Name: "123", Namespace: "stacks-1"},
|
||||||
)
|
)
|
||||||
|
|
||||||
if tt.expected.err {
|
if tt.expeted.err {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, authorizer.DecisionDeny, authorized)
|
require.Equal(t, authorizer.DecisionDeny, authorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.expected.authorized, authorized)
|
require.Equal(t, tt.expeted.authorized, authorized)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type dummyClient struct {
|
|
||||||
check func(ctx context.Context, info authlib.AuthInfo, req authlib.CheckRequest) (authlib.CheckResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *dummyClient) Check(ctx context.Context, info authlib.AuthInfo, req authlib.CheckRequest) (authlib.CheckResponse, error) {
|
|
||||||
return c.check(ctx, info, req)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package folders
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ import (
|
|||||||
authlib "github.com/grafana/authlib/types"
|
authlib "github.com/grafana/authlib/types"
|
||||||
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||||
"github.com/grafana/grafana/apps/iam/pkg/reconcilers"
|
"github.com/grafana/grafana/apps/iam/pkg/reconcilers"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
@@ -38,6 +40,9 @@ var _ builder.APIGroupValidation = (*FolderAPIBuilder)(nil)
|
|||||||
|
|
||||||
var resourceInfo = folders.FolderResourceInfo
|
var resourceInfo = folders.FolderResourceInfo
|
||||||
|
|
||||||
|
var errNoUser = errors.New("valid user is required")
|
||||||
|
var errNoResource = errors.New("resource name is required")
|
||||||
|
|
||||||
// This is used just so wire has something unique to return
|
// This is used just so wire has something unique to return
|
||||||
type FolderAPIBuilder struct {
|
type FolderAPIBuilder struct {
|
||||||
features featuremgmt.FeatureToggles
|
features featuremgmt.FeatureToggles
|
||||||
@@ -77,7 +82,7 @@ func RegisterAPIService(cfg *setting.Cfg,
|
|||||||
acService: acService,
|
acService: acService,
|
||||||
ac: accessControl,
|
ac: accessControl,
|
||||||
permissionsOnCreate: cfg.RBAC.PermissionsOnCreation("folder"),
|
permissionsOnCreate: cfg.RBAC.PermissionsOnCreation("folder"),
|
||||||
authorizer: newAuthorizer(accessClient),
|
authorizer: newLegacyAuthorizer(accessControl),
|
||||||
searcher: unified,
|
searcher: unified,
|
||||||
permissionStore: reconcilers.NewZanzanaPermissionStore(zanzanaClient),
|
permissionStore: reconcilers.NewZanzanaPermissionStore(zanzanaClient),
|
||||||
}
|
}
|
||||||
@@ -87,7 +92,7 @@ func RegisterAPIService(cfg *setting.Cfg,
|
|||||||
|
|
||||||
func NewAPIService(ac authlib.AccessClient) *FolderAPIBuilder {
|
func NewAPIService(ac authlib.AccessClient) *FolderAPIBuilder {
|
||||||
return &FolderAPIBuilder{
|
return &FolderAPIBuilder{
|
||||||
authorizer: newAuthorizer(ac),
|
authorizer: newMultiTenantAuthorizer(ac),
|
||||||
ignoreLegacy: true,
|
ignoreLegacy: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,6 +216,11 @@ func (b *FolderAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAP
|
|||||||
return oas, nil
|
return oas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type authorizerParams struct {
|
||||||
|
user identity.Requester
|
||||||
|
evaluator accesscontrol.Evaluator
|
||||||
|
}
|
||||||
|
|
||||||
func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||||
return b.authorizer
|
return b.authorizer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package rbac
|
|||||||
|
|
||||||
import claims "github.com/grafana/authlib/types"
|
import claims "github.com/grafana/authlib/types"
|
||||||
|
|
||||||
type checkRequest struct {
|
type CheckRequest struct {
|
||||||
Namespace claims.NamespaceInfo
|
Namespace claims.NamespaceInfo
|
||||||
IdentityType claims.IdentityType
|
IdentityType claims.IdentityType
|
||||||
UserUID string
|
UserUID string
|
||||||
Action string // Verb has been mapped into an action
|
Action string
|
||||||
Group string
|
Group string
|
||||||
Resource string
|
Resource string
|
||||||
Verb string
|
Verb string
|
||||||
@@ -14,7 +14,7 @@ type checkRequest struct {
|
|||||||
ParentFolder string
|
ParentFolder string
|
||||||
}
|
}
|
||||||
|
|
||||||
type listRequest struct {
|
type ListRequest struct {
|
||||||
Namespace claims.NamespaceInfo
|
Namespace claims.NamespaceInfo
|
||||||
IdentityType claims.IdentityType
|
IdentityType claims.IdentityType
|
||||||
UserUID string
|
UserUID string
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ func (s *Service) List(ctx context.Context, req *authzv1.ListRequest) (*authzv1.
|
|||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) validateCheckRequest(ctx context.Context, req *authzv1.CheckRequest) (*checkRequest, error) {
|
func (s *Service) validateCheckRequest(ctx context.Context, req *authzv1.CheckRequest) (*CheckRequest, error) {
|
||||||
ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.validateCheckRequest")
|
ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.validateCheckRequest")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ func (s *Service) validateCheckRequest(ctx context.Context, req *authzv1.CheckRe
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
checkReq := &checkRequest{
|
checkReq := &CheckRequest{
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
UserUID: userUID,
|
UserUID: userUID,
|
||||||
IdentityType: idType,
|
IdentityType: idType,
|
||||||
@@ -261,7 +261,7 @@ func (s *Service) validateCheckRequest(ctx context.Context, req *authzv1.CheckRe
|
|||||||
return checkReq, nil
|
return checkReq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) validateListRequest(ctx context.Context, req *authzv1.ListRequest) (*listRequest, error) {
|
func (s *Service) validateListRequest(ctx context.Context, req *authzv1.ListRequest) (*ListRequest, error) {
|
||||||
ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.validateListRequest")
|
ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.validateListRequest")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ func (s *Service) validateListRequest(ctx context.Context, req *authzv1.ListRequ
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
listReq := &listRequest{
|
listReq := &ListRequest{
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
UserUID: userUID,
|
UserUID: userUID,
|
||||||
IdentityType: idType,
|
IdentityType: idType,
|
||||||
@@ -331,19 +331,18 @@ func (s *Service) validateSubject(ctx context.Context, subject string) (string,
|
|||||||
return userUID, identityType, nil
|
return userUID, identityType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the action for a selected verb
|
|
||||||
func (s *Service) validateAction(ctx context.Context, group, resource, verb string) (string, error) {
|
func (s *Service) validateAction(ctx context.Context, group, resource, verb string) (string, error) {
|
||||||
ctxLogger := s.logger.FromContext(ctx)
|
ctxLogger := s.logger.FromContext(ctx)
|
||||||
|
|
||||||
t, ok := s.mapper.Get(group, resource)
|
t, ok := s.mapper.Get(group, resource)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctxLogger.Error("unsupported resource", "group", group, "resource", resource)
|
ctxLogger.Error("unsupport resource", "group", group, "resource", resource)
|
||||||
return "", status.Error(codes.NotFound, "unsupported resource")
|
return "", status.Error(codes.NotFound, "unsupported resource")
|
||||||
}
|
}
|
||||||
|
|
||||||
action, ok := t.Action(verb)
|
action, ok := t.Action(verb)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctxLogger.Error("unsupported verb", "group", group, "resource", resource, "verb", verb)
|
ctxLogger.Error("unsupport verb", "group", group, "resource", resource, "verb", verb)
|
||||||
return "", status.Error(codes.NotFound, "unsupported verb")
|
return "", status.Error(codes.NotFound, "unsupported verb")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,7 +570,7 @@ func (s *Service) getUserBasicRole(ctx context.Context, ns types.NamespaceInfo,
|
|||||||
return *basicRole, nil
|
return *basicRole, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) checkPermission(ctx context.Context, scopeMap map[string]bool, req *checkRequest) (bool, error) {
|
func (s *Service) checkPermission(ctx context.Context, scopeMap map[string]bool, req *CheckRequest) (bool, error) {
|
||||||
ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.checkPermission", trace.WithAttributes(
|
ctx, span := s.tracer.Start(ctx, "authz_direct_db.service.checkPermission", trace.WithAttributes(
|
||||||
attribute.Int("scope_count", len(scopeMap))))
|
attribute.Int("scope_count", len(scopeMap))))
|
||||||
defer span.End()
|
defer span.End()
|
||||||
@@ -620,7 +619,7 @@ func getScopeMap(permissions []accesscontrol.Permission) map[string]bool {
|
|||||||
return permMap
|
return permMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) checkInheritedPermissions(ctx context.Context, scopeMap map[string]bool, req *checkRequest) (bool, error) {
|
func (s *Service) checkInheritedPermissions(ctx context.Context, scopeMap map[string]bool, req *CheckRequest) (bool, error) {
|
||||||
if req.ParentFolder == "" {
|
if req.ParentFolder == "" {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -697,7 +696,7 @@ func (s *Service) buildFolderTree(ctx context.Context, ns types.NamespaceInfo) (
|
|||||||
return res.(folderTree), nil
|
return res.(folderTree), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) listPermission(ctx context.Context, scopeMap map[string]bool, req *listRequest) (*authzv1.ListResponse, error) {
|
func (s *Service) listPermission(ctx context.Context, scopeMap map[string]bool, req *ListRequest) (*authzv1.ListResponse, error) {
|
||||||
if scopeMap["*"] {
|
if scopeMap["*"] {
|
||||||
return &authzv1.ListResponse{All: true}, nil
|
return &authzv1.ListResponse{All: true}, nil
|
||||||
}
|
}
|
||||||
@@ -708,7 +707,7 @@ func (s *Service) listPermission(ctx context.Context, scopeMap map[string]bool,
|
|||||||
|
|
||||||
t, ok := s.mapper.Get(req.Group, req.Resource)
|
t, ok := s.mapper.Get(req.Group, req.Resource)
|
||||||
if !ok {
|
if !ok {
|
||||||
ctxLogger.Error("unsupported resource", "group", req.Group, "resource", req.Resource)
|
ctxLogger.Error("unsupport resource", "group", req.Group, "resource", req.Resource)
|
||||||
return nil, status.Error(codes.NotFound, "unsupported resource")
|
return nil, status.Error(codes.NotFound, "unsupported resource")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
authzv1 "github.com/grafana/authlib/authz/proto/v1"
|
||||||
"github.com/grafana/authlib/cache"
|
"github.com/grafana/authlib/cache"
|
||||||
"github.com/grafana/authlib/types"
|
"github.com/grafana/authlib/types"
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
@@ -30,7 +30,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
permissions []accesscontrol.Permission
|
permissions []accesscontrol.Permission
|
||||||
check checkRequest
|
check CheckRequest
|
||||||
folders []store.Folder
|
folders []store.Folder
|
||||||
expected bool
|
expected bool
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
Identifier: "some_dashboard",
|
Identifier: "some_dashboard",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -66,7 +66,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
Identifier: "another_dashboard",
|
Identifier: "another_dashboard",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -85,7 +85,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
Identifier: "*",
|
Identifier: "*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -103,7 +103,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
Attribute: "*",
|
Attribute: "*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -120,7 +120,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
Kind: "*",
|
Kind: "*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -139,7 +139,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
Identifier: "general",
|
Identifier: "general",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:create",
|
Action: "dashboards:create",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -154,7 +154,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
Action: "dashboards:create",
|
Action: "dashboards:create",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:create",
|
Action: "dashboards:create",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -165,7 +165,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "should return false if user has no permissions on resource",
|
name: "should return false if user has no permissions on resource",
|
||||||
permissions: []accesscontrol.Permission{},
|
permissions: []accesscontrol.Permission{},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -187,7 +187,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
{UID: "parent"},
|
{UID: "parent"},
|
||||||
{UID: "child", ParentUID: strPtr("parent")},
|
{UID: "child", ParentUID: strPtr("parent")},
|
||||||
},
|
},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -208,7 +208,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
folders: []store.Folder{{UID: "parent"}},
|
folders: []store.Folder{{UID: "parent"}},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:create",
|
Action: "dashboards:create",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -230,7 +230,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
folders: []store.Folder{{UID: "parent"}, {UID: "other_parent"}},
|
folders: []store.Folder{{UID: "parent"}, {UID: "other_parent"}},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:create",
|
Action: "dashboards:create",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -252,7 +252,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
folders: []store.Folder{{UID: "parent"}},
|
folders: []store.Folder{{UID: "parent"}},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -273,7 +273,7 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
Identifier: "some_datasource",
|
Identifier: "some_datasource",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
check: checkRequest{
|
check: CheckRequest{
|
||||||
Action: "datasources:query",
|
Action: "datasources:query",
|
||||||
Group: "query.grafana.app",
|
Group: "query.grafana.app",
|
||||||
Resource: "query",
|
Resource: "query",
|
||||||
@@ -297,75 +297,6 @@ func TestService_checkPermission(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestService_mapping(t *testing.T) {
|
|
||||||
type testCase struct {
|
|
||||||
name string
|
|
||||||
input *authzv1.CheckRequest
|
|
||||||
output *checkRequest
|
|
||||||
err string
|
|
||||||
}
|
|
||||||
|
|
||||||
ns := "default"
|
|
||||||
testUserA := &identity.StaticRequester{
|
|
||||||
Type: types.TypeUser,
|
|
||||||
Login: "test",
|
|
||||||
UserID: 123,
|
|
||||||
UserUID: "u123",
|
|
||||||
OrgRole: identity.RoleAdmin,
|
|
||||||
IsGrafanaAdmin: true, // can do anything
|
|
||||||
Namespace: ns,
|
|
||||||
OrgID: 1,
|
|
||||||
}
|
|
||||||
ctx := types.WithAuthInfo(request.WithNamespace(context.Background(), ns), testUserA)
|
|
||||||
|
|
||||||
testCases := []testCase{
|
|
||||||
{
|
|
||||||
name: "should return true if user has permission",
|
|
||||||
input: &authzv1.CheckRequest{
|
|
||||||
Group: "folder.grafana.app",
|
|
||||||
Resource: "folders",
|
|
||||||
Name: "aaa",
|
|
||||||
Verb: utils.VerbCreate,
|
|
||||||
Folder: "folder",
|
|
||||||
},
|
|
||||||
output: &checkRequest{
|
|
||||||
Action: "folders:create",
|
|
||||||
Group: "folder.grafana.app",
|
|
||||||
Resource: "folders",
|
|
||||||
Name: "aaa",
|
|
||||||
Verb: "create",
|
|
||||||
ParentFolder: "folder",
|
|
||||||
Namespace: types.NamespaceInfo{
|
|
||||||
Value: ns,
|
|
||||||
OrgID: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
s := setupService()
|
|
||||||
tc.input.Namespace = ns
|
|
||||||
tc.input.Subject = testUserA.GetUID() // the subject string
|
|
||||||
|
|
||||||
got, err := s.validateCheckRequest(ctx, tc.input)
|
|
||||||
if tc.err != "" {
|
|
||||||
require.Error(t, err)
|
|
||||||
require.ErrorContains(t, err, tc.err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, got)
|
|
||||||
|
|
||||||
tc.output.IdentityType = types.TypeUser
|
|
||||||
tc.output.UserUID = testUserA.GetIdentifier()
|
|
||||||
|
|
||||||
require.Equal(t, tc.output, got)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestService_checkPermission_folderCacheMissRecovery(t *testing.T) {
|
func TestService_checkPermission_folderCacheMissRecovery(t *testing.T) {
|
||||||
s := setupService()
|
s := setupService()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@@ -386,7 +317,7 @@ func TestService_checkPermission_folderCacheMissRecovery(t *testing.T) {
|
|||||||
s.folderCache.Set(ctx, folderCacheKey("default"), newFolderTree([]store.Folder{{UID: "root"}}))
|
s.folderCache.Set(ctx, folderCacheKey("default"), newFolderTree([]store.Folder{{UID: "root"}}))
|
||||||
|
|
||||||
// Perform check on sub folder
|
// Perform check on sub folder
|
||||||
check := checkRequest{
|
check := CheckRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -628,7 +559,7 @@ func TestService_listPermission(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
permissions []accesscontrol.Permission
|
permissions []accesscontrol.Permission
|
||||||
folders []store.Folder
|
folders []store.Folder
|
||||||
list listRequest
|
list ListRequest
|
||||||
expectedItems []string
|
expectedItems []string
|
||||||
expectedFolders []string
|
expectedFolders []string
|
||||||
expectedAll bool
|
expectedAll bool
|
||||||
@@ -644,7 +575,7 @@ func TestService_listPermission(t *testing.T) {
|
|||||||
Kind: "*",
|
Kind: "*",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: listRequest{
|
list: ListRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -680,7 +611,7 @@ func TestService_listPermission(t *testing.T) {
|
|||||||
{UID: "some_folder_1"},
|
{UID: "some_folder_1"},
|
||||||
{UID: "some_folder_2"},
|
{UID: "some_folder_2"},
|
||||||
},
|
},
|
||||||
list: listRequest{
|
list: ListRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -707,7 +638,7 @@ func TestService_listPermission(t *testing.T) {
|
|||||||
{UID: "some_folder_subsubchild", ParentUID: strPtr("some_folder_subchild2")},
|
{UID: "some_folder_subsubchild", ParentUID: strPtr("some_folder_subchild2")},
|
||||||
{UID: "some_folder_1", ParentUID: strPtr("some_other_folder")},
|
{UID: "some_folder_1", ParentUID: strPtr("some_other_folder")},
|
||||||
},
|
},
|
||||||
list: listRequest{
|
list: ListRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -736,7 +667,7 @@ func TestService_listPermission(t *testing.T) {
|
|||||||
{UID: "some_folder_parent"},
|
{UID: "some_folder_parent"},
|
||||||
{UID: "some_folder_child", ParentUID: strPtr("some_folder_parent")},
|
{UID: "some_folder_child", ParentUID: strPtr("some_folder_parent")},
|
||||||
},
|
},
|
||||||
list: listRequest{
|
list: ListRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -768,7 +699,7 @@ func TestService_listPermission(t *testing.T) {
|
|||||||
{UID: "some_folder_subchild", ParentUID: strPtr("some_folder_child")},
|
{UID: "some_folder_subchild", ParentUID: strPtr("some_folder_child")},
|
||||||
{UID: "some_folder_child2", ParentUID: strPtr("some_folder_parent")},
|
{UID: "some_folder_child2", ParentUID: strPtr("some_folder_parent")},
|
||||||
},
|
},
|
||||||
list: listRequest{
|
list: ListRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -782,7 +713,7 @@ func TestService_listPermission(t *testing.T) {
|
|||||||
folders: []store.Folder{
|
folders: []store.Folder{
|
||||||
{UID: "some_folder_1"},
|
{UID: "some_folder_1"},
|
||||||
},
|
},
|
||||||
list: listRequest{
|
list: ListRequest{
|
||||||
Action: "dashboards:read",
|
Action: "dashboards:read",
|
||||||
Group: "dashboard.grafana.app",
|
Group: "dashboard.grafana.app",
|
||||||
Resource: "dashboards",
|
Resource: "dashboards",
|
||||||
@@ -803,7 +734,7 @@ func TestService_listPermission(t *testing.T) {
|
|||||||
{UID: "some_folder_parent"},
|
{UID: "some_folder_parent"},
|
||||||
{UID: "some_folder_child", ParentUID: strPtr("some_folder_parent")},
|
{UID: "some_folder_child", ParentUID: strPtr("some_folder_parent")},
|
||||||
},
|
},
|
||||||
list: listRequest{
|
list: ListRequest{
|
||||||
Action: "folders:read",
|
Action: "folders:read",
|
||||||
Group: "folder.grafana.app",
|
Group: "folder.grafana.app",
|
||||||
Resource: "folders",
|
Resource: "folders",
|
||||||
|
|||||||
Reference in New Issue
Block a user