mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 19:00:54 -06:00
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:
parent
3a1c3be057
commit
b9b4246432
@ -62,7 +62,7 @@ func ProvideBackgroundServiceRegistry(
|
||||
_ serviceaccounts.Service, _ *guardian.Provider,
|
||||
_ *plugindashboardsservice.DashboardUpdater, _ *sanitizer.Provider,
|
||||
_ *grpcserver.HealthService, _ entity.EntityStoreServer, _ *grpcserver.ReflectionService, _ *ldapapi.Service,
|
||||
_ *apiregistry.Service,
|
||||
_ *apiregistry.Service, _ auth.IDService,
|
||||
) *BackgroundServiceRegistry {
|
||||
return NewBackgroundServiceRegistry(
|
||||
httpServer,
|
||||
|
@ -62,6 +62,9 @@ type Requester interface {
|
||||
HasUniqueId() bool
|
||||
// AuthenticatedBy returns the authentication method used to authenticate the entity.
|
||||
GetAuthenticatedBy() string
|
||||
// GetIDToken returns a signed token representing the identity that can be forwarded to plugins and external services.
|
||||
// Will only be set when featuremgmt.FlagIdForwarding is enabled.
|
||||
GetIDToken() string
|
||||
}
|
||||
|
||||
// IntIdentifier converts a string identifier to an int64.
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -22,8 +24,17 @@ const (
|
||||
|
||||
var _ auth.IDService = (*Service)(nil)
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, signer auth.IDSigner, cache remotecache.CacheStorage) *Service {
|
||||
return &Service{cfg, log.New("id-service"), signer, cache}
|
||||
func ProvideService(
|
||||
cfg *setting.Cfg, signer auth.IDSigner, cache remotecache.CacheStorage,
|
||||
features featuremgmt.FeatureToggles, authnService authn.Service,
|
||||
) *Service {
|
||||
s := &Service{cfg, log.New("id-service"), signer, cache}
|
||||
|
||||
if features.IsEnabled(featuremgmt.FlagIdForwarding) {
|
||||
authnService.RegisterPostAuthHook(s.hook, 140)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
@ -46,7 +57,6 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
|
||||
s.logger.Debug("Sign new id token", "namespace", namespace, "id", identifier)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
token, err := s.signer.SignIDToken(ctx, &auth.IDClaims{
|
||||
Claims: jwt.Claims{
|
||||
ID: identifier,
|
||||
@ -69,6 +79,21 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (s *Service) hook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
|
||||
// FIXME(kalleep): implement identity.Requester for authn.Identity
|
||||
// FIXME(kalleep): we should probably lazy load this
|
||||
token, err := s.SignIdentity(ctx, identity.SignedInUser())
|
||||
if err != nil {
|
||||
namespace, id := identity.NamespacedID()
|
||||
s.logger.Error("Failed to sign id token", "err", err, "namespace", namespace, "id", id)
|
||||
// for now don't return error so we don't break authentication from this hook
|
||||
return nil
|
||||
}
|
||||
|
||||
identity.IDToken = token
|
||||
return nil
|
||||
}
|
||||
|
||||
func prefixCacheKey(key string) string {
|
||||
return fmt.Sprintf("%s-%s", cachePrefix, key)
|
||||
}
|
||||
|
41
pkg/services/auth/idimpl/service_test.go
Normal file
41
pkg/services/auth/idimpl/service_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
package idimpl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_ProvideService(t *testing.T) {
|
||||
t.Run("should register post auth hook when feature flag is enabled", func(t *testing.T) {
|
||||
features := featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding)
|
||||
|
||||
var hookRegistered bool
|
||||
authnService := &authntest.MockService{
|
||||
RegisterPostAuthHookFunc: func(_ authn.PostAuthHookFn, _ uint) {
|
||||
hookRegistered = true
|
||||
},
|
||||
}
|
||||
|
||||
_ = ProvideService(setting.NewCfg(), nil, nil, features, authnService)
|
||||
assert.True(t, hookRegistered)
|
||||
})
|
||||
|
||||
t.Run("should not register post auth hook when feature flag is disabled", func(t *testing.T) {
|
||||
features := featuremgmt.WithFeatures()
|
||||
|
||||
var hookRegistered bool
|
||||
authnService := &authntest.MockService{
|
||||
RegisterPostAuthHookFunc: func(_ authn.PostAuthHookFn, _ uint) {
|
||||
hookRegistered = true
|
||||
},
|
||||
}
|
||||
|
||||
_ = ProvideService(setting.NewCfg(), nil, nil, features, authnService)
|
||||
assert.False(t, hookRegistered)
|
||||
})
|
||||
}
|
@ -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) {
|
||||
|
179
pkg/services/authn/identity.go
Normal file
179
pkg/services/authn/identity.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
@ -27,6 +27,9 @@ type SignedInUser struct {
|
||||
Teams []int64
|
||||
// Permissions grouped by orgID and actions
|
||||
Permissions map[int64]map[string][]string `json:"-"`
|
||||
// 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 `json:"-" xorm:"-"`
|
||||
}
|
||||
|
||||
func (u *SignedInUser) ShouldUpdateLastSeenAt() bool {
|
||||
@ -210,3 +213,7 @@ func (u *SignedInUser) GetDisplayName() string {
|
||||
func (u *SignedInUser) GetAuthenticatedBy() string {
|
||||
return u.AuthenticatedBy
|
||||
}
|
||||
|
||||
func (u *SignedInUser) GetIDToken() string {
|
||||
return u.IDToken
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user