Identity: Rename "namespace" to "type" in the requester interface (#90567)

This commit is contained in:
Ryan McKinley
2024-07-25 12:52:14 +03:00
committed by GitHub
parent 8cdf5ee824
commit 9db3bc926e
109 changed files with 649 additions and 632 deletions
+4 -4
View File
@@ -95,7 +95,7 @@ type Service interface {
// RegisterPreLogoutHook registers a hook that is called before a logout request.
RegisterPreLogoutHook(hook PreLogoutHookFn, priority uint)
// ResolveIdentity resolves an identity from org and namespace id.
ResolveIdentity(ctx context.Context, orgID int64, namespaceID NamespaceID) (*Identity, error)
ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error)
// RegisterClient will register a new authn.Client that can be used for authentication
RegisterClient(c Client)
@@ -157,7 +157,7 @@ type RedirectClient interface {
// that should happen during logout and supports client specific redirect URL.
type LogoutClient interface {
Client
Logout(ctx context.Context, user Requester) (*Redirect, bool)
Logout(ctx context.Context, user identity.Requester) (*Redirect, bool)
}
type PasswordClient interface {
@@ -179,8 +179,8 @@ type UsageStatClient interface {
// 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)
IdentityType() identity.IdentityType
ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error)
}
type Request struct {
+12 -11
View File
@@ -13,6 +13,7 @@ import (
"go.opentelemetry.io/otel/trace"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/network"
"github.com/grafana/grafana/pkg/infra/tracing"
@@ -216,9 +217,9 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i
}
// Login is only supported for users
if !id.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
s.metrics.failedLogin.WithLabelValues(client).Inc()
return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.ID.Namespace())
return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.ID.Type())
}
userID, err := id.ID.ParseInt()
@@ -271,7 +272,7 @@ func (s *Service) RegisterPreLogoutHook(hook authn.PreLogoutHookFn, priority uin
s.preLogoutHooks.insert(hook, priority)
}
func (s *Service) Logout(ctx context.Context, user authn.Requester, sessionToken *auth.UserToken) (*authn.Redirect, error) {
func (s *Service) Logout(ctx context.Context, user identity.Requester, sessionToken *auth.UserToken) (*authn.Redirect, error) {
ctx, span := s.tracer.Start(ctx, "authn.Logout")
defer span.End()
@@ -280,7 +281,7 @@ func (s *Service) Logout(ctx context.Context, user authn.Requester, sessionToken
redirect.URL = s.cfg.SignoutRedirectUrl
}
if !user.GetID().IsNamespace(authn.NamespaceUser) {
if !user.GetID().IsType(identity.TypeUser) {
return redirect, nil
}
@@ -327,7 +328,7 @@ Default:
return redirect, nil
}
func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
ctx, span := s.tracer.Start(ctx, "authn.ResolveIdentity")
defer span.End()
@@ -352,7 +353,7 @@ func (s *Service) RegisterClient(c authn.Client) {
}
if rc, ok := c.(authn.IdentityResolverClient); ok {
s.idenityResolverClients[rc.Namespace()] = rc
s.idenityResolverClients[rc.IdentityType().String()] = rc
}
}
@@ -375,11 +376,11 @@ 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) {
func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
ctx, span := s.tracer.Start(ctx, "authn.resolveIdentity")
defer span.End()
if namespaceID.IsNamespace(authn.NamespaceUser) {
if namespaceID.IsType(identity.TypeUser) {
return &authn.Identity{
OrgID: orgID,
ID: namespaceID,
@@ -390,7 +391,7 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a
}}, nil
}
if namespaceID.IsNamespace(authn.NamespaceServiceAccount) {
if namespaceID.IsType(identity.TypeServiceAccount) {
return &authn.Identity{
ID: namespaceID,
OrgID: orgID,
@@ -401,9 +402,9 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID a
}}, nil
}
resolver, ok := s.idenityResolverClients[namespaceID.Namespace().String()]
resolver, ok := s.idenityResolverClients[string(namespaceID.Type())]
if !ok {
return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", namespaceID.Namespace())
return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", namespaceID.Type())
}
return resolver.ResolveIdentity(ctx, orgID, namespaceID)
}
+24 -24
View File
@@ -44,9 +44,9 @@ func TestService_Authenticate(t *testing.T) {
{
desc: "should succeed with authentication for configured client",
clients: []authn.Client{
&authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}},
&authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}},
},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
desc: "should succeed with authentication for configured client for identity with fetch permissions params",
@@ -54,7 +54,7 @@ func TestService_Authenticate(t *testing.T) {
&authntest.FakeClient{
ExpectedTest: true,
ExpectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
ClientParams: authn.ClientParams{
FetchPermissionsParams: authn.FetchPermissionsParams{
ActionsLookup: []string{
@@ -70,7 +70,7 @@ func TestService_Authenticate(t *testing.T) {
},
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
ClientParams: authn.ClientParams{
FetchPermissionsParams: authn.FetchPermissionsParams{
ActionsLookup: []string{
@@ -92,19 +92,19 @@ func TestService_Authenticate(t *testing.T) {
ExpectedName: "2",
ExpectedPriority: 2,
ExpectedTest: true,
ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
},
},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"},
},
{
desc: "should succeed with authentication for third client when error happened in first",
clients: []authn.Client{
&authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false},
&authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 1, ExpectedTest: true, ExpectedErr: errors.New("some error")},
&authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:3")}},
&authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:3")}},
},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:3")},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:3")},
},
{
desc: "should return error when no client could authenticate the request",
@@ -315,10 +315,10 @@ func TestService_Login(t *testing.T) {
client: "fake",
expectedClientOK: true,
expectedClientIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
SessionToken: &auth.UserToken{UserId: 1},
},
},
@@ -331,7 +331,7 @@ func TestService_Login(t *testing.T) {
desc: "should not login non user identity",
client: "fake",
expectedClientOK: true,
expectedClientIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("api-key:1")},
expectedClientIdentity: &authn.Identity{ID: identity.MustParseTypedID("api-key:1")},
expectedErr: authn.ErrUnsupportedIdentity,
},
}
@@ -420,31 +420,31 @@ func TestService_Logout(t *testing.T) {
tests := []TestCase{
{
desc: "should redirect to default redirect url when identity is not a user",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceServiceAccount, 1)},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeServiceAccount, 1)},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
},
{
desc: "should redirect to default redirect url when no external provider was used to authenticate",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1)},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1)},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
expectedTokenRevoked: true,
},
{
desc: "should redirect to default redirect url when client is not found",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "notfound"},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "notfound"},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
expectedTokenRevoked: true,
},
{
desc: "should redirect to default redirect url when client do not implement logout extension",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"},
expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"},
client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"},
expectedTokenRevoked: true,
},
{
desc: "should use signout redirect url if configured",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"},
expectedRedirect: &authn.Redirect{URL: "some-url"},
client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"},
signoutRedirectURL: "some-url",
@@ -452,7 +452,7 @@ func TestService_Logout(t *testing.T) {
},
{
desc: "should redirect to client specific url",
identity: &authn.Identity{ID: authn.NewNamespaceID(authn.NamespaceUser, 1), AuthenticatedBy: "azuread"},
identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"},
expectedRedirect: &authn.Redirect{URL: "http://idp.com/logout"},
client: &authntest.MockClient{
NameFunc: func() string { return "auth.client.azuread" },
@@ -500,26 +500,26 @@ 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, authn.NewNamespaceID("some", 1))
_, err := svc.ResolveIdentity(context.Background(), 1, identity.NewTypedID("some", 1))
assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity)
})
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, authn.MustParseNamespaceID("api-key:1"))
_, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("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, authn.MustParseNamespaceID("user:1"))
identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("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, authn.MustParseNamespaceID("service-account:1"))
identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("service-account:1"))
assert.NoError(t, err)
assert.NotNil(t, identity)
})
@@ -527,14 +527,14 @@ func TestService_ResolveIdentity(t *testing.T) {
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.String() },
ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
IdentityTypeFunc: func() identity.IdentityType { return identity.TypeAPIKey },
ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
return &authn.Identity{}, nil
},
})
})
identity, err := svc.ResolveIdentity(context.Background(), 1, authn.MustParseNamespaceID("api-key:1"))
identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("api-key:1"))
assert.NoError(t, err)
assert.NotNil(t, identity)
})
@@ -8,6 +8,7 @@ import (
"golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login/social"
@@ -36,42 +37,42 @@ type OAuthTokenSync struct {
tracer tracing.Tracer
}
func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "oauth.sync.SyncOauthTokenHook")
defer span.End()
// only perform oauth token check if identity is a user
if !identity.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
return nil
}
// Not authenticated through session tokens, so we can skip this hook.
if identity.SessionToken == nil {
if id.SessionToken == nil {
return nil
}
// Not authenticated with a oauth provider, so we can skip this hook.
if !strings.HasPrefix(identity.GetAuthenticatedBy(), "oauth") {
if !strings.HasPrefix(id.GetAuthenticatedBy(), "oauth") {
return nil
}
ctxLogger := s.log.FromContext(ctx).New("userID", identity.ID.ID())
ctxLogger := s.log.FromContext(ctx).New("userID", id.ID.ID())
_, err, _ := s.singleflightGroup.Do(identity.ID.String(), func() (interface{}, error) {
_, err, _ := s.singleflightGroup.Do(id.ID.String(), func() (interface{}, error) {
ctxLogger.Debug("Singleflight request for OAuth token sync")
// FIXME: Consider using context.WithoutCancel instead of context.Background after Go 1.21 update
updateCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if refreshErr := s.service.TryTokenRefresh(updateCtx, identity); refreshErr != nil {
if refreshErr := s.service.TryTokenRefresh(updateCtx, id); refreshErr != nil {
if errors.Is(refreshErr, context.Canceled) {
return nil, nil
}
token, _, err := s.service.HasOAuthEntry(ctx, identity)
token, _, err := s.service.HasOAuthEntry(ctx, id)
if err != nil {
ctxLogger.Error("Failed to get OAuth entry for verifying if token has already been refreshed", "id", identity.ID, "error", err)
ctxLogger.Error("Failed to get OAuth entry for verifying if token has already been refreshed", "id", id.ID, "error", err)
return nil, err
}
@@ -81,14 +82,14 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn
return nil, nil
}
ctxLogger.Error("Failed to refresh OAuth access token", "id", identity.ID, "error", refreshErr)
ctxLogger.Error("Failed to refresh OAuth access token", "id", id.ID, "error", refreshErr)
if err := s.service.InvalidateOAuthTokens(ctx, token); err != nil {
ctxLogger.Warn("Failed to invalidate OAuth tokens", "id", identity.ID, "error", err)
ctxLogger.Warn("Failed to invalidate OAuth tokens", "id", id.ID, "error", err)
}
if err := s.sessionService.RevokeToken(ctx, identity.SessionToken, false); err != nil {
ctxLogger.Warn("Failed to revoke session token", "id", identity.ID, "tokenId", identity.SessionToken.Id, "error", err)
if err := s.sessionService.RevokeToken(ctx, id.SessionToken, false); err != nil {
ctxLogger.Warn("Failed to revoke session token", "id", id.ID, "tokenId", id.SessionToken.Id, "error", err)
}
return nil, refreshErr
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"golang.org/x/sync/singleflight"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/login/social"
@@ -41,17 +42,17 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
tests := []testCase{
{
desc: "should skip sync when identity is not a user",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("service-account:1")},
expectTryRefreshTokenCalled: false,
},
{
desc: "should skip sync when identity is a user but is not authenticated with session token",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
expectTryRefreshTokenCalled: false,
},
{
desc: "should invalidate access token and session token if token refresh fails",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
expectHasEntryCalled: true,
expectedTryRefreshErr: errors.New("some err"),
expectTryRefreshTokenCalled: true,
@@ -62,7 +63,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
},
{
desc: "should refresh the token successfully",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
expectHasEntryCalled: false,
expectTryRefreshTokenCalled: true,
expectInvalidateOauthTokensCalled: false,
@@ -70,7 +71,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
},
{
desc: "should not invalidate the token if the token has already been refreshed by another request (singleflight)",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule},
expectHasEntryCalled: true,
expectTryRefreshTokenCalled: true,
expectInvalidateOauthTokensCalled: false,
@@ -92,7 +93,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
)
service := &oauthtokentest.MockOauthTokenService{
HasOAuthEntryFunc: func(ctx context.Context, usr authn.Requester) (*login.UserAuth, bool, error) {
HasOAuthEntryFunc: func(ctx context.Context, usr identity.Requester) (*login.UserAuth, bool, error) {
hasEntryCalled = true
return tt.expectedHasEntryToken, tt.expectedHasEntryToken != nil, nil
},
@@ -100,7 +101,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) {
invalidateTokensCalled = true
return nil
},
TryTokenRefreshFunc: func(ctx context.Context, usr authn.Requester) error {
TryTokenRefreshFunc: func(ctx context.Context, usr identity.Requester) error {
tryRefreshCalled = true
return tt.expectedTryRefreshErr
},
@@ -6,6 +6,7 @@ import (
"fmt"
"sort"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -38,14 +39,14 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a
ctxLogger := s.log.FromContext(ctx).New("id", id.ID, "login", id.Login)
if !id.ID.IsNamespace(authn.NamespaceUser) {
ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "namespace", id.ID.Namespace())
if !id.ID.IsType(identity.TypeUser) {
ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "type", id.ID.Type())
return nil
}
userID, err := id.ID.ParseInt()
if err != nil {
ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "namespace", id.ID.Namespace(), "err", err)
ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "type", id.ID.Type(), "err", err)
return nil
}
@@ -144,14 +145,14 @@ func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn.
ctxLogger := s.log.FromContext(ctx)
if !currentIdentity.ID.IsNamespace(authn.NamespaceUser) {
ctxLogger.Debug("Skipping default org sync, not a user", "namespace", currentIdentity.ID.Namespace())
if !currentIdentity.ID.IsType(identity.TypeUser) {
ctxLogger.Debug("Skipping default org sync, not a user", "type", currentIdentity.ID.Type())
return
}
userID, err := currentIdentity.ID.ParseInt()
if err != nil {
ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "namespace", currentIdentity.ID.Namespace(), "err", err)
ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "type", currentIdentity.ID.Type(), "err", err)
return
}
@@ -76,7 +76,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) {
args: args{
ctx: context.Background(),
id: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@@ -92,7 +92,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) {
},
},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@@ -139,7 +139,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should set default org",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) {
userService.On("Update", mock.Anything, mock.MatchedBy(func(cmd *user.UpdateUserCommand) bool {
return cmd.UserID == 1 && *cmd.OrgID == 2
@@ -149,7 +149,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should skip setting the default org when default org is not set",
defaultOrgSetting: -1,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
name: "should skip setting the default org when identity is nil",
@@ -159,28 +159,28 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should skip setting the default org when input err is not nil",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
inputErr: fmt.Errorf("error"),
},
{
name: "should skip setting the default org when identity is not a user",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("service-account:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("service-account:1")},
},
{
name: "should skip setting the default org when user id is not valid",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:invalid")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:invalid")},
},
{
name: "should skip setting the default org when user is not allowed to use the configured default org",
defaultOrgSetting: 3,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
name: "should skip setting the default org when validateUsingOrg returns error",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) {
orgService.ExpectedError = fmt.Errorf("error")
},
@@ -188,7 +188,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) {
{
name: "should skip the hook when the user org update was unsuccessful",
defaultOrgSetting: 2,
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) {
userService.On("Update", mock.Anything, mock.Anything).Return(fmt.Errorf("error"))
},
@@ -5,6 +5,7 @@ import (
"errors"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -146,7 +147,7 @@ func (s *RBACSync) SyncCloudRoles(ctx context.Context, ident *authn.Identity, r
return nil
}
if !ident.ID.IsNamespace(authn.NamespaceUser) {
if !ident.ID.IsType(identity.TypeUser) {
s.log.FromContext(ctx).Debug("Skip syncing cloud role", "id", ident.ID)
return nil
}
@@ -4,6 +4,10 @@ import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -11,8 +15,6 @@ import (
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRBACSync_SyncPermission(t *testing.T) {
@@ -24,14 +26,14 @@ func TestRBACSync_SyncPermission(t *testing.T) {
testCases := []testCase{
{
name: "enriches the identity successfully when SyncPermissions is true",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
expectedPermissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersRead},
},
},
{
name: "does not load the permissions when SyncPermissions is false",
identity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
identity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}},
expectedPermissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersRead},
},
@@ -65,7 +67,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should call sync when authenticated with grafana com and has viewer role",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
},
@@ -76,7 +78,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should call sync when authenticated with grafana com and has editor role",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
},
@@ -87,7 +89,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should call sync when authenticated with grafana com and has admin role",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
},
@@ -98,7 +100,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should not call sync when authenticated with grafana com and has invalid role",
module: login.GrafanaComAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleType("something else")},
},
@@ -109,7 +111,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) {
desc: "should not call sync when not authenticated with grafana com",
module: login.LDAPAuthModule,
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
},
@@ -155,7 +157,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{
desc: "should map Cloud Viewer to Grafana Cloud Viewer and Support ticket reader",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
},
@@ -174,7 +176,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{
desc: "should map Cloud Editor to Grafana Cloud Editor and Support ticket admin",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
},
@@ -193,7 +195,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{
desc: "should map Cloud Admin to Grafana Cloud Admin and Support ticket admin",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
},
@@ -212,7 +214,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
{
desc: "should return an error for not supported role",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleNone},
},
@@ -234,7 +236,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) {
func setupTestEnv() *RBACSync {
acMock := &acmock.Mock{
GetUserPermissionsFunc: func(ctx context.Context, siu authn.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) {
GetUserPermissionsFunc: func(ctx context.Context, siu identity.Requester, o accesscontrol.Options) ([]accesscontrol.Permission, error) {
return []accesscontrol.Permission{
{Action: accesscontrol.ActionUsersRead},
}, nil
+37 -36
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn"
@@ -109,21 +110,21 @@ func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *auth
return nil
}
func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
func (s *UserSync) FetchSyncedUserHook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.FetchSyncedUserHook")
defer span.End()
if !identity.ClientParams.FetchSyncedUser {
if !id.ClientParams.FetchSyncedUser {
return nil
}
if !identity.ID.IsNamespace(authn.NamespaceUser, authn.NamespaceServiceAccount) {
if !id.ID.IsType(identity.TypeUser, identity.TypeServiceAccount) {
return nil
}
userID, err := identity.ID.ParseInt()
userID, err := id.ID.ParseInt()
if err != nil {
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err)
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err)
return nil
}
@@ -138,18 +139,18 @@ func (s *UserSync) FetchSyncedUserHook(ctx context.Context, identity *authn.Iden
return errFetchingSignedInUser.Errorf("failed to resolve user: %w", err)
}
if identity.ClientParams.AllowGlobalOrg && identity.OrgID == authn.GlobalOrgID {
if id.ClientParams.AllowGlobalOrg && id.OrgID == authn.GlobalOrgID {
usr.Teams = nil
usr.OrgName = ""
usr.OrgRole = org.RoleNone
usr.OrgID = authn.GlobalOrgID
}
syncSignedInUserToIdentity(usr, identity)
syncSignedInUserToIdentity(usr, id)
return nil
}
func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
func (s *UserSync) SyncLastSeenHook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.SyncLastSeenHook")
defer span.End()
@@ -158,13 +159,13 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identit
return nil
}
if !identity.ID.IsNamespace(authn.NamespaceUser, authn.NamespaceServiceAccount) {
if !id.ID.IsType(identity.TypeUser, identity.TypeServiceAccount) {
return nil
}
userID, err := identity.ID.ParseInt()
userID, err := id.ID.ParseInt()
if err != nil {
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err)
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err)
return nil
}
@@ -186,21 +187,21 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, identity *authn.Identit
return nil
}
func (s *UserSync) EnableUserHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
func (s *UserSync) EnableUserHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
ctx, span := s.tracer.Start(ctx, "user.sync.EnableUserHook")
defer span.End()
if !identity.ClientParams.EnableUser {
if !id.ClientParams.EnableUser {
return nil
}
if !identity.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
return nil
}
userID, err := identity.ID.ParseInt()
userID, err := id.ID.ParseInt()
if err != nil {
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", identity.ID, "err", err)
s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err)
return nil
}
@@ -417,8 +418,8 @@ func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupPar
// syncUserToIdentity syncs a user to an identity.
// This is used to update the identity with the latest user information.
func syncUserToIdentity(usr *user.User, id *authn.Identity) {
id.ID = authn.NewNamespaceID(authn.NamespaceUser, usr.ID)
id.UID = authn.NewNamespaceIDString(authn.NamespaceUser, usr.UID)
id.ID = identity.NewTypedID(identity.TypeUser, usr.ID)
id.UID = identity.NewTypedIDString(identity.TypeUser, usr.UID)
id.Login = usr.Login
id.Email = usr.Email
id.Name = usr.Name
@@ -427,25 +428,25 @@ func syncUserToIdentity(usr *user.User, id *authn.Identity) {
}
// syncSignedInUserToIdentity syncs a user to an identity.
func syncSignedInUserToIdentity(usr *user.SignedInUser, identity *authn.Identity) {
var ns authn.Namespace
if identity.ID.IsNamespace(authn.NamespaceServiceAccount) {
ns = authn.NamespaceServiceAccount
func syncSignedInUserToIdentity(usr *user.SignedInUser, id *authn.Identity) {
var ns identity.IdentityType
if id.ID.IsType(identity.TypeServiceAccount) {
ns = identity.TypeServiceAccount
} else {
ns = authn.NamespaceUser
ns = identity.TypeUser
}
identity.UID = authn.NewNamespaceIDString(ns, usr.UserUID)
id.UID = identity.NewTypedIDString(ns, usr.UserUID)
identity.Name = usr.Name
identity.Login = usr.Login
identity.Email = usr.Email
identity.OrgID = usr.OrgID
identity.OrgName = usr.OrgName
identity.OrgRoles = map[int64]org.RoleType{identity.OrgID: usr.OrgRole}
identity.HelpFlags1 = usr.HelpFlags1
identity.Teams = usr.Teams
identity.LastSeenAt = usr.LastSeenAt
identity.IsDisabled = usr.IsDisabled
identity.IsGrafanaAdmin = &usr.IsGrafanaAdmin
identity.EmailVerified = usr.EmailVerified
id.Name = usr.Name
id.Login = usr.Login
id.Email = usr.Email
id.OrgID = usr.OrgID
id.OrgName = usr.OrgName
id.OrgRoles = map[int64]org.RoleType{id.OrgID: usr.OrgRole}
id.HelpFlags1 = usr.HelpFlags1
id.Teams = usr.Teams
id.LastSeenAt = usr.LastSeenAt
id.IsDisabled = usr.IsDisabled
id.IsGrafanaAdmin = &usr.IsGrafanaAdmin
id.EmailVerified = usr.EmailVerified
}
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
@@ -163,8 +164,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
UID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
UID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@@ -202,8 +203,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
UID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
UID: identity.MustParseTypedID("user:1"),
Login: "test",
Name: "test",
Email: "test",
@@ -243,8 +244,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
UID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
UID: identity.MustParseTypedID("user:1"),
AuthID: "2032",
AuthenticatedBy: "oauth",
Login: "test",
@@ -315,8 +316,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
UID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
UID: identity.MustParseTypedID("user:2"),
Login: "test_create",
Name: "test_create",
Email: "test_create",
@@ -361,8 +362,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:3"),
UID: authn.MustParseNamespaceID("user:3"),
ID: identity.MustParseTypedID("user:3"),
UID: identity.MustParseTypedID("user:3"),
Login: "test_mod",
Name: "test_mod",
Email: "test_mod",
@@ -406,8 +407,8 @@ func TestUserSync_SyncUserHook(t *testing.T) {
},
wantErr: false,
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:3"),
UID: authn.MustParseNamespaceID("user:3"),
ID: identity.MustParseTypedID("user:3"),
UID: identity.MustParseTypedID("user:3"),
Name: "test",
Login: "test",
Email: "test_mod@test.com",
@@ -457,7 +458,7 @@ func TestUserSync_FetchSyncedUserHook(t *testing.T) {
{
desc: "should skip hook when identity is not a user",
req: &authn.Request{},
identity: &authn.Identity{ID: authn.MustParseNamespaceID("api-key:1"), ClientParams: authn.ClientParams{FetchSyncedUser: true}},
identity: &authn.Identity{ID: identity.MustParseTypedID("api-key:1"), ClientParams: authn.ClientParams{FetchSyncedUser: true}},
},
}
@@ -483,7 +484,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
{
desc: "should skip if correct flag is not set",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
IsDisabled: true,
ClientParams: authn.ClientParams{EnableUser: false},
},
@@ -492,7 +493,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
{
desc: "should skip if identity is not a user",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceAPIKey, 1),
ID: identity.NewTypedID(identity.TypeAPIKey, 1),
IsDisabled: true,
ClientParams: authn.ClientParams{EnableUser: true},
},
@@ -501,7 +502,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) {
{
desc: "should enabled disabled user",
identity: &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, 1),
ID: identity.NewTypedID(identity.TypeUser, 1),
IsDisabled: true,
ClientParams: authn.ClientParams{EnableUser: true},
},
+1 -1
View File
@@ -78,7 +78,7 @@ func (f *FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertok
panic("unimplemented")
}
func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if f.ExpectedIdentities != nil {
if f.CurrentIndex >= len(f.ExpectedIdentities) {
panic("ExpectedIdentities is empty")
+8 -8
View File
@@ -54,7 +54,7 @@ func (*MockService) Logout(_ context.Context, _ identity.Requester, _ *usertoken
panic("unimplemented")
}
func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
panic("unimplemented")
}
@@ -77,8 +77,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)
IdentityTypeFunc func() identity.IdentityType
ResolveIdentityFunc func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error)
}
func (m MockClient) Name() string {
@@ -127,15 +127,15 @@ 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()
func (m *MockClient) IdentityType() identity.IdentityType {
if m.IdentityTypeFunc != nil {
return m.IdentityTypeFunc()
}
return ""
return identity.TypeEmpty
}
// ResolveIdentity implements authn.IdentityResolverClient.
func (m *MockClient) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
func (m *MockClient) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if m.ResolveIdentityFunc != nil {
return m.ResolveIdentityFunc(ctx, orgID, namespaceID)
}
+14 -13
View File
@@ -7,6 +7,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/components/satokengen"
"github.com/grafana/grafana/pkg/infra/log"
@@ -134,13 +135,13 @@ func (s *APIKey) Priority() uint {
return 30
}
func (s *APIKey) Namespace() string {
return authn.NamespaceAPIKey.String()
func (s *APIKey) IdentityType() identity.IdentityType {
return identity.TypeAPIKey
}
func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID authn.NamespaceID) (*authn.Identity, error) {
if !namespaceID.IsNamespace(authn.NamespaceAPIKey) {
return nil, authn.ErrInvalidNamespaceID.Errorf("got unspected namespace: %s", namespaceID.Namespace())
func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) {
if !namespaceID.IsType(identity.TypeAPIKey) {
return nil, identity.ErrInvalidTypedID.Errorf("got unspected namespace: %s", namespaceID.Type())
}
apiKeyID, err := namespaceID.ParseInt()
@@ -160,7 +161,7 @@ func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID a
}
if key.ServiceAccountId != nil && *key.ServiceAccountId >= 1 {
return nil, authn.ErrInvalidNamespaceID.Errorf("api key belongs to service account")
return nil, identity.ErrInvalidTypedID.Errorf("api key belongs to service account")
}
return newAPIKeyIdentity(key), nil
@@ -187,18 +188,18 @@ func (s *APIKey) Hook(ctx context.Context, identity *authn.Identity, r *authn.Re
return nil
}
func (s *APIKey) getAPIKeyID(ctx context.Context, identity *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) {
id, err := identity.ID.ParseInt()
func (s *APIKey) getAPIKeyID(ctx context.Context, id *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) {
internalId, err := id.ID.ParseInt()
if err != nil {
s.log.Warn("Failed to parse ID from identifier", "err", err)
return -1, false
}
if identity.ID.IsNamespace(authn.NamespaceAPIKey) {
return id, true
if id.ID.IsType(identity.TypeAPIKey) {
return internalId, true
}
if identity.ID.IsNamespace(authn.NamespaceServiceAccount) {
if id.ID.IsType(identity.TypeServiceAccount) {
// When the identity is service account, the ID in from the namespace is the service account ID.
// We need to fetch the API key in this scenario, as we could use it to uniquely identify a service account token.
apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r))
@@ -255,7 +256,7 @@ func validateApiKey(orgID int64, key *apikey.APIKey) error {
func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity {
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceAPIKey, key.ID),
ID: identity.NewTypedID(identity.TypeAPIKey, key.ID),
OrgID: key.OrgID,
OrgRoles: map[int64]org.RoleType{key.OrgID: key.Role},
ClientParams: authn.ClientParams{SyncPermissions: true},
@@ -265,7 +266,7 @@ func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity {
func newServiceAccountIdentity(key *apikey.APIKey) *authn.Identity {
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceServiceAccount, *key.ServiceAccountId),
ID: identity.NewTypedID(identity.TypeServiceAccount, *key.ServiceAccountId),
OrgID: key.OrgID,
AuthenticatedBy: login.APIKeyAuthModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
+17 -16
View File
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/components/satokengen"
"github.com/grafana/grafana/pkg/services/apikey"
@@ -47,7 +48,7 @@ func TestAPIKey_Authenticate(t *testing.T) {
Role: org.RoleAdmin,
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("api-key:1"),
ID: identity.MustParseTypedID("api-key:1"),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin},
ClientParams: authn.ClientParams{
@@ -70,7 +71,7 @@ func TestAPIKey_Authenticate(t *testing.T) {
ServiceAccountId: intPtr(1),
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("service-account:1"),
ID: identity.MustParseTypedID("service-account:1"),
OrgID: 1,
ClientParams: authn.ClientParams{
FetchSyncedUser: true,
@@ -205,7 +206,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
ServiceAccountId: intPtr(1),
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("service-account:1"),
ID: identity.MustParseTypedID("service-account:1"),
OrgID: 1,
Name: "test",
AuthenticatedBy: login.APIKeyAuthModule,
@@ -221,7 +222,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
Key: hash,
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("api-key:2"),
ID: identity.MustParseTypedID("api-key:2"),
OrgID: 1,
Name: "test",
AuthenticatedBy: login.APIKeyAuthModule,
@@ -237,7 +238,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
Key: hash,
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
OrgID: 1,
Name: "test",
AuthenticatedBy: login.APIKeyAuthModule,
@@ -253,7 +254,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
Key: hash,
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("service-account:2"),
ID: identity.MustParseTypedID("service-account:2"),
OrgID: 1,
Name: "test",
AuthenticatedBy: login.APIKeyAuthModule,
@@ -286,7 +287,7 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) {
func TestAPIKey_ResolveIdentity(t *testing.T) {
type testCase struct {
desc string
namespaceID authn.NamespaceID
namespaceID identity.TypedID
exptedApiKey *apikey.APIKey
@@ -297,12 +298,12 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
tests := []testCase{
{
desc: "should return error for invalid namespace",
namespaceID: authn.MustParseNamespaceID("user:1"),
expectedErr: authn.ErrInvalidNamespaceID,
namespaceID: identity.MustParseTypedID("user:1"),
expectedErr: identity.ErrInvalidTypedID,
},
{
desc: "should return error when api key has expired",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 1,
@@ -312,7 +313,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
},
{
desc: "should return error when api key is revoked",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 1,
@@ -322,17 +323,17 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
},
{
desc: "should return error when api key is connected to service account",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 1,
ServiceAccountId: intPtr(1),
},
expectedErr: authn.ErrInvalidNamespaceID,
expectedErr: identity.ErrInvalidTypedID,
},
{
desc: "should return error when api key is belongs to different org",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 2,
@@ -342,7 +343,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
},
{
desc: "should return valid idenitty",
namespaceID: authn.MustParseNamespaceID("api-key:1"),
namespaceID: identity.MustParseTypedID("api-key:1"),
exptedApiKey: &apikey.APIKey{
ID: 1,
OrgID: 1,
@@ -351,7 +352,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) {
expectedIdenity: &authn.Identity{
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleEditor},
ID: authn.MustParseNamespaceID("api-key:1"),
ID: identity.MustParseTypedID("api-key:1"),
AuthenticatedBy: login.APIKeyAuthModule,
ClientParams: authn.ClientParams{SyncPermissions: true},
},
+3 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
)
@@ -24,8 +25,8 @@ func TestBasic_Authenticate(t *testing.T) {
{
desc: "should success when password client return identity",
req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "password")}}}},
client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
desc: "should fail when basic auth header could not be decoded",
+7 -6
View File
@@ -10,6 +10,7 @@ import (
authlib "github.com/grafana/authlib/authn"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/authn"
@@ -108,21 +109,21 @@ func (s *ExtendedJWT) authenticateAsUser(
return nil, errExtJWTMisMatchedNamespaceClaims.Errorf("unexpected access token namespace: %s", accessTokenClaims.Rest.Namespace)
}
accessID, err := authn.ParseNamespaceID(accessTokenClaims.Subject)
accessID, err := identity.ParseTypedID(accessTokenClaims.Subject)
if err != nil {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessID.String())
}
if !accessID.IsNamespace(authn.NamespaceAccessPolicy) {
if !accessID.IsType(identity.TypeAccessPolicy) {
return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessID.String())
}
userID, err := authn.ParseNamespaceID(idTokenClaims.Subject)
userID, err := identity.ParseTypedID(idTokenClaims.Subject)
if err != nil {
return nil, errExtJWTInvalid.Errorf("failed to parse id token subject: %w", err)
}
if !userID.IsNamespace(authn.NamespaceUser) {
if !userID.IsType(identity.TypeUser) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", userID.String())
}
@@ -154,12 +155,12 @@ func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.Acces
return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected access token namespace: %s", claims.Rest.Namespace)
}
id, err := authn.ParseNamespaceID(claims.Subject)
id, err := identity.ParseTypedID(claims.Subject)
if err != nil {
return nil, fmt.Errorf("failed to parse access token subject: %w", err)
}
if !id.IsNamespace(authn.NamespaceAccessPolicy) {
if !id.IsType(identity.TypeAccessPolicy) {
return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", id.String())
}
+8 -7
View File
@@ -17,6 +17,7 @@ import (
authnlib "github.com/grafana/authlib/authn"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/setting"
)
@@ -207,8 +208,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
accessToken: &validAccessTokenClaims,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"),
ID: identity.MustParseTypedID("access-policy:this-uid"),
UID: identity.MustParseTypedID("access-policy:this-uid"),
OrgID: 1,
AllowedKubernetesNamespace: "default",
AuthenticatedBy: "extendedjwt",
@@ -223,8 +224,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
accessToken: &validAcessTokenClaimsWildcard,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("access-policy:this-uid"),
UID: authn.MustParseNamespaceID("access-policy:this-uid"),
ID: identity.MustParseTypedID("access-policy:this-uid"),
UID: identity.MustParseTypedID("access-policy:this-uid"),
OrgID: 1,
AllowedKubernetesNamespace: "*",
AuthenticatedBy: "extendedjwt",
@@ -240,7 +241,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
idToken: &validIDTokenClaims,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
OrgID: 1,
AllowedKubernetesNamespace: "default",
AuthenticatedBy: "extendedjwt",
@@ -260,7 +261,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
idToken: &validIDTokenClaims,
orgID: 1,
want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
OrgID: 1,
AllowedKubernetesNamespace: "*",
AuthenticatedBy: "extendedjwt",
@@ -285,7 +286,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
},
},
want: &authn.Identity{
ID: authn.MustParseNamespaceID("user:2"),
ID: identity.MustParseTypedID("user:2"),
OrgID: 1,
AllowedKubernetesNamespace: "stack-1234",
AuthenticatedBy: "extendedjwt",
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"errors"
"net/mail"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
@@ -105,7 +106,7 @@ func (c *Grafana) AuthenticatePassword(ctx context.Context, r *authn.Request, us
}
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, usr.ID),
ID: identity.NewTypedID(identity.TypeUser, usr.ID),
OrgID: r.OrgID,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
AuthenticatedBy: login.PasswordAuthModule,
+2 -1
View File
@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
@@ -140,7 +141,7 @@ func TestGrafana_AuthenticatePassword(t *testing.T) {
password: "password",
findUser: true,
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
OrgID: 1,
AuthenticatedBy: login.PasswordAuthModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
+3 -3
View File
@@ -245,10 +245,10 @@ func (c *OAuth) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redir
}, nil
}
func (c *OAuth) Logout(ctx context.Context, user authn.Requester) (*authn.Redirect, bool) {
func (c *OAuth) Logout(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) {
token := c.oauthService.GetCurrentOAuthToken(ctx, user)
namespace, id := user.GetNamespacedID()
namespace, id := user.GetTypedID()
userID, err := identity.UserIdentifier(namespace, id)
if err != nil {
c.log.FromContext(ctx).Error("Failed to parse user id", "namespace", namespace, "id", id, "error", err)
@@ -260,7 +260,7 @@ func (c *OAuth) Logout(ctx context.Context, user authn.Requester) (*authn.Redire
AuthId: user.GetAuthID(),
AuthModule: user.GetAuthenticatedBy(),
}); err != nil {
namespace, id := user.GetNamespacedID()
namespace, id := user.GetTypedID()
c.log.FromContext(ctx).Error("Failed to invalidate tokens", "namespace", namespace, "id", id, "error", err)
}
+5 -4
View File
@@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/services/loginattempt/loginattempttest"
@@ -29,16 +30,16 @@ func TestPassword_AuthenticatePassword(t *testing.T) {
username: "test",
password: "test",
req: &authn.Request{},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")}}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:1")},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}}},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")},
},
{
desc: "should success when found in second client",
username: "test",
password: "test",
req: &authn.Request{},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")}}},
expectedIdentity: &authn.Identity{ID: authn.MustParseNamespaceID("user:2")},
clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2")}}},
expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2")},
},
{
desc: "should fail for empty password",
+12 -11
View File
@@ -13,6 +13,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/services/authn"
@@ -124,7 +125,7 @@ func (c *Proxy) retrieveIDFromCache(ctx context.Context, cacheKey string, r *aut
}
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, uid),
ID: identity.NewTypedID(identity.TypeUser, uid),
OrgID: r.OrgID,
// FIXME: This does not match the actual auth module used, but should not have any impact
// Maybe caching the auth module used with the user ID would be a good idea
@@ -144,18 +145,18 @@ func (c *Proxy) Priority() uint {
return 50
}
func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Request) error {
if identity.ClientParams.CacheAuthProxyKey == "" {
func (c *Proxy) Hook(ctx context.Context, id *authn.Identity, r *authn.Request) error {
if id.ClientParams.CacheAuthProxyKey == "" {
return nil
}
if !identity.ID.IsNamespace(authn.NamespaceUser) {
if !id.ID.IsType(identity.TypeUser) {
return nil
}
id, err := identity.ID.ParseInt()
internalId, err := id.ID.ParseInt()
if err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", identity.ID.ID(), "err", err)
c.log.Warn("Failed to cache proxy user", "error", err, "userId", id.ID.ID(), "err", err)
return nil
}
@@ -175,15 +176,15 @@ func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Req
}
}
c.log.FromContext(ctx).Debug("Cache proxy user", "userId", id)
bytes := []byte(strconv.FormatInt(id, 10))
c.log.FromContext(ctx).Debug("Cache proxy user", "userId", internalId)
bytes := []byte(strconv.FormatInt(internalId, 10))
duration := time.Duration(c.cfg.AuthProxy.SyncTTL) * time.Minute
if err := c.cache.Set(ctx, identity.ClientParams.CacheAuthProxyKey, bytes, duration); err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", id)
if err := c.cache.Set(ctx, id.ClientParams.CacheAuthProxyKey, bytes, duration); err != nil {
c.log.Warn("Failed to cache proxy user", "error", err, "userId", internalId)
}
// store current cacheKey for the user
return c.cache.Set(ctx, userKey, []byte(identity.ClientParams.CacheAuthProxyKey), duration)
return c.cache.Set(ctx, userKey, []byte(id.ClientParams.CacheAuthProxyKey), duration)
}
func (c *Proxy) isAllowedIP(r *authn.Request) bool {
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/authn/authntest"
"github.com/grafana/grafana/pkg/setting"
@@ -203,7 +204,7 @@ func TestProxy_Hook(t *testing.T) {
}
cache := &fakeCache{data: make(map[string][]byte)}
userId := int64(1)
userID := authn.NewNamespaceID(authn.NamespaceUser, userId)
userID := identity.NewTypedID(identity.TypeUser, userId)
// withRole creates a test case for a user with a specific role.
withRole := func(role string) func(t *testing.T) {
+3 -2
View File
@@ -5,6 +5,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
@@ -42,7 +43,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
if renderUsr.UserID <= 0 {
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceRenderService, 0),
ID: identity.NewTypedID(identity.TypeRenderService, 0),
OrgID: renderUsr.OrgID,
OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)},
ClientParams: authn.ClientParams{SyncPermissions: true},
@@ -52,7 +53,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide
}
return &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, renderUsr.UserID),
ID: identity.NewTypedID(identity.TypeUser, renderUsr.UserID),
LastSeenAt: time.Now(),
AuthenticatedBy: login.RenderModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
+3 -2
View File
@@ -9,6 +9,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/org"
@@ -35,7 +36,7 @@ func TestRender_Authenticate(t *testing.T) {
},
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("render:0"),
ID: identity.MustParseTypedID("render:0"),
OrgID: 1,
OrgRoles: map[int64]org.RoleType{1: org.RoleViewer},
AuthenticatedBy: login.RenderModule,
@@ -56,7 +57,7 @@ func TestRender_Authenticate(t *testing.T) {
},
},
expectedIdentity: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
AuthenticatedBy: login.RenderModule,
ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true},
},
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"net/url"
"time"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/authn"
@@ -57,7 +58,7 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id
}
ident := &authn.Identity{
ID: authn.NewNamespaceID(authn.NamespaceUser, token.UserId),
ID: identity.NewTypedID(identity.TypeUser, token.UserId),
SessionToken: token,
ClientParams: authn.ClientParams{
FetchSyncedUser: true,
+5 -4
View File
@@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/models/usertoken"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/authtest"
@@ -96,7 +97,7 @@ func TestSession_Authenticate(t *testing.T) {
},
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
SessionToken: validToken,
ClientParams: authn.ClientParams{
SyncPermissions: true,
@@ -129,7 +130,7 @@ func TestSession_Authenticate(t *testing.T) {
},
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
SessionToken: validToken,
ClientParams: authn.ClientParams{
SyncPermissions: true,
@@ -148,7 +149,7 @@ func TestSession_Authenticate(t *testing.T) {
},
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
AuthID: "1",
AuthenticatedBy: "oauth_azuread",
SessionToken: validToken,
@@ -170,7 +171,7 @@ func TestSession_Authenticate(t *testing.T) {
},
args: args{r: &authn.Request{HTTPRequest: validHTTPReq}},
wantID: &authn.Identity{
ID: authn.MustParseNamespaceID("user:1"),
ID: identity.MustParseTypedID("user:1"),
SessionToken: validToken,
ClientParams: authn.ClientParams{
+15 -15
View File
@@ -15,16 +15,14 @@ import (
const GlobalOrgID = int64(0)
type Requester = identity.Requester
var _ Requester = (*Identity)(nil)
var _ identity.Requester = (*Identity)(nil)
type Identity struct {
// ID is the unique identifier for the entity in the Grafana database.
// If the entity is not found in the DB or this entity is non-persistent, this field will be empty.
ID NamespaceID
ID identity.TypedID
// UID is a unique identifier stored for the entity in Grafana database. Not all entities support uid so it can be empty.
UID NamespaceID
UID identity.TypedID
// OrgID is the active organization for the entity.
OrgID int64
// OrgName is the name of the active organization.
@@ -74,15 +72,15 @@ type Identity struct {
IDToken string
}
func (i *Identity) GetID() NamespaceID {
func (i *Identity) GetID() identity.TypedID {
return i.ID
}
func (i *Identity) GetNamespacedID() (namespace identity.Namespace, identifier string) {
return i.ID.Namespace(), i.ID.ID()
func (i *Identity) GetTypedID() (namespace identity.IdentityType, identifier string) {
return i.ID.Type(), i.ID.ID()
}
func (i *Identity) GetUID() NamespaceID {
func (i *Identity) GetUID() identity.TypedID {
return i.UID
}
@@ -95,7 +93,7 @@ func (i *Identity) GetAuthenticatedBy() string {
}
func (i *Identity) GetCacheKey() string {
namespace, id := i.GetNamespacedID()
namespace, id := i.GetTypedID()
if !i.HasUniqueId() {
// Hack use the org role as id for identities that do not have a unique id
// e.g. anonymous and render key.
@@ -191,8 +189,10 @@ func (i *Identity) HasRole(role org.RoleType) bool {
}
func (i *Identity) HasUniqueId() bool {
namespace, _ := i.GetNamespacedID()
return namespace == NamespaceUser || namespace == NamespaceServiceAccount || namespace == NamespaceAPIKey
namespace, _ := i.GetTypedID()
return namespace == identity.TypeUser ||
namespace == identity.TypeServiceAccount ||
namespace == identity.TypeAPIKey
}
func (i *Identity) IsAuthenticatedBy(providers ...string) bool {
@@ -220,7 +220,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
AuthID: i.AuthID,
AuthenticatedBy: i.AuthenticatedBy,
IsGrafanaAdmin: i.GetIsGrafanaAdmin(),
IsAnonymous: i.ID.IsNamespace(NamespaceAnonymous),
IsAnonymous: i.ID.IsType(identity.TypeAnonymous),
IsDisabled: i.IsDisabled,
HelpFlags1: i.HelpFlags1,
LastSeenAt: i.LastSeenAt,
@@ -230,14 +230,14 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
NamespacedID: i.ID,
}
if i.ID.IsNamespace(NamespaceAPIKey) {
if i.ID.IsType(identity.TypeAPIKey) {
id, _ := i.ID.ParseInt()
u.ApiKeyID = id
} else {
id, _ := i.ID.UserID()
u.UserID = id
u.UserUID = i.UID.ID()
u.IsServiceAccount = i.ID.IsNamespace(NamespaceServiceAccount)
u.IsServiceAccount = i.ID.IsType(identity.TypeServiceAccount)
}
return u
-27
View File
@@ -1,27 +0,0 @@
package authn
import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
)
const (
NamespaceUser = identity.NamespaceUser
NamespaceAPIKey = identity.NamespaceAPIKey
NamespaceServiceAccount = identity.NamespaceServiceAccount
NamespaceAnonymous = identity.NamespaceAnonymous
NamespaceRenderService = identity.NamespaceRenderService
NamespaceAccessPolicy = identity.NamespaceAccessPolicy
)
var AnonymousNamespaceID = NewNamespaceID(NamespaceAnonymous, 0)
type Namespace = identity.Namespace
type NamespaceID = identity.NamespaceID
var (
ParseNamespaceID = identity.ParseNamespaceID
MustParseNamespaceID = identity.MustParseNamespaceID
NewNamespaceID = identity.NewNamespaceID
NewNamespaceIDString = identity.NewNamespaceIDString
ErrInvalidNamespaceID = identity.ErrInvalidNamespaceID
)