mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Authn: Identity resolvers (#85930)
* AuthN: Add NamespaceID struct. We should replace the usage of encoded namespaceID with this one * AuthN: Add optional interface that clients can implement to be able to resolve identity for a namespace * Authn: Implement IdentityResolverClient for api keys * AuthN: use idenity resolvers Co-authored-by: Misi <mgyongyosi@users.noreply.github.com>
This commit is contained in:
@@ -175,7 +175,9 @@ func HasGlobalAccess(ac AccessControl, authnService authn.Service, c *contextmod
|
||||
var targetOrgID int64 = GlobalOrgID
|
||||
orgUser, err := authnService.ResolveIdentity(c.Req.Context(), targetOrgID, c.SignedInUser.GetID())
|
||||
if err != nil {
|
||||
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
|
||||
// This will be an common error for entities that can't authenticate in global scope
|
||||
c.Logger.Debug("Failed to authenticate user in global scope", "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
hasAccess, err := ac.Evaluate(c.Req.Context(), orgUser, evaluator)
|
||||
|
||||
@@ -108,7 +108,7 @@ type Client interface {
|
||||
}
|
||||
|
||||
// ContextAwareClient is an optional interface that auth client can implement.
|
||||
// Clients that implements this interface will be tried during request authentication
|
||||
// Clients that implements this interface will be tried during request authentication.
|
||||
type ContextAwareClient interface {
|
||||
Client
|
||||
// Test should return true if client can be used to authenticate request
|
||||
@@ -127,7 +127,7 @@ type HookClient interface {
|
||||
|
||||
// RedirectClient is an optional interface that auth clients can implement.
|
||||
// Clients that implements this interface can be used to generate redirect urls
|
||||
// for authentication flows, e.g. oauth clients
|
||||
// for authentication flows, e.g. oauth clients.
|
||||
type RedirectClient interface {
|
||||
Client
|
||||
RedirectURL(ctx context.Context, r *Request) (*Redirect, error)
|
||||
@@ -150,12 +150,20 @@ type ProxyClient interface {
|
||||
}
|
||||
|
||||
// UsageStatClient is an optional interface that auth clients can implement.
|
||||
// Clients that implements this interface can specify a usage stat collection hook
|
||||
// Clients that implements this interface can specify a usage stat collection hook.
|
||||
type UsageStatClient interface {
|
||||
Client
|
||||
UsageStatFn(ctx context.Context) (map[string]any, error)
|
||||
}
|
||||
|
||||
// IdentityResolverClient is an optional interface that auth clients can implement.
|
||||
// Clients that implements this interface can resolve an full identity from an orgID and namespaceID.
|
||||
type IdentityResolverClient interface {
|
||||
Client
|
||||
Namespace() string
|
||||
ResolveIdentity(ctx context.Context, orgID int64, namespaceID NamespaceID) (*Identity, error)
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// OrgID will be populated by authn.Service
|
||||
OrgID int64
|
||||
|
||||
@@ -53,6 +53,7 @@ func ProvideService(
|
||||
cfg: cfg,
|
||||
clients: make(map[string]authn.Client),
|
||||
clientQueue: newQueue[authn.ContextAwareClient](),
|
||||
idenityResolverClients: make(map[string]authn.IdentityResolverClient),
|
||||
tracer: tracer,
|
||||
metrics: newMetrics(registerer),
|
||||
sessionService: sessionService,
|
||||
@@ -71,6 +72,8 @@ type Service struct {
|
||||
clients map[string]authn.Client
|
||||
clientQueue *queue[authn.ContextAwareClient]
|
||||
|
||||
idenityResolverClients map[string]authn.IdentityResolverClient
|
||||
|
||||
tracer tracing.Tracer
|
||||
metrics *metrics
|
||||
|
||||
@@ -292,19 +295,29 @@ func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID
|
||||
// hack to not update last seen
|
||||
r.SetMeta(authn.MetaKeyIsLogin, "true")
|
||||
|
||||
identity, err := s.authenticate(ctx, clients.ProvideIdentity(namespaceID), r)
|
||||
id, err := authn.ParseNamespaceID(namespaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
identity, err := s.resolveIdenity(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.authenticate(ctx, clients.ProvideIdentity(identity), r)
|
||||
}
|
||||
|
||||
func (s *Service) RegisterClient(c authn.Client) {
|
||||
s.clients[c.Name()] = c
|
||||
|
||||
if cac, ok := c.(authn.ContextAwareClient); ok {
|
||||
s.clientQueue.insert(cac, cac.Priority())
|
||||
}
|
||||
|
||||
if rc, ok := c.(authn.IdentityResolverClient); ok {
|
||||
s.idenityResolverClients[rc.Namespace()] = rc
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) error {
|
||||
@@ -314,6 +327,35 @@ func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) er
|
||||
return s.runPostAuthHooks(ctx, identity, r)
|
||||
}
|
||||
|
||||
func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
|
||||
if namespaceID.IsNamespace(authn.NamespaceUser) {
|
||||
return &authn.Identity{
|
||||
OrgID: orgID,
|
||||
ID: namespaceID.String(),
|
||||
ClientParams: authn.ClientParams{
|
||||
AllowGlobalOrg: true,
|
||||
FetchSyncedUser: true,
|
||||
SyncPermissions: true,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
if namespaceID.IsNamespace(authn.NamespaceServiceAccount) {
|
||||
return &authn.Identity{
|
||||
ID: namespaceID.String(),
|
||||
OrgID: orgID,
|
||||
ClientParams: authn.ClientParams{
|
||||
FetchSyncedUser: true,
|
||||
SyncPermissions: true,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
resolver, ok := s.idenityResolverClients[namespaceID.Namespace()]
|
||||
if !ok {
|
||||
return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", namespaceID.Namespace())
|
||||
}
|
||||
return resolver.ResolveIdentity(ctx, orgID, namespaceID)
|
||||
}
|
||||
|
||||
func (s *Service) errorLogFunc(ctx context.Context, err error) func(msg string, ctx ...any) {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return func(msg string, ctx ...any) {}
|
||||
|
||||
@@ -383,6 +383,49 @@ func TestService_Logout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_ResolveIdentity(t *testing.T) {
|
||||
t.Run("should return error for for unknown namespace", func(t *testing.T) {
|
||||
svc := setupTests(t)
|
||||
_, err := svc.ResolveIdentity(context.Background(), 1, "some:1")
|
||||
assert.ErrorIs(t, err, authn.ErrInvalidNamepsaceID)
|
||||
})
|
||||
|
||||
t.Run("should return error for for namespace that don't have a resolver", func(t *testing.T) {
|
||||
svc := setupTests(t)
|
||||
_, err := svc.ResolveIdentity(context.Background(), 1, "api-key:1")
|
||||
assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity)
|
||||
})
|
||||
|
||||
t.Run("should resolve for user", func(t *testing.T) {
|
||||
svc := setupTests(t)
|
||||
identity, err := svc.ResolveIdentity(context.Background(), 1, "user:1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, identity)
|
||||
})
|
||||
|
||||
t.Run("should resolve for service account", func(t *testing.T) {
|
||||
svc := setupTests(t)
|
||||
identity, err := svc.ResolveIdentity(context.Background(), 1, "service-account:1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, identity)
|
||||
})
|
||||
|
||||
t.Run("should resolve for valid namespace if client is registered", func(t *testing.T) {
|
||||
svc := setupTests(t, func(svc *Service) {
|
||||
svc.RegisterClient(&authntest.MockClient{
|
||||
NamespaceFunc: func() string { return authn.NamespaceAPIKey },
|
||||
ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
|
||||
return &authn.Identity{}, nil
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
identity, err := svc.ResolveIdentity(context.Background(), 1, "api-key:1")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, identity)
|
||||
})
|
||||
}
|
||||
|
||||
func mustParseURL(s string) *url.URL {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
@@ -397,8 +440,9 @@ func setupTests(t *testing.T, opts ...func(svc *Service)) *Service {
|
||||
s := &Service{
|
||||
log: log.NewNopLogger(),
|
||||
cfg: setting.NewCfg(),
|
||||
clients: map[string]authn.Client{},
|
||||
clients: make(map[string]authn.Client),
|
||||
clientQueue: newQueue[authn.ContextAwareClient](),
|
||||
idenityResolverClients: make(map[string]authn.IdentityResolverClient),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
metrics: newMetrics(nil),
|
||||
postAuthHooks: newQueue[authn.PostAuthHookFn](),
|
||||
|
||||
@@ -60,6 +60,7 @@ func (m *MockService) SyncIdentity(ctx context.Context, identity *authn.Identity
|
||||
var _ authn.HookClient = new(MockClient)
|
||||
var _ authn.LogoutClient = new(MockClient)
|
||||
var _ authn.ContextAwareClient = new(MockClient)
|
||||
var _ authn.IdentityResolverClient = new(MockClient)
|
||||
|
||||
type MockClient struct {
|
||||
NameFunc func() string
|
||||
@@ -68,6 +69,8 @@ type MockClient struct {
|
||||
PriorityFunc func() uint
|
||||
HookFunc func(ctx context.Context, identity *authn.Identity, r *authn.Request) error
|
||||
LogoutFunc func(ctx context.Context, user identity.Requester) (*authn.Redirect, bool)
|
||||
NamespaceFunc func() string
|
||||
ResolveIdentityFunc func(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error)
|
||||
}
|
||||
|
||||
func (m MockClient) Name() string {
|
||||
@@ -112,6 +115,21 @@ func (m *MockClient) Logout(ctx context.Context, user identity.Requester) (*auth
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (m *MockClient) Namespace() string {
|
||||
if m.NamespaceFunc != nil {
|
||||
return m.NamespaceFunc()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ResolveIdentity implements authn.IdentityResolverClient.
|
||||
func (m *MockClient) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
|
||||
if m.ResolveIdentityFunc != nil {
|
||||
return m.ResolveIdentityFunc(ctx, orgID, namespaceID)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var _ authn.ProxyClient = new(MockProxyClient)
|
||||
|
||||
type MockProxyClient struct {
|
||||
|
||||
@@ -27,6 +27,7 @@ var (
|
||||
|
||||
var _ authn.HookClient = new(APIKey)
|
||||
var _ authn.ContextAwareClient = new(APIKey)
|
||||
var _ authn.IdentityResolverClient = new(APIKey)
|
||||
|
||||
func ProvideAPIKey(apiKeyService apikey.Service) *APIKey {
|
||||
return &APIKey{
|
||||
@@ -45,7 +46,7 @@ func (s *APIKey) Name() string {
|
||||
}
|
||||
|
||||
func (s *APIKey) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
|
||||
apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r))
|
||||
key, err := s.getAPIKey(ctx, getTokenFromRequest(r))
|
||||
if err != nil {
|
||||
if errors.Is(err, apikeygen.ErrInvalidApiKey) {
|
||||
return nil, errAPIKeyInvalid.Errorf("API key is invalid")
|
||||
@@ -53,37 +54,20 @@ func (s *APIKey) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if apiKey.Expires != nil && *apiKey.Expires <= time.Now().Unix() {
|
||||
return nil, errAPIKeyExpired.Errorf("API key has expired")
|
||||
}
|
||||
|
||||
if apiKey.IsRevoked != nil && *apiKey.IsRevoked {
|
||||
return nil, errAPIKeyRevoked.Errorf("Api key is revoked")
|
||||
}
|
||||
|
||||
if r.OrgID == 0 {
|
||||
r.OrgID = apiKey.OrgID
|
||||
} else if r.OrgID != apiKey.OrgID {
|
||||
return nil, errAPIKeyOrgMismatch.Errorf("API does not belong in Organization %v", r.OrgID)
|
||||
r.OrgID = key.OrgID
|
||||
}
|
||||
|
||||
if err := validateApiKey(r.OrgID, key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if the api key don't belong to a service account construct the identity and return it
|
||||
if apiKey.ServiceAccountId == nil || *apiKey.ServiceAccountId < 1 {
|
||||
return &authn.Identity{
|
||||
ID: authn.NamespacedID(authn.NamespaceAPIKey, apiKey.ID),
|
||||
OrgID: apiKey.OrgID,
|
||||
OrgRoles: map[int64]org.RoleType{apiKey.OrgID: apiKey.Role},
|
||||
ClientParams: authn.ClientParams{SyncPermissions: true},
|
||||
AuthenticatedBy: login.APIKeyAuthModule,
|
||||
}, nil
|
||||
if key.ServiceAccountId == nil || *key.ServiceAccountId < 1 {
|
||||
return newAPIKeyIdentity(key), nil
|
||||
}
|
||||
|
||||
return &authn.Identity{
|
||||
ID: authn.NamespacedID(authn.NamespaceServiceAccount, *apiKey.ServiceAccountId),
|
||||
OrgID: apiKey.OrgID,
|
||||
AuthenticatedBy: login.APIKeyAuthModule,
|
||||
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
|
||||
}, nil
|
||||
return newServiceAccountIdentity(key), nil
|
||||
}
|
||||
|
||||
func (s *APIKey) getAPIKey(ctx context.Context, token string) (*apikey.APIKey, error) {
|
||||
@@ -147,6 +131,38 @@ func (s *APIKey) Priority() uint {
|
||||
return 30
|
||||
}
|
||||
|
||||
func (s *APIKey) Namespace() string {
|
||||
return authn.NamespaceAPIKey
|
||||
}
|
||||
|
||||
func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
|
||||
if !namespaceID.IsNamespace(authn.NamespaceAPIKey) {
|
||||
return nil, authn.ErrInvalidNamepsaceID.Errorf("got unspected namespace: %s", namespaceID.Namespace())
|
||||
}
|
||||
|
||||
apiKeyID, err := namespaceID.ParseInt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := s.apiKeyService.GetApiKeyById(ctx, &apikey.GetByIDQuery{
|
||||
ApiKeyID: apiKeyID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateApiKey(orgID, key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if key.ServiceAccountId != nil && *key.ServiceAccountId >= 1 {
|
||||
return nil, authn.ErrInvalidNamepsaceID.Errorf("api key belongs to service account")
|
||||
}
|
||||
|
||||
return newAPIKeyIdentity(key), nil
|
||||
}
|
||||
|
||||
func (s *APIKey) Hook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
|
||||
id, exists := s.getAPIKeyID(ctx, identity, r)
|
||||
|
||||
@@ -217,3 +233,38 @@ func getTokenFromRequest(r *authn.Request) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func validateApiKey(orgID int64, key *apikey.APIKey) error {
|
||||
if key.Expires != nil && *key.Expires <= time.Now().Unix() {
|
||||
return errAPIKeyExpired.Errorf("API key has expired")
|
||||
}
|
||||
|
||||
if key.IsRevoked != nil && *key.IsRevoked {
|
||||
return errAPIKeyRevoked.Errorf("Api key is revoked")
|
||||
}
|
||||
|
||||
if orgID != key.OrgID {
|
||||
return errAPIKeyOrgMismatch.Errorf("API does not belong in Organization")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity {
|
||||
return &authn.Identity{
|
||||
ID: authn.NamespacedID(authn.NamespaceAPIKey, key.ID),
|
||||
OrgID: key.OrgID,
|
||||
OrgRoles: map[int64]org.RoleType{key.OrgID: key.Role},
|
||||
ClientParams: authn.ClientParams{SyncPermissions: true},
|
||||
AuthenticatedBy: login.APIKeyAuthModule,
|
||||
}
|
||||
}
|
||||
|
||||
func newServiceAccountIdentity(key *apikey.APIKey) *authn.Identity {
|
||||
return &authn.Identity{
|
||||
ID: authn.NamespacedID(authn.NamespaceServiceAccount, *key.ServiceAccountId),
|
||||
OrgID: key.OrgID,
|
||||
AuthenticatedBy: login.APIKeyAuthModule,
|
||||
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +283,100 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIKey_ResolveIdentity(t *testing.T) {
|
||||
type testCase struct {
|
||||
desc string
|
||||
namespaceID authn.NamespaceID
|
||||
|
||||
exptedApiKey *apikey.APIKey
|
||||
|
||||
expectedIdenity *authn.Identity
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
desc: "should return error for invalid namespace",
|
||||
namespaceID: authn.MustParseNamespaceID("user:1"),
|
||||
expectedErr: authn.ErrInvalidNamepsaceID,
|
||||
},
|
||||
{
|
||||
desc: "should return error when api key has expired",
|
||||
namespaceID: authn.MustParseNamespaceID("api-key:1"),
|
||||
exptedApiKey: &apikey.APIKey{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
Expires: intPtr(0),
|
||||
},
|
||||
expectedErr: errAPIKeyExpired,
|
||||
},
|
||||
{
|
||||
desc: "should return error when api key is revoked",
|
||||
namespaceID: authn.MustParseNamespaceID("api-key:1"),
|
||||
exptedApiKey: &apikey.APIKey{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
IsRevoked: boolPtr(true),
|
||||
},
|
||||
expectedErr: errAPIKeyRevoked,
|
||||
},
|
||||
{
|
||||
desc: "should return error when api key is connected to service account",
|
||||
namespaceID: authn.MustParseNamespaceID("api-key:1"),
|
||||
exptedApiKey: &apikey.APIKey{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
ServiceAccountId: intPtr(1),
|
||||
},
|
||||
expectedErr: authn.ErrInvalidNamepsaceID,
|
||||
},
|
||||
{
|
||||
desc: "should return error when api key is belongs to different org",
|
||||
namespaceID: authn.MustParseNamespaceID("api-key:1"),
|
||||
exptedApiKey: &apikey.APIKey{
|
||||
ID: 1,
|
||||
OrgID: 2,
|
||||
ServiceAccountId: intPtr(1),
|
||||
},
|
||||
expectedErr: errAPIKeyOrgMismatch,
|
||||
},
|
||||
{
|
||||
desc: "should return valid idenitty",
|
||||
namespaceID: authn.MustParseNamespaceID("api-key:1"),
|
||||
exptedApiKey: &apikey.APIKey{
|
||||
ID: 1,
|
||||
OrgID: 1,
|
||||
Role: org.RoleEditor,
|
||||
},
|
||||
expectedIdenity: &authn.Identity{
|
||||
OrgID: 1,
|
||||
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
|
||||
ID: "api-key:1",
|
||||
AuthenticatedBy: login.APIKeyAuthModule,
|
||||
ClientParams: authn.ClientParams{SyncPermissions: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
c := ProvideAPIKey(&apikeytest.Service{
|
||||
ExpectedAPIKey: tt.exptedApiKey,
|
||||
})
|
||||
|
||||
identity, err := c.ResolveIdentity(context.Background(), 1, tt.namespaceID)
|
||||
if tt.expectedErr != nil {
|
||||
assert.Nil(t, identity)
|
||||
assert.ErrorIs(t, err, tt.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, *tt.expectedIdenity, *identity)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func intPtr(n int64) *int64 {
|
||||
return &n
|
||||
}
|
||||
|
||||
@@ -8,27 +8,18 @@ import (
|
||||
|
||||
var _ authn.Client = (*IdentityClient)(nil)
|
||||
|
||||
func ProvideIdentity(namespaceID string) *IdentityClient {
|
||||
return &IdentityClient{namespaceID}
|
||||
func ProvideIdentity(identity *authn.Identity) *IdentityClient {
|
||||
return &IdentityClient{identity}
|
||||
}
|
||||
|
||||
type IdentityClient struct {
|
||||
namespaceID string
|
||||
identity *authn.Identity
|
||||
}
|
||||
|
||||
func (i *IdentityClient) Name() string {
|
||||
return "identity"
|
||||
}
|
||||
|
||||
// Authenticate implements authn.Client.
|
||||
func (i *IdentityClient) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
|
||||
return &authn.Identity{
|
||||
OrgID: r.OrgID,
|
||||
ID: i.namespaceID,
|
||||
ClientParams: authn.ClientParams{
|
||||
AllowGlobalOrg: true,
|
||||
FetchSyncedUser: true,
|
||||
SyncPermissions: true,
|
||||
},
|
||||
}, nil
|
||||
return i.identity, nil
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ var (
|
||||
ErrClientNotConfigured = errutil.BadRequest("auth.client.notConfigured")
|
||||
ErrUnsupportedIdentity = errutil.NotImplemented("auth.identity.unsupported")
|
||||
ErrExpiredAccessToken = errutil.Unauthorized("oauth.expired-token", errutil.WithPublicMessage("OAuth access token expired"))
|
||||
ErrInvalidNamepsaceID = errutil.BadRequest("auth.identity.invalid-namespace-id")
|
||||
)
|
||||
|
||||
@@ -16,24 +16,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
// NamespacedID builds a namespaced ID from a namespace and an ID.
|
||||
func NamespacedID(namespace string, id int64) string {
|
||||
return fmt.Sprintf("%s:%d", namespace, id)
|
||||
}
|
||||
|
||||
const (
|
||||
NamespaceUser = identity.NamespaceUser
|
||||
NamespaceAPIKey = identity.NamespaceAPIKey
|
||||
NamespaceServiceAccount = identity.NamespaceServiceAccount
|
||||
NamespaceAnonymous = identity.NamespaceAnonymous
|
||||
NamespaceRenderService = identity.NamespaceRenderService
|
||||
NamespaceAccessPolicy = identity.NamespaceAccessPolicy
|
||||
)
|
||||
|
||||
const (
|
||||
AnonymousNamespaceID = NamespaceAnonymous + ":0"
|
||||
GlobalOrgID = int64(0)
|
||||
)
|
||||
const GlobalOrgID = int64(0)
|
||||
|
||||
var _ identity.Requester = (*Identity)(nil)
|
||||
|
||||
@@ -145,7 +128,6 @@ func (i *Identity) GetLogin() string {
|
||||
return i.Login
|
||||
}
|
||||
|
||||
// GetOrgID implements identity.Requester.
|
||||
func (i *Identity) GetOrgID() int64 {
|
||||
return i.OrgID
|
||||
}
|
||||
|
||||
89
pkg/services/authn/namespace.go
Normal file
89
pkg/services/authn/namespace.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package authn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
)
|
||||
|
||||
const (
|
||||
NamespaceUser = identity.NamespaceUser
|
||||
NamespaceAPIKey = identity.NamespaceAPIKey
|
||||
NamespaceServiceAccount = identity.NamespaceServiceAccount
|
||||
NamespaceAnonymous = identity.NamespaceAnonymous
|
||||
NamespaceRenderService = identity.NamespaceRenderService
|
||||
NamespaceAccessPolicy = identity.NamespaceAccessPolicy
|
||||
AnonymousNamespaceID = NamespaceAnonymous + ":0"
|
||||
)
|
||||
|
||||
var namespaceLookup = map[string]struct{}{
|
||||
NamespaceUser: {},
|
||||
NamespaceAPIKey: {},
|
||||
NamespaceServiceAccount: {},
|
||||
NamespaceAnonymous: {},
|
||||
NamespaceRenderService: {},
|
||||
NamespaceAccessPolicy: {},
|
||||
}
|
||||
|
||||
// NamespacedID builds a namespaced ID from a namespace and an ID.
|
||||
func NamespacedID(namespace string, id int64) string {
|
||||
return fmt.Sprintf("%s:%d", namespace, id)
|
||||
}
|
||||
|
||||
func ParseNamespaceID(str string) (NamespaceID, error) {
|
||||
var namespaceID NamespaceID
|
||||
|
||||
parts := strings.Split(str, ":")
|
||||
if len(parts) != 2 {
|
||||
return namespaceID, ErrInvalidNamepsaceID.Errorf("expected namespace id to have 2 parts")
|
||||
}
|
||||
|
||||
namespace, id := parts[0], parts[1]
|
||||
|
||||
if _, ok := namespaceLookup[namespace]; !ok {
|
||||
return namespaceID, ErrInvalidNamepsaceID.Errorf("got invalid namespace %s", namespace)
|
||||
}
|
||||
|
||||
namespaceID.id = id
|
||||
namespaceID.namespace = namespace
|
||||
|
||||
return namespaceID, nil
|
||||
}
|
||||
|
||||
// MustParseNamespaceID parses namespace id, it will panic it failes to do so.
|
||||
// Sutable to use in tests or when we can garantuee that we pass a correct format.
|
||||
func MustParseNamespaceID(str string) NamespaceID {
|
||||
namespaceID, err := ParseNamespaceID(str)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return namespaceID
|
||||
}
|
||||
|
||||
// FIXME: use this instead of encoded string through the codebase
|
||||
type NamespaceID struct {
|
||||
id string
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (ni NamespaceID) ID() string {
|
||||
return ni.id
|
||||
}
|
||||
|
||||
func (ni NamespaceID) ParseInt() (int64, error) {
|
||||
return strconv.ParseInt(ni.id, 10, 64)
|
||||
}
|
||||
|
||||
func (ni NamespaceID) Namespace() string {
|
||||
return ni.namespace
|
||||
}
|
||||
|
||||
func (ni NamespaceID) IsNamespace(expected ...string) bool {
|
||||
return identity.IsNamespace(ni.namespace, expected...)
|
||||
}
|
||||
|
||||
func (ni NamespaceID) String() string {
|
||||
return fmt.Sprintf("%s:%s", ni.namespace, ni.id)
|
||||
}
|
||||
Reference in New Issue
Block a user