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
pull/83163/head^2
Karl Persson 2 years ago committed by GitHub
parent ced09883d3
commit d4e802dd47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      pkg/api/org_users.go
  2. 8
      pkg/services/auth/identity/requester.go
  3. 4
      pkg/services/authn/authn.go
  4. 14
      pkg/services/authn/authnimpl/service.go
  5. 6
      pkg/services/authn/authntest/fake.go
  6. 4
      pkg/services/authn/authntest/mock.go
  7. 33
      pkg/services/authn/clients/identity.go
  8. 22
      pkg/services/authn/identity.go
  9. 36
      pkg/services/user/identity.go

@ -326,11 +326,11 @@ func (hs *HTTPServer) searchOrgUsersHelper(c *contextmodel.ReqContext, query *or
if c.QueryBool("accesscontrol") { if c.QueryBool("accesscontrol") {
permissions := c.SignedInUser.GetPermissions() permissions := c.SignedInUser.GetPermissions()
if query.OrgID != c.SignedInUser.GetOrgID() { if query.OrgID != c.SignedInUser.GetOrgID() {
permissionsList, err := hs.accesscontrolService.GetUserPermissionsInOrg(c.Req.Context(), c.SignedInUser, query.OrgID) identity, err := hs.authnService.ResolveIdentity(c.Req.Context(), query.OrgID, c.SignedInUser.GetID())
if err != nil { if err != nil {
return nil, err return nil, err
} }
permissions = accesscontrol.GroupScopesByAction(permissionsList) permissions = identity.GetPermissions()
} }
accessControlMetadata = accesscontrol.GetResourcesMetadata(c.Req.Context(), permissions, "users:id:", userIDs) accessControlMetadata = accesscontrol.GetResourcesMetadata(c.Req.Context(), permissions, "users:id:", userIDs)
} }

@ -20,6 +20,11 @@ var ErrNotIntIdentifier = errors.New("identifier is not an int64")
var ErrIdentifierNotInitialized = errors.New("identifier is not initialized") var ErrIdentifierNotInitialized = errors.New("identifier is not initialized")
type Requester interface { 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. // GetDisplayName returns the display name of the active entity.
// The display name is the name if it is set, otherwise the login or email. // The display name is the name if it is set, otherwise the login or email.
GetDisplayName() string GetDisplayName() string
@ -31,9 +36,6 @@ type Requester interface {
// GetLogin returns the login of the active entity // GetLogin returns the login of the active entity
// Can be empty. // Can be empty.
GetLogin() string 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 returns the ID of the active organization
GetOrgID() int64 GetOrgID() int64
// GetOrgRole returns the role of the active entity in the active organization. // GetOrgRole returns the role of the active entity in the active organization.

@ -77,6 +77,10 @@ type Service interface {
RedirectURL(ctx context.Context, client string, r *Request) (*Redirect, error) 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 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) 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 will register a new authn.Client that can be used for authentication
RegisterClient(c Client) RegisterClient(c Client)
} }

@ -391,6 +391,20 @@ Default:
return redirect, nil 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) { func (s *Service) RegisterClient(c authn.Client) {
s.clients[c.Name()] = c s.clients[c.Name()] = c
if cac, ok := c.(authn.ContextAwareClient); ok { if cac, ok := c.(authn.ContextAwareClient); ok {

@ -68,7 +68,11 @@ func (f *FakeService) RedirectURL(ctx context.Context, client string, r *authn.R
return f.ExpectedRedirect, f.ExpectedErr 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") panic("unimplemented")
} }

@ -47,6 +47,10 @@ func (*MockService) Logout(_ context.Context, _ identity.Requester, _ *usertoken
panic("unimplemented") 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 { func (m *MockService) SyncIdentity(ctx context.Context, identity *authn.Identity) error {
if m.SyncIdentityFunc != nil { if m.SyncIdentityFunc != nil {
return m.SyncIdentityFunc(ctx, identity) return m.SyncIdentityFunc(ctx, identity)

@ -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
}

@ -87,6 +87,18 @@ type Identity struct {
IDToken string 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 { func (i *Identity) GetAuthenticatedBy() string {
return i.AuthenticatedBy return i.AuthenticatedBy
} }
@ -122,16 +134,6 @@ func (i *Identity) GetLogin() string {
return i.Login 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. // GetOrgID implements identity.Requester.
func (i *Identity) GetOrgID() int64 { func (i *Identity) GetOrgID() int64 {
return i.OrgID return i.OrgID

@ -2,6 +2,7 @@ package user
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"github.com/grafana/grafana/pkg/models/roletype" "github.com/grafana/grafana/pkg/models/roletype"
@ -189,28 +190,31 @@ func (u *SignedInUser) GetOrgRole() roletype.RoleType {
return u.OrgRole return u.OrgRole
} }
// GetNamespacedID returns the namespace and ID of the active entity // GetID returns namespaced id for the entity
// The namespace is one of the constants defined in pkg/services/auth/identity func (u *SignedInUser) GetID() string {
func (u *SignedInUser) GetNamespacedID() (string, string) {
switch { switch {
case u.ApiKeyID != 0: case u.ApiKeyID != 0:
return identity.NamespaceAPIKey, fmt.Sprintf("%d", u.ApiKeyID) return namespacedID(identity.NamespaceAPIKey, u.ApiKeyID)
case u.IsServiceAccount: case u.IsServiceAccount:
return identity.NamespaceServiceAccount, fmt.Sprintf("%d", u.UserID) return namespacedID(identity.NamespaceServiceAccount, u.UserID)
case u.UserID > 0: case u.UserID > 0:
return identity.NamespaceUser, fmt.Sprintf("%d", u.UserID) return namespacedID(identity.NamespaceUser, u.UserID)
case u.IsAnonymous: case u.IsAnonymous:
return identity.NamespaceAnonymous, "" return identity.NamespaceAnonymous + ":"
case u.AuthenticatedBy == "render": //import cycle render case u.AuthenticatedBy == "render" && u.UserID == 0:
if u.UserID == 0 { return namespacedID(identity.NamespaceRenderService, 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)
}
} }
// backwards compatibility // 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 // 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 { func (u *SignedInUser) GetIDToken() string {
return u.IDToken return u.IDToken
} }
func namespacedID(namespace string, id int64) string {
return fmt.Sprintf("%s:%d", namespace, id)
}

Loading…
Cancel
Save