IDForwarding: Add auth hook to generate id token (#75555)
* AuthN: Move identity struct to its own file * IDForwarding: Add IDToken property to usr and identity structs and add GetIDToken to requester interface * Inject IDService into background services * IDForwarding: Register post auth hook when feature toggle is enabled
This commit is contained in:
@@ -6,18 +6,12 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/middleware/cookies"
|
||||
"github.com/grafana/grafana/pkg/models/usertoken"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@@ -176,164 +170,6 @@ type Redirect struct {
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
const (
|
||||
NamespaceUser = identity.NamespaceUser
|
||||
NamespaceAPIKey = identity.NamespaceAPIKey
|
||||
NamespaceServiceAccount = identity.NamespaceServiceAccount
|
||||
NamespaceAnonymous = identity.NamespaceAnonymous
|
||||
NamespaceRenderService = identity.NamespaceRenderService
|
||||
)
|
||||
|
||||
type Identity struct {
|
||||
// OrgID is the active organization for the entity.
|
||||
OrgID int64
|
||||
// OrgName is the name of the active organization.
|
||||
OrgName string
|
||||
// OrgRoles is the list of organizations the entity is a member of and their roles.
|
||||
OrgRoles map[int64]org.RoleType
|
||||
// ID is the unique identifier for the entity in the Grafana database.
|
||||
// It is in the format <namespace>:<id> where namespace is one of the
|
||||
// Namespace* constants. For example, "user:1" or "api-key:1".
|
||||
// If the entity is not found in the DB or this entity is non-persistent, this field will be empty.
|
||||
ID string
|
||||
// IsAnonymous
|
||||
IsAnonymous bool
|
||||
// Login is the shorthand identifier of the entity. Should be unique.
|
||||
Login string
|
||||
// Name is the display name of the entity. It is not guaranteed to be unique.
|
||||
Name string
|
||||
// Email is the email address of the entity. Should be unique.
|
||||
Email string
|
||||
// IsGrafanaAdmin is true if the entity is a Grafana admin.
|
||||
IsGrafanaAdmin *bool
|
||||
// AuthenticatedBy is the name of the authentication client that was used to authenticate the current Identity.
|
||||
// For example, "password", "apikey", "auth_ldap" or "auth_azuread".
|
||||
AuthenticatedBy string
|
||||
// AuthId is the unique identifier for the entity in the external system.
|
||||
// Empty if the identity is provided by Grafana.
|
||||
AuthID string
|
||||
// IsDisabled is true if the entity is disabled.
|
||||
IsDisabled bool
|
||||
// HelpFlags1 is the help flags for the entity.
|
||||
HelpFlags1 user.HelpFlags1
|
||||
// LastSeenAt is the time when the entity was last seen.
|
||||
LastSeenAt time.Time
|
||||
// Teams is the list of teams the entity is a member of.
|
||||
Teams []int64
|
||||
// idP Groups that the entity is a member of. This is only populated if the
|
||||
// identity provider supports groups.
|
||||
Groups []string
|
||||
// OAuthToken is the OAuth token used to authenticate the entity.
|
||||
OAuthToken *oauth2.Token
|
||||
// SessionToken is the session token used to authenticate the entity.
|
||||
SessionToken *usertoken.UserToken
|
||||
// ClientParams are hints for the auth service on how to handle the identity.
|
||||
// Set by the authenticating client.
|
||||
ClientParams ClientParams
|
||||
// Permissions is the list of permissions the entity has.
|
||||
Permissions map[int64]map[string][]string
|
||||
}
|
||||
|
||||
// Role returns the role of the identity in the active organization.
|
||||
func (i *Identity) Role() org.RoleType {
|
||||
return i.OrgRoles[i.OrgID]
|
||||
}
|
||||
|
||||
// NamespacedID returns the namespace, e.g. "user" and the id for that namespace
|
||||
func (i *Identity) NamespacedID() (string, int64) {
|
||||
split := strings.Split(i.ID, ":")
|
||||
if len(split) != 2 {
|
||||
return "", -1
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(split[1], 10, 64)
|
||||
if err != nil {
|
||||
// FIXME (kalleep): Improve error handling
|
||||
return "", -1
|
||||
}
|
||||
|
||||
return split[0], id
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// SignedInUser returns a SignedInUser from the identity.
|
||||
func (i *Identity) SignedInUser() *user.SignedInUser {
|
||||
var isGrafanaAdmin bool
|
||||
if i.IsGrafanaAdmin != nil {
|
||||
isGrafanaAdmin = *i.IsGrafanaAdmin
|
||||
}
|
||||
|
||||
u := &user.SignedInUser{
|
||||
UserID: 0,
|
||||
OrgID: i.OrgID,
|
||||
OrgName: i.OrgName,
|
||||
OrgRole: i.Role(),
|
||||
Login: i.Login,
|
||||
Name: i.Name,
|
||||
Email: i.Email,
|
||||
AuthenticatedBy: i.AuthenticatedBy,
|
||||
IsGrafanaAdmin: isGrafanaAdmin,
|
||||
IsAnonymous: i.IsAnonymous,
|
||||
IsDisabled: i.IsDisabled,
|
||||
HelpFlags1: i.HelpFlags1,
|
||||
LastSeenAt: i.LastSeenAt,
|
||||
Teams: i.Teams,
|
||||
Permissions: i.Permissions,
|
||||
}
|
||||
|
||||
namespace, id := i.NamespacedID()
|
||||
if namespace == NamespaceAPIKey {
|
||||
u.ApiKeyID = id
|
||||
} else {
|
||||
u.UserID = id
|
||||
u.IsServiceAccount = namespace == NamespaceServiceAccount
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (i *Identity) ExternalUserInfo() login.ExternalUserInfo {
|
||||
_, id := i.NamespacedID()
|
||||
return login.ExternalUserInfo{
|
||||
OAuthToken: i.OAuthToken,
|
||||
AuthModule: i.AuthenticatedBy,
|
||||
AuthId: i.AuthID,
|
||||
UserId: id,
|
||||
Email: i.Email,
|
||||
Login: i.Login,
|
||||
Name: i.Name,
|
||||
Groups: i.Groups,
|
||||
OrgRoles: i.OrgRoles,
|
||||
IsGrafanaAdmin: i.IsGrafanaAdmin,
|
||||
IsDisabled: i.IsDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
// IdentityFromSignedInUser creates an identity from a SignedInUser.
|
||||
func IdentityFromSignedInUser(id string, usr *user.SignedInUser, params ClientParams, authenticatedBy string) *Identity {
|
||||
return &Identity{
|
||||
ID: id,
|
||||
OrgID: usr.OrgID,
|
||||
OrgName: usr.OrgName,
|
||||
OrgRoles: map[int64]org.RoleType{usr.OrgID: usr.OrgRole},
|
||||
Login: usr.Login,
|
||||
Name: usr.Name,
|
||||
Email: usr.Email,
|
||||
AuthenticatedBy: authenticatedBy,
|
||||
IsGrafanaAdmin: &usr.IsGrafanaAdmin,
|
||||
IsDisabled: usr.IsDisabled,
|
||||
HelpFlags1: usr.HelpFlags1,
|
||||
LastSeenAt: usr.LastSeenAt,
|
||||
Teams: usr.Teams,
|
||||
ClientParams: params,
|
||||
Permissions: usr.Permissions,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientWithPrefix returns a client name prefixed with "auth.client."
|
||||
func ClientWithPrefix(name string) string {
|
||||
return fmt.Sprintf("auth.client.%s", name)
|
||||
|
||||
@@ -10,7 +10,8 @@ var _ authn.Service = new(MockService)
|
||||
var _ authn.IdentitySynchronizer = new(MockService)
|
||||
|
||||
type MockService struct {
|
||||
SyncIdentityFunc func(ctx context.Context, identity *authn.Identity) error
|
||||
SyncIdentityFunc func(ctx context.Context, identity *authn.Identity) error
|
||||
RegisterPostAuthHookFunc func(hook authn.PostAuthHookFn, priority uint)
|
||||
}
|
||||
|
||||
func (m *MockService) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) {
|
||||
@@ -30,7 +31,9 @@ func (m *MockService) RegisterClient(c authn.Client) {
|
||||
}
|
||||
|
||||
func (m *MockService) RegisterPostAuthHook(hook authn.PostAuthHookFn, priority uint) {
|
||||
panic("unimplemented")
|
||||
if m.RegisterPostAuthHookFunc != nil {
|
||||
m.RegisterPostAuthHookFunc(hook, priority)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockService) RegisterPostLoginHook(hook authn.PostLoginHookFn, priority uint) {
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package authn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models/usertoken"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"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
|
||||
)
|
||||
|
||||
type Identity struct {
|
||||
// OrgID is the active organization for the entity.
|
||||
OrgID int64
|
||||
// OrgName is the name of the active organization.
|
||||
OrgName string
|
||||
// OrgRoles is the list of organizations the entity is a member of and their roles.
|
||||
OrgRoles map[int64]org.RoleType
|
||||
// ID is the unique identifier for the entity in the Grafana database.
|
||||
// It is in the format <namespace>:<id> where namespace is one of the
|
||||
// Namespace* constants. For example, "user:1" or "api-key:1".
|
||||
// If the entity is not found in the DB or this entity is non-persistent, this field will be empty.
|
||||
ID string
|
||||
// IsAnonymous
|
||||
IsAnonymous bool
|
||||
// Login is the shorthand identifier of the entity. Should be unique.
|
||||
Login string
|
||||
// Name is the display name of the entity. It is not guaranteed to be unique.
|
||||
Name string
|
||||
// Email is the email address of the entity. Should be unique.
|
||||
Email string
|
||||
// IsGrafanaAdmin is true if the entity is a Grafana admin.
|
||||
IsGrafanaAdmin *bool
|
||||
// AuthenticatedBy is the name of the authentication client that was used to authenticate the current Identity.
|
||||
// For example, "password", "apikey", "auth_ldap" or "auth_azuread".
|
||||
AuthenticatedBy string
|
||||
// AuthId is the unique identifier for the entity in the external system.
|
||||
// Empty if the identity is provided by Grafana.
|
||||
AuthID string
|
||||
// IsDisabled is true if the entity is disabled.
|
||||
IsDisabled bool
|
||||
// HelpFlags1 is the help flags for the entity.
|
||||
HelpFlags1 user.HelpFlags1
|
||||
// LastSeenAt is the time when the entity was last seen.
|
||||
LastSeenAt time.Time
|
||||
// Teams is the list of teams the entity is a member of.
|
||||
Teams []int64
|
||||
// idP Groups that the entity is a member of. This is only populated if the
|
||||
// identity provider supports groups.
|
||||
Groups []string
|
||||
// OAuthToken is the OAuth token used to authenticate the entity.
|
||||
OAuthToken *oauth2.Token
|
||||
// SessionToken is the session token used to authenticate the entity.
|
||||
SessionToken *usertoken.UserToken
|
||||
// ClientParams are hints for the auth service on how to handle the identity.
|
||||
// Set by the authenticating client.
|
||||
ClientParams ClientParams
|
||||
// Permissions is the list of permissions the entity has.
|
||||
Permissions map[int64]map[string][]string
|
||||
// IDToken is a signed token representing the identity that can be forwarded to plugins and external services.
|
||||
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
|
||||
IDToken string
|
||||
}
|
||||
|
||||
// Role returns the role of the identity in the active organization.
|
||||
func (i *Identity) Role() org.RoleType {
|
||||
return i.OrgRoles[i.OrgID]
|
||||
}
|
||||
|
||||
// NamespacedID returns the namespace, e.g. "user" and the id for that namespace
|
||||
func (i *Identity) NamespacedID() (string, int64) {
|
||||
split := strings.Split(i.ID, ":")
|
||||
if len(split) != 2 {
|
||||
return "", -1
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(split[1], 10, 64)
|
||||
if err != nil {
|
||||
// FIXME (kalleep): Improve error handling
|
||||
return "", -1
|
||||
}
|
||||
|
||||
return split[0], id
|
||||
}
|
||||
|
||||
// SignedInUser returns a SignedInUser from the identity.
|
||||
func (i *Identity) SignedInUser() *user.SignedInUser {
|
||||
var isGrafanaAdmin bool
|
||||
if i.IsGrafanaAdmin != nil {
|
||||
isGrafanaAdmin = *i.IsGrafanaAdmin
|
||||
}
|
||||
|
||||
u := &user.SignedInUser{
|
||||
UserID: 0,
|
||||
OrgID: i.OrgID,
|
||||
OrgName: i.OrgName,
|
||||
OrgRole: i.Role(),
|
||||
Login: i.Login,
|
||||
Name: i.Name,
|
||||
Email: i.Email,
|
||||
AuthenticatedBy: i.AuthenticatedBy,
|
||||
IsGrafanaAdmin: isGrafanaAdmin,
|
||||
IsAnonymous: i.IsAnonymous,
|
||||
IsDisabled: i.IsDisabled,
|
||||
HelpFlags1: i.HelpFlags1,
|
||||
LastSeenAt: i.LastSeenAt,
|
||||
Teams: i.Teams,
|
||||
Permissions: i.Permissions,
|
||||
IDToken: i.IDToken,
|
||||
}
|
||||
|
||||
namespace, id := i.NamespacedID()
|
||||
if namespace == NamespaceAPIKey {
|
||||
u.ApiKeyID = id
|
||||
} else {
|
||||
u.UserID = id
|
||||
u.IsServiceAccount = namespace == NamespaceServiceAccount
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (i *Identity) ExternalUserInfo() login.ExternalUserInfo {
|
||||
_, id := i.NamespacedID()
|
||||
return login.ExternalUserInfo{
|
||||
OAuthToken: i.OAuthToken,
|
||||
AuthModule: i.AuthenticatedBy,
|
||||
AuthId: i.AuthID,
|
||||
UserId: id,
|
||||
Email: i.Email,
|
||||
Login: i.Login,
|
||||
Name: i.Name,
|
||||
Groups: i.Groups,
|
||||
OrgRoles: i.OrgRoles,
|
||||
IsGrafanaAdmin: i.IsGrafanaAdmin,
|
||||
IsDisabled: i.IsDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
// IdentityFromSignedInUser creates an identity from a SignedInUser.
|
||||
func IdentityFromSignedInUser(id string, usr *user.SignedInUser, params ClientParams, authenticatedBy string) *Identity {
|
||||
return &Identity{
|
||||
ID: id,
|
||||
OrgID: usr.OrgID,
|
||||
OrgName: usr.OrgName,
|
||||
OrgRoles: map[int64]org.RoleType{usr.OrgID: usr.OrgRole},
|
||||
Login: usr.Login,
|
||||
Name: usr.Name,
|
||||
Email: usr.Email,
|
||||
AuthenticatedBy: authenticatedBy,
|
||||
IsGrafanaAdmin: &usr.IsGrafanaAdmin,
|
||||
IsDisabled: usr.IsDisabled,
|
||||
HelpFlags1: usr.HelpFlags1,
|
||||
LastSeenAt: usr.LastSeenAt,
|
||||
Teams: usr.Teams,
|
||||
ClientParams: params,
|
||||
Permissions: usr.Permissions,
|
||||
IDToken: usr.IDToken,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user