Authn: Add function to resolve identity from org and namespace id (#84555)

* Add function to get the namespaced id

* Add function to resolve an identity through authn.Service from org and namespace id

* Switch to resolve identity for re-authenticate in another org
This commit is contained in:
Karl Persson
2024-03-15 15:08:15 +01:00
committed by GitHub
parent ced09883d3
commit d4e802dd47
9 changed files with 101 additions and 30 deletions

View File

@@ -20,6 +20,11 @@ var ErrNotIntIdentifier = errors.New("identifier is not an int64")
var ErrIdentifierNotInitialized = errors.New("identifier is not initialized")
type Requester interface {
// GetID returns namespaced id for the entity
GetID() string
// GetNamespacedID returns the namespace and ID of the active entity.
// The namespace is one of the constants defined in pkg/services/auth/identity.
GetNamespacedID() (namespace string, identifier string)
// GetDisplayName returns the display name of the active entity.
// The display name is the name if it is set, otherwise the login or email.
GetDisplayName() string
@@ -31,9 +36,6 @@ type Requester interface {
// GetLogin returns the login of the active entity
// Can be empty.
GetLogin() string
// GetNamespacedID returns the namespace and ID of the active entity.
// The namespace is one of the constants defined in pkg/services/auth/identity.
GetNamespacedID() (namespace string, identifier string)
// GetOrgID returns the ID of the active organization
GetOrgID() int64
// GetOrgRole returns the role of the active entity in the active organization.

View File

@@ -77,6 +77,10 @@ type Service interface {
RedirectURL(ctx context.Context, client string, r *Request) (*Redirect, error)
// Logout revokes session token and does additional clean up if client used to authenticate supports it
Logout(ctx context.Context, user identity.Requester, sessionToken *usertoken.UserToken) (*Redirect, error)
// ResolveIdentity resolves an identity from org and namespace id.
ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*Identity, error)
// RegisterClient will register a new authn.Client that can be used for authentication
RegisterClient(c Client)
}

View File

@@ -391,6 +391,20 @@ Default:
return redirect, nil
}
func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*authn.Identity, error) {
r := &authn.Request{}
r.OrgID = orgID
// hack to not update last seen
r.SetMeta(authn.MetaKeyIsLogin, "true")
identity, err := s.authenticate(ctx, clients.ProvideIdentity(namespaceID), r)
if err != nil {
return nil, err
}
return identity, nil
}
func (s *Service) RegisterClient(c authn.Client) {
s.clients[c.Name()] = c
if cac, ok := c.(authn.ContextAwareClient); ok {

View File

@@ -68,7 +68,11 @@ func (f *FakeService) RedirectURL(ctx context.Context, client string, r *authn.R
return f.ExpectedRedirect, f.ExpectedErr
}
func (*FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertoken.UserToken) (*authn.Redirect, error) {
func (f *FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertoken.UserToken) (*authn.Redirect, error) {
panic("unimplemented")
}
func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*authn.Identity, error) {
panic("unimplemented")
}

View File

@@ -47,6 +47,10 @@ func (*MockService) Logout(_ context.Context, _ identity.Requester, _ *usertoken
panic("unimplemented")
}
func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID string) (*authn.Identity, error) {
panic("unimplemented")
}
func (m *MockService) SyncIdentity(ctx context.Context, identity *authn.Identity) error {
if m.SyncIdentityFunc != nil {
return m.SyncIdentityFunc(ctx, identity)

View File

@@ -0,0 +1,33 @@
package clients
import (
"context"
"github.com/grafana/grafana/pkg/services/authn"
)
var _ authn.Client = (*IdentityClient)(nil)
func ProvideIdentity(namespaceID string) *IdentityClient {
return &IdentityClient{namespaceID}
}
type IdentityClient struct {
namespaceID string
}
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{
FetchSyncedUser: true,
SyncPermissions: true,
},
}, nil
}

View File

@@ -87,6 +87,18 @@ type Identity struct {
IDToken string
}
func (i *Identity) GetID() string {
return i.ID
}
func (i *Identity) GetNamespacedID() (namespace string, identifier string) {
split := strings.Split(i.GetID(), ":")
if len(split) != 2 {
return "", ""
}
return split[0], split[1]
}
func (i *Identity) GetAuthenticatedBy() string {
return i.AuthenticatedBy
}
@@ -122,16 +134,6 @@ func (i *Identity) GetLogin() string {
return i.Login
}
func (i *Identity) GetNamespacedID() (namespace string, identifier string) {
split := strings.Split(i.ID, ":")
if len(split) != 2 {
return "", ""
}
return split[0], split[1]
}
// GetOrgID implements identity.Requester.
func (i *Identity) GetOrgID() int64 {
return i.OrgID

View File

@@ -2,6 +2,7 @@ package user
import (
"fmt"
"strings"
"time"
"github.com/grafana/grafana/pkg/models/roletype"
@@ -189,28 +190,31 @@ func (u *SignedInUser) GetOrgRole() roletype.RoleType {
return u.OrgRole
}
// GetNamespacedID returns the namespace and ID of the active entity
// The namespace is one of the constants defined in pkg/services/auth/identity
func (u *SignedInUser) GetNamespacedID() (string, string) {
// GetID returns namespaced id for the entity
func (u *SignedInUser) GetID() string {
switch {
case u.ApiKeyID != 0:
return identity.NamespaceAPIKey, fmt.Sprintf("%d", u.ApiKeyID)
return namespacedID(identity.NamespaceAPIKey, u.ApiKeyID)
case u.IsServiceAccount:
return identity.NamespaceServiceAccount, fmt.Sprintf("%d", u.UserID)
return namespacedID(identity.NamespaceServiceAccount, u.UserID)
case u.UserID > 0:
return identity.NamespaceUser, fmt.Sprintf("%d", u.UserID)
return namespacedID(identity.NamespaceUser, u.UserID)
case u.IsAnonymous:
return identity.NamespaceAnonymous, ""
case u.AuthenticatedBy == "render": //import cycle render
if u.UserID == 0 {
return identity.NamespaceRenderService, "0"
} else { // this should never happen as u.UserID > 0 already catches this
return identity.NamespaceUser, fmt.Sprintf("%d", u.UserID)
}
return identity.NamespaceAnonymous + ":"
case u.AuthenticatedBy == "render" && u.UserID == 0:
return namespacedID(identity.NamespaceRenderService, 0)
}
// backwards compatibility
return identity.NamespaceUser, fmt.Sprintf("%d", u.UserID)
return namespacedID(identity.NamespaceUser, u.UserID)
}
// GetNamespacedID returns the namespace and ID of the active entity
// The namespace is one of the constants defined in pkg/services/auth/identity
func (u *SignedInUser) GetNamespacedID() (string, string) {
parts := strings.Split(u.GetID(), ":")
// Safety: GetID always returns a ':' separated string
return parts[0], parts[1]
}
// FIXME: remove this method once all services are using an interface
@@ -238,3 +242,7 @@ func (u *SignedInUser) GetAuthenticatedBy() string {
func (u *SignedInUser) GetIDToken() string {
return u.IDToken
}
func namespacedID(namespace string, id int64) string {
return fmt.Sprintf("%s:%d", namespace, id)
}