|
|
|
@ -897,6 +897,299 @@ func TestService_Check(t *testing.T) { |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestService_List(t *testing.T) { |
|
|
|
|
callingService := authn.NewAccessTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{ |
|
|
|
|
Claims: jwt.Claims{ |
|
|
|
|
Subject: types.NewTypeID(types.TypeAccessPolicy, "some-service"), |
|
|
|
|
Audience: []string{"authzservice"}, |
|
|
|
|
}, |
|
|
|
|
Rest: authn.AccessTokenClaims{Namespace: "org-12"}, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
type testCase struct { |
|
|
|
|
name string |
|
|
|
|
req *authzv1.ListRequest |
|
|
|
|
permissions []accesscontrol.Permission |
|
|
|
|
expected *authzv1.ListResponse |
|
|
|
|
expectErr bool |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
t.Run("Require auth info", func(t *testing.T) { |
|
|
|
|
s := setupService() |
|
|
|
|
ctx := context.Background() |
|
|
|
|
_, err := s.List(ctx, &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "user:test-uid", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}) |
|
|
|
|
require.Error(t, err) |
|
|
|
|
require.Contains(t, err.Error(), "could not get auth info") |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
testCases := []testCase{ |
|
|
|
|
{ |
|
|
|
|
name: "should error if no namespace is provided", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "", |
|
|
|
|
Subject: "user:test-uid", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
expectErr: true, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "should error if caller namespace does not match request namespace", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-13", |
|
|
|
|
Subject: "user:test-uid", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
expectErr: true, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "should error if no subject is provided", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
expectErr: true, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "should error if an unsupported subject type is provided", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "api-key:12", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
expectErr: true, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "should error if an invalid subject is provided", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "invalid:12", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
expectErr: true, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "should error if an unknown group is provided", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "user:test-uid", |
|
|
|
|
Group: "unknown.grafana.app", |
|
|
|
|
Resource: "unknown", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
expectErr: true, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "should error if an unknown verb is provided", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "user:test-uid", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "unknown", |
|
|
|
|
}, |
|
|
|
|
expectErr: true, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
t.Run("Request validation", func(t *testing.T) { |
|
|
|
|
for _, tc := range testCases { |
|
|
|
|
t.Run(tc.name, func(t *testing.T) { |
|
|
|
|
s := setupService() |
|
|
|
|
ctx := types.WithAuthInfo(context.Background(), callingService) |
|
|
|
|
userID := &store.UserIdentifiers{UID: "test-uid", ID: 1} |
|
|
|
|
store := &fakeStore{ |
|
|
|
|
userID: userID, |
|
|
|
|
userPermissions: tc.permissions, |
|
|
|
|
} |
|
|
|
|
s.store = store |
|
|
|
|
s.permissionStore = store |
|
|
|
|
s.identityStore = &fakeIdentityStore{teams: []int64{1, 2}} |
|
|
|
|
|
|
|
|
|
_, err := s.List(ctx, tc.req) |
|
|
|
|
require.Error(t, err) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
testCases = []testCase{ |
|
|
|
|
{ |
|
|
|
|
name: "should list permissions for user with permission", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "user:test-uid", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
permissions: []accesscontrol.Permission{ |
|
|
|
|
{Action: "dashboards:read", Scope: "dashboards:uid:dash1"}, |
|
|
|
|
{Action: "dashboards:read", Scope: "dashboards:uid:dash2"}, |
|
|
|
|
{Action: "dashboards:read", Scope: "folders:uid:fold1"}, |
|
|
|
|
}, |
|
|
|
|
expected: &authzv1.ListResponse{ |
|
|
|
|
Items: []string{"dash1", "dash2"}, |
|
|
|
|
Folders: []string{"fold1"}, |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "should return empty list for user without permission", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "user:test-uid", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
permissions: []accesscontrol.Permission{}, |
|
|
|
|
expected: &authzv1.ListResponse{}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
t.Run("User permission list", func(t *testing.T) { |
|
|
|
|
for _, tc := range testCases { |
|
|
|
|
t.Run(tc.name, func(t *testing.T) { |
|
|
|
|
s := setupService() |
|
|
|
|
ctx := types.WithAuthInfo(context.Background(), callingService) |
|
|
|
|
userID := &store.UserIdentifiers{UID: "test-uid", ID: 1} |
|
|
|
|
store := &fakeStore{ |
|
|
|
|
userID: userID, |
|
|
|
|
userPermissions: tc.permissions, |
|
|
|
|
} |
|
|
|
|
s.store = store |
|
|
|
|
s.permissionStore = store |
|
|
|
|
s.identityStore = &fakeIdentityStore{teams: []int64{1, 2}} |
|
|
|
|
|
|
|
|
|
resp, err := s.List(ctx, tc.req) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
require.ElementsMatch(t, resp.Items, tc.expected.Items) |
|
|
|
|
require.ElementsMatch(t, resp.Folders, tc.expected.Folders) |
|
|
|
|
|
|
|
|
|
// Check cache
|
|
|
|
|
id, ok := s.idCache.Get(ctx, userIdentifierCacheKey("org-12", "test-uid")) |
|
|
|
|
require.True(t, ok) |
|
|
|
|
require.Equal(t, id.UID, "test-uid") |
|
|
|
|
perms, ok := s.permCache.Get(ctx, userPermCacheKey("org-12", "test-uid", "dashboards:read")) |
|
|
|
|
require.True(t, ok) |
|
|
|
|
require.Len(t, perms, len(tc.expected.Items)+len(tc.expected.Folders)) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
testCases = []testCase{ |
|
|
|
|
{ |
|
|
|
|
name: "should list permissions for anonymous with permission", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "anonymous:0", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
permissions: []accesscontrol.Permission{ |
|
|
|
|
{Action: "dashboards:read", Scope: "dashboards:uid:dash1"}, |
|
|
|
|
{Action: "dashboards:read", Scope: "dashboards:uid:dash2"}, |
|
|
|
|
{Action: "dashboards:read", Scope: "folders:uid:fold1"}, |
|
|
|
|
}, |
|
|
|
|
expected: &authzv1.ListResponse{ |
|
|
|
|
Items: []string{"dash1", "dash2"}, |
|
|
|
|
Folders: []string{"fold1"}, |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "should return empty list for anonymous without permission", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "anonymous:0", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
permissions: []accesscontrol.Permission{}, |
|
|
|
|
expected: &authzv1.ListResponse{}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
t.Run("Anonymous permission list", func(t *testing.T) { |
|
|
|
|
for _, tc := range testCases { |
|
|
|
|
t.Run(tc.name, func(t *testing.T) { |
|
|
|
|
s := setupService() |
|
|
|
|
ctx := types.WithAuthInfo(context.Background(), callingService) |
|
|
|
|
store := &fakeStore{userPermissions: tc.permissions} |
|
|
|
|
s.store = store |
|
|
|
|
s.permissionStore = store |
|
|
|
|
s.identityStore = &fakeIdentityStore{teams: []int64{1, 2}} |
|
|
|
|
|
|
|
|
|
resp, err := s.List(ctx, tc.req) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
require.ElementsMatch(t, resp.Items, tc.expected.Items) |
|
|
|
|
require.ElementsMatch(t, resp.Folders, tc.expected.Folders) |
|
|
|
|
|
|
|
|
|
// Check cache
|
|
|
|
|
perms, ok := s.permCache.Get(ctx, anonymousPermCacheKey("org-12", "dashboards:read")) |
|
|
|
|
require.True(t, ok) |
|
|
|
|
require.Len(t, perms, len(tc.expected.Items)+len(tc.expected.Folders)) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
testCases = []testCase{ |
|
|
|
|
{ |
|
|
|
|
name: "should list permissions for rendering", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "render:0", |
|
|
|
|
Group: "dashboard.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
expected: &authzv1.ListResponse{ |
|
|
|
|
All: true, |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "should deny rendering access to another app resources", |
|
|
|
|
req: &authzv1.ListRequest{ |
|
|
|
|
Namespace: "org-12", |
|
|
|
|
Subject: "render:0", |
|
|
|
|
Group: "another.grafana.app", |
|
|
|
|
Resource: "dashboards", |
|
|
|
|
Verb: "get", |
|
|
|
|
}, |
|
|
|
|
expectErr: true, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
t.Run("Rendering permission list", func(t *testing.T) { |
|
|
|
|
for _, tc := range testCases { |
|
|
|
|
t.Run(tc.name, func(t *testing.T) { |
|
|
|
|
s := setupService() |
|
|
|
|
ctx := types.WithAuthInfo(context.Background(), callingService) |
|
|
|
|
|
|
|
|
|
resp, err := s.List(ctx, tc.req) |
|
|
|
|
if tc.expectErr { |
|
|
|
|
require.Error(t, err) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
assert.Equal(t, tc.expected.All, resp.All) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func setupService() *Service { |
|
|
|
|
cache := cache.NewLocalCache(cache.Config{Expiry: 5 * time.Minute, CleanupInterval: 5 * time.Minute}) |
|
|
|
|
logger := log.New("authz-rbac-service") |
|
|
|
|