Files
grafana/pkg/services/authz/rbac/resolver_test.go
T
Gabriel MABILLE 4b217c601a AuthZ: Scope resolution (#107948)
* AuthZ: Scope resolution

* Account for PR feedback

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
2025-07-17 14:34:10 +02:00

164 lines
3.8 KiB
Go

package rbac
import (
"context"
"testing"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/services/team"
"github.com/stretchr/testify/require"
)
func TestService_resolveScopeMap(t *testing.T) {
tests := []struct {
name string
scopeMap map[string]bool
ns types.NamespaceInfo
cache map[string]map[int64]string // Namespace: team ID -> UID cache
store []team.Team
want map[string]bool
}{
{
name: "Should resolve team IDs to team UIDs",
ns: types.NamespaceInfo{Value: "org-2", OrgID: 2},
scopeMap: map[string]bool{
"teams:id:1": true,
"teams:id:2": true,
},
store: []team.Team{
{ID: 1, UID: "t1"},
{ID: 2, UID: "t2"},
},
want: map[string]bool{
"teams:uid:t1": true,
"teams:uid:t2": true,
},
},
{
name: "Should use cache",
ns: types.NamespaceInfo{Value: "org-2", OrgID: 2},
scopeMap: map[string]bool{
"teams:id:1": true,
"teams:id:2": true,
},
cache: map[string]map[int64]string{
"org-2": {1: "t1", 2: "t2"},
},
want: map[string]bool{
"teams:uid:t1": true,
"teams:uid:t2": true,
},
},
{
name: "Shouldn't use cache from another namespace",
ns: types.NamespaceInfo{Value: "org-2", OrgID: 2},
scopeMap: map[string]bool{
"teams:id:1": true,
},
cache: map[string]map[int64]string{
"org-31": {1: "org31-t1"},
},
store: []team.Team{{ID: 1, UID: "org2-t1"}},
want: map[string]bool{
"teams:uid:org2-t1": true,
},
},
{
name: "Should handle wildcard",
ns: types.NamespaceInfo{Value: "org-2", OrgID: 2},
scopeMap: map[string]bool{"teams:id:*": true},
store: []team.Team{{ID: 1, UID: "t1"}, {ID: 2, UID: "t2"}},
want: map[string]bool{"teams:uid:*": true},
},
{
name: "Should skip short scopes",
ns: types.NamespaceInfo{Value: "org-2", OrgID: 2},
scopeMap: map[string]bool{
"teams:short": true,
"teams:*": true,
"*": true,
"teams:id:1": true,
},
store: []team.Team{{ID: 1, UID: "t1"}},
want: map[string]bool{
"teams:short": true,
"teams:uid:t1": true,
"teams:*": true,
"*": true,
},
},
{
name: "Should gracefully handle invalid scopes",
ns: types.NamespaceInfo{Value: "org-2", OrgID: 2},
scopeMap: map[string]bool{
"teams:id:": true,
"teams:id:NaN": true,
"teams:id:1": true,
},
store: []team.Team{{ID: 1, UID: "t1"}},
want: map[string]bool{
"teams:id:": true,
"teams:id:NaN": true,
"teams:uid:t1": true,
},
},
{
name: "Should recover from stale cache",
ns: types.NamespaceInfo{Value: "org-2", OrgID: 2},
scopeMap: map[string]bool{
"teams:id:1": true,
"teams:id:2": true,
"teams:id:3": true,
},
cache: map[string]map[int64]string{
"org-2": {1: "t1"},
},
store: []team.Team{
{ID: 1, UID: "t1"},
{ID: 2, UID: "t2"},
{ID: 3, UID: "t3"},
},
want: map[string]bool{
"teams:uid:t1": true,
"teams:uid:t2": true,
"teams:uid:t3": true,
},
},
{
name: "Should skip unknown team IDs",
ns: types.NamespaceInfo{Value: "org-2", OrgID: 2},
scopeMap: map[string]bool{
"teams:id:1": true,
},
store: []team.Team{
{ID: 2, UID: "t2"},
},
want: map[string]bool{
"teams:id:1": true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := setupService()
s.identityStore = &fakeIdentityStore{
disableNsCheck: true,
teams: tt.store,
}
if tt.cache != nil {
for ns, cache := range tt.cache {
s.teamIDCache.Set(context.Background(), teamIDsCacheKey(ns), cache)
}
}
got, err := s.resolveScopeMap(context.Background(), tt.ns, tt.scopeMap)
require.NoError(t, err)
require.Len(t, got, len(tt.want))
for scope := range tt.want {
_, ok := got[scope]
require.True(t, ok)
}
})
}
}