AuthN: Set uid during authentication (#87797)
* Identity: Remove GetNamespacedUID and use GetUID instead * Authn: Set uid for users and service accounts
This commit is contained in:
@@ -12,12 +12,10 @@ type Requester interface {
|
||||
GetID() NamespaceID
|
||||
// GetNamespacedID returns the namespace and ID of the active entity.
|
||||
// The namespace is one of the constants defined in pkg/services/auth/identity.
|
||||
// Deprecated: use GetID instead
|
||||
GetNamespacedID() (namespace Namespace, identifier string)
|
||||
// GetID returns namespaced id for the entity
|
||||
// GetUID returns namespaced uid for the entity
|
||||
GetUID() NamespaceID
|
||||
// GetNamespacedID returns the namespace and ID of the active entity.
|
||||
// The namespace is one of the constants defined in pkg/services/auth/identity.
|
||||
GetNamespacedUID() (namespace Namespace, 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
|
||||
|
||||
@@ -364,7 +364,7 @@ func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupPar
|
||||
var err error
|
||||
|
||||
// If not found, try to find the user by email address
|
||||
if usr == nil && params.Email != nil && *params.Email != "" {
|
||||
if params.Email != nil && *params.Email != "" {
|
||||
usr, err = s.userService.GetByEmail(ctx, &user.GetUserByEmailQuery{Email: *params.Email})
|
||||
if err != nil && !errors.Is(err, user.ErrUserNotFound) {
|
||||
return nil, err
|
||||
@@ -390,6 +390,7 @@ func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupPar
|
||||
// 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.Login = usr.Login
|
||||
id.Email = usr.Email
|
||||
id.Name = usr.Name
|
||||
@@ -399,6 +400,14 @@ 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
|
||||
} else {
|
||||
ns = authn.NamespaceUser
|
||||
}
|
||||
identity.UID = authn.NewNamespaceIDString(ns, usr.UserUID)
|
||||
|
||||
identity.Name = usr.Name
|
||||
identity.Login = usr.Login
|
||||
identity.Email = usr.Email
|
||||
|
||||
@@ -47,6 +47,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
|
||||
userService := &usertest.FakeUserService{ExpectedUser: &user.User{
|
||||
ID: 1,
|
||||
UID: "1",
|
||||
Login: "test",
|
||||
Name: "test",
|
||||
Email: "test",
|
||||
@@ -54,6 +55,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
|
||||
userServiceMod := &usertest.FakeUserService{ExpectedUser: &user.User{
|
||||
ID: 3,
|
||||
UID: "3",
|
||||
Login: "test",
|
||||
Name: "test",
|
||||
Email: "test",
|
||||
@@ -63,6 +65,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
|
||||
userServiceEmailMod := &usertest.FakeUserService{ExpectedUser: &user.User{
|
||||
ID: 3,
|
||||
UID: "3",
|
||||
Login: "test",
|
||||
Name: "test",
|
||||
Email: "test@test.com",
|
||||
@@ -76,6 +79,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
CreateFn: func(ctx context.Context, cmd *user.CreateUserCommand) (*user.User, error) {
|
||||
return &user.User{
|
||||
ID: 2,
|
||||
UID: "2",
|
||||
Login: cmd.Login,
|
||||
Name: cmd.Name,
|
||||
Email: cmd.Email,
|
||||
@@ -159,6 +163,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
wantErr: false,
|
||||
wantID: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("user:1"),
|
||||
UID: authn.MustParseNamespaceID("user:1"),
|
||||
Login: "test",
|
||||
Name: "test",
|
||||
Email: "test",
|
||||
@@ -197,6 +202,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
wantErr: false,
|
||||
wantID: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("user:1"),
|
||||
UID: authn.MustParseNamespaceID("user:1"),
|
||||
Login: "test",
|
||||
Name: "test",
|
||||
Email: "test",
|
||||
@@ -237,6 +243,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
wantErr: false,
|
||||
wantID: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("user:1"),
|
||||
UID: authn.MustParseNamespaceID("user:1"),
|
||||
AuthID: "2032",
|
||||
AuthenticatedBy: "oauth",
|
||||
Login: "test",
|
||||
@@ -308,6 +315,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
wantErr: false,
|
||||
wantID: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("user:2"),
|
||||
UID: authn.MustParseNamespaceID("user:2"),
|
||||
Login: "test_create",
|
||||
Name: "test_create",
|
||||
Email: "test_create",
|
||||
@@ -353,6 +361,7 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
wantErr: false,
|
||||
wantID: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("user:3"),
|
||||
UID: authn.MustParseNamespaceID("user:3"),
|
||||
Login: "test_mod",
|
||||
Name: "test_mod",
|
||||
Email: "test_mod",
|
||||
@@ -397,8 +406,9 @@ func TestUserSync_SyncUserHook(t *testing.T) {
|
||||
wantErr: false,
|
||||
wantID: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("user:3"),
|
||||
Login: "test",
|
||||
UID: authn.MustParseNamespaceID("user:3"),
|
||||
Name: "test",
|
||||
Login: "test",
|
||||
Email: "test_mod@test.com",
|
||||
IsDisabled: false,
|
||||
EmailVerified: false,
|
||||
|
||||
@@ -149,6 +149,7 @@ func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.Acces
|
||||
|
||||
return &authn.Identity{
|
||||
ID: id,
|
||||
UID: id,
|
||||
OrgID: s.getDefaultOrgID(),
|
||||
AuthenticatedBy: login.ExtendedJWTModule,
|
||||
AuthID: claims.Subject,
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/go-jose/go-jose/v3/jwt"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -19,7 +18,6 @@ import (
|
||||
authlib "github.com/grafana/authlib/authn"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
"github.com/grafana/grafana/pkg/models/usertoken"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/signingkeys"
|
||||
"github.com/grafana/grafana/pkg/services/signingkeys/signingkeystest"
|
||||
@@ -209,20 +207,16 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
name: "successful authentication as service",
|
||||
payload: &validPayload,
|
||||
orgID: 1,
|
||||
want: &authn.Identity{OrgID: 1, OrgName: "",
|
||||
OrgRoles: map[int64]roletype.RoleType(nil),
|
||||
ID: authn.MustParseNamespaceID("access-policy:this-uid"), Login: "", Name: "", Email: "",
|
||||
IsGrafanaAdmin: (*bool)(nil), AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid", IsDisabled: false, HelpFlags1: 0x0,
|
||||
LastSeenAt: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
Teams: []int64(nil), Groups: []string(nil),
|
||||
OAuthToken: (*oauth2.Token)(nil), SessionToken: (*usertoken.UserToken)(nil),
|
||||
ClientParams: authn.ClientParams{SyncUser: false,
|
||||
AllowSignUp: false, EnableUser: false, FetchSyncedUser: false,
|
||||
SyncTeams: false, SyncOrgRoles: false, CacheAuthProxyKey: "",
|
||||
want: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("access-policy:this-uid"),
|
||||
UID: authn.MustParseNamespaceID("access-policy:this-uid"),
|
||||
OrgID: 1,
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
ClientParams: authn.ClientParams{
|
||||
SyncPermissions: true,
|
||||
FetchPermissionsParams: authn.FetchPermissionsParams{ActionsLookup: []string(nil), Roles: []string{"fixed:folders:reader"}}},
|
||||
Permissions: map[int64]map[string][]string(nil), IDToken: ""},
|
||||
FetchPermissionsParams: authn.FetchPermissionsParams{Roles: []string{"fixed:folders:reader"}}},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
@@ -240,21 +234,19 @@ func TestExtendedJWT_Authenticate(t *testing.T) {
|
||||
Login: "johndoe",
|
||||
}
|
||||
},
|
||||
want: &authn.Identity{OrgID: 1, OrgName: "",
|
||||
OrgRoles: map[int64]roletype.RoleType(nil), ID: authn.MustParseNamespaceID("user:2"),
|
||||
Login: "", Name: "", Email: "",
|
||||
IsGrafanaAdmin: (*bool)(nil), AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid", IsDisabled: false, HelpFlags1: 0x0,
|
||||
LastSeenAt: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
Teams: []int64(nil), Groups: []string(nil),
|
||||
OAuthToken: (*oauth2.Token)(nil), SessionToken: (*usertoken.UserToken)(nil),
|
||||
ClientParams: authn.ClientParams{SyncUser: false, AllowSignUp: false,
|
||||
EnableUser: false, FetchSyncedUser: true, SyncTeams: false,
|
||||
SyncOrgRoles: false, CacheAuthProxyKey: "",
|
||||
want: &authn.Identity{
|
||||
ID: authn.MustParseNamespaceID("user:2"),
|
||||
OrgID: 1,
|
||||
AuthenticatedBy: "extendedjwt",
|
||||
AuthID: "access-policy:this-uid",
|
||||
ClientParams: authn.ClientParams{
|
||||
FetchSyncedUser: true,
|
||||
SyncPermissions: true,
|
||||
FetchPermissionsParams: authn.FetchPermissionsParams{ActionsLookup: []string{"dashboards:create",
|
||||
"folders:read", "datasources:explore", "datasources.insights:read"},
|
||||
Roles: []string(nil)}}, Permissions: map[int64]map[string][]string(nil), IDToken: ""},
|
||||
FetchPermissionsParams: authn.FetchPermissionsParams{
|
||||
ActionsLookup: []string{"dashboards:create", "folders:read", "datasources:explore", "datasources.insights:read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@ package authn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
@@ -22,10 +21,10 @@ var _ Requester = (*Identity)(nil)
|
||||
|
||||
type Identity struct {
|
||||
// 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 NamespaceID
|
||||
// UID is a unique identifier stored for the entity in Grafana database. Not all entities support uid so it can be empty.
|
||||
UID NamespaceID
|
||||
// OrgID is the active organization for the entity.
|
||||
OrgID int64
|
||||
// OrgName is the name of the active organization.
|
||||
@@ -71,8 +70,6 @@ type Identity struct {
|
||||
// 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
|
||||
// UserUID is the unique identifier for the entity in the Grafana database.
|
||||
UserUID string
|
||||
}
|
||||
|
||||
func (i *Identity) GetID() NamespaceID {
|
||||
@@ -84,12 +81,7 @@ func (i *Identity) GetNamespacedID() (namespace identity.Namespace, identifier s
|
||||
}
|
||||
|
||||
func (i *Identity) GetUID() NamespaceID {
|
||||
ns, uid := i.GetNamespacedUID()
|
||||
return identity.NewNamespaceIDString(ns, uid)
|
||||
}
|
||||
|
||||
func (i *Identity) GetNamespacedUID() (namespace identity.Namespace, identifier string) {
|
||||
return i.ID.Namespace(), i.UserUID
|
||||
return i.UID
|
||||
}
|
||||
|
||||
func (i *Identity) GetAuthID() string {
|
||||
@@ -212,8 +204,6 @@ func (i *Identity) IsNil() bool {
|
||||
|
||||
// SignedInUser returns a SignedInUser from the identity.
|
||||
func (i *Identity) SignedInUser() *user.SignedInUser {
|
||||
namespace, id := i.GetNamespacedID()
|
||||
|
||||
u := &user.SignedInUser{
|
||||
OrgID: i.OrgID,
|
||||
OrgName: i.OrgName,
|
||||
@@ -224,7 +214,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
|
||||
AuthID: i.AuthID,
|
||||
AuthenticatedBy: i.AuthenticatedBy,
|
||||
IsGrafanaAdmin: i.GetIsGrafanaAdmin(),
|
||||
IsAnonymous: namespace == NamespaceAnonymous,
|
||||
IsAnonymous: i.ID.IsNamespace(NamespaceAnonymous),
|
||||
IsDisabled: i.IsDisabled,
|
||||
HelpFlags1: i.HelpFlags1,
|
||||
LastSeenAt: i.LastSeenAt,
|
||||
@@ -234,33 +224,26 @@ func (i *Identity) SignedInUser() *user.SignedInUser {
|
||||
NamespacedID: i.ID,
|
||||
}
|
||||
|
||||
if namespace == NamespaceAPIKey {
|
||||
u.ApiKeyID = intIdentifier(id)
|
||||
if i.ID.IsNamespace(NamespaceAPIKey) {
|
||||
id, _ := i.ID.ParseInt()
|
||||
u.ApiKeyID = id
|
||||
} else {
|
||||
u.UserID = intIdentifier(id)
|
||||
u.IsServiceAccount = namespace == NamespaceServiceAccount
|
||||
id, _ := i.ID.UserID()
|
||||
u.UserID = id
|
||||
u.UserUID = i.UID.ID()
|
||||
u.IsServiceAccount = i.ID.IsNamespace(NamespaceServiceAccount)
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func intIdentifier(identifier string) int64 {
|
||||
id, err := strconv.ParseInt(identifier, 10, 64)
|
||||
if err != nil {
|
||||
// FIXME (kalleep): Improve error handling
|
||||
return -1
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (i *Identity) ExternalUserInfo() login.ExternalUserInfo {
|
||||
_, id := i.GetNamespacedID()
|
||||
id, _ := i.ID.UserID()
|
||||
return login.ExternalUserInfo{
|
||||
OAuthToken: i.OAuthToken,
|
||||
AuthModule: i.AuthenticatedBy,
|
||||
AuthId: i.AuthID,
|
||||
UserId: intIdentifier(id),
|
||||
UserId: id,
|
||||
Email: i.Email,
|
||||
Login: i.Login,
|
||||
Name: i.Name,
|
||||
|
||||
@@ -15,11 +15,13 @@ const (
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
@@ -37,16 +37,12 @@ type userDisplayDTO struct {
|
||||
|
||||
// Static function to parse a requester into a userDisplayDTO
|
||||
func newUserDisplayDTOFromRequester(requester identity.Requester) *userDisplayDTO {
|
||||
userID := int64(0)
|
||||
namespaceID, identifier := requester.GetNamespacedID()
|
||||
if namespaceID == identity.NamespaceUser || namespaceID == identity.NamespaceServiceAccount {
|
||||
userID, _ = identity.IntIdentifier(namespaceID, identifier)
|
||||
}
|
||||
namespaceID, uid := requester.GetNamespacedUID()
|
||||
if namespaceID != identity.NamespaceUser && namespaceID != identity.NamespaceServiceAccount {
|
||||
uid = ""
|
||||
uid := ""
|
||||
if requester.GetUID().IsNamespace(identity.NamespaceUser, identity.NamespaceServiceAccount) {
|
||||
uid = requester.GetUID().ID()
|
||||
}
|
||||
|
||||
userID, _ := requester.GetID().UserID()
|
||||
return &userDisplayDTO{
|
||||
ID: userID,
|
||||
UID: uid,
|
||||
|
||||
@@ -192,27 +192,20 @@ func (u *SignedInUser) GetNamespacedID() (identity.Namespace, string) {
|
||||
|
||||
// GetUID returns namespaced uid for the entity
|
||||
func (u *SignedInUser) GetUID() identity.NamespaceID {
|
||||
ns, uid := u.GetNamespacedUID()
|
||||
return identity.NewNamespaceIDString(ns, uid)
|
||||
}
|
||||
|
||||
// GetNamespacedUID returns the namespace and UID of the active entity
|
||||
// The namespace is one of the constants defined in pkg/services/auth/identity
|
||||
func (u *SignedInUser) GetNamespacedUID() (identity.Namespace, string) {
|
||||
switch {
|
||||
case u.ApiKeyID != 0:
|
||||
return identity.NamespaceAPIKey, fmt.Sprint(u.ApiKeyID)
|
||||
return identity.NewNamespaceIDString(identity.NamespaceAPIKey, strconv.FormatInt(u.ApiKeyID, 10))
|
||||
case u.IsServiceAccount:
|
||||
return identity.NamespaceServiceAccount, u.UserUID
|
||||
return identity.NewNamespaceIDString(identity.NamespaceServiceAccount, u.UserUID)
|
||||
case u.UserID > 0:
|
||||
return identity.NamespaceUser, u.UserUID
|
||||
return identity.NewNamespaceIDString(identity.NamespaceUser, u.UserUID)
|
||||
case u.IsAnonymous:
|
||||
return identity.NamespaceAnonymous, ""
|
||||
return identity.NewNamespaceIDString(identity.NamespaceAnonymous, "0")
|
||||
case u.AuthenticatedBy == "render" && u.UserID == 0:
|
||||
return identity.NamespaceRenderService, ""
|
||||
return identity.NewNamespaceIDString(identity.NamespaceRenderService, "0")
|
||||
}
|
||||
|
||||
return identity.NamespaceEmpty, ""
|
||||
return identity.NewNamespaceIDString(identity.NamespaceEmpty, "0")
|
||||
}
|
||||
|
||||
func (u *SignedInUser) GetAuthID() string {
|
||||
|
||||
Reference in New Issue
Block a user