From 9c9e5e68c8bbdc30dcfe7080e5104de3a5c217b6 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 1 Feb 2024 18:14:10 -0800 Subject: [PATCH] User: Add uid colum to user table (#81615) --- packages/grafana-data/src/types/config.ts | 1 + pkg/api/dtos/models.go | 1 + pkg/api/index.go | 1 + pkg/api/user_test.go | 2 ++ .../ngalert/migration/permissions_test.go | 1 + .../migrations/accesscontrol/test/ac_test.go | 5 ++++ pkg/services/sqlstore/migrations/user_mig.go | 13 +++++++++ pkg/services/user/identity.go | 6 +++-- pkg/services/user/model.go | 7 ++++- pkg/services/user/userimpl/store.go | 5 ++++ pkg/services/user/userimpl/store_test.go | 27 +++++++++++++++++++ pkg/services/user/userimpl/user.go | 1 + public/api-enterprise-spec.json | 6 +++++ public/api-merged.json | 6 +++++ public/app/core/services/context_srv.ts | 2 ++ public/openapi3.json | 6 +++++ 16 files changed, 87 insertions(+), 3 deletions(-) diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index d6dfa26ab1f..012755695eb 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -105,6 +105,7 @@ export interface AnalyticsSettings { export interface CurrentUserDTO { isSignedIn: boolean; id: number; + uid: string; externalUserId: string; login: string; email: string; diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index c0291f65c30..8933f8b49bb 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -30,6 +30,7 @@ type LoginCommand struct { type CurrentUser struct { IsSignedIn bool `json:"isSignedIn"` Id int64 `json:"id"` + UID string `json:"uid"` Login string `json:"login"` Email string `json:"email"` Name string `json:"name"` diff --git a/pkg/api/index.go b/pkg/api/index.go index 75483655916..4c52249cbb4 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -92,6 +92,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV data := dtos.IndexViewData{ User: &dtos.CurrentUser{ Id: userID, + UID: c.UserUID, // << not set yet IsSignedIn: c.IsSignedIn, Login: c.Login, Email: c.SignedInUser.GetEmail(), diff --git a/pkg/api/user_test.go b/pkg/api/user_test.go index 2b9bfa01951..964eb23da87 100644 --- a/pkg/api/user_test.go +++ b/pkg/api/user_test.go @@ -82,6 +82,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { } usr, err := userSvc.Create(context.Background(), &createUserCmd) require.NoError(t, err) + theUserUID := usr.UID sc.handlerFunc = hs.GetUserByID @@ -108,6 +109,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) { expected := user.UserProfileDTO{ ID: 1, + UID: theUserUID, // from original request Email: "user@test.com", Name: "user", Login: "loginuser", diff --git a/pkg/services/ngalert/migration/permissions_test.go b/pkg/services/ngalert/migration/permissions_test.go index 0b33927b60d..195b0a75200 100644 --- a/pkg/services/ngalert/migration/permissions_test.go +++ b/pkg/services/ngalert/migration/permissions_test.go @@ -643,6 +643,7 @@ func TestDashAlertPermissionMigration(t *testing.T) { for i := 1; i < 3; i++ { _, err := x.Insert(user.User{ ID: int64(i), + UID: fmt.Sprintf("u%d", i), OrgID: 1, Name: fmt.Sprintf("user%v", i), Login: fmt.Sprintf("user%v", i), diff --git a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go index ef60e500799..c811260a588 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go @@ -46,6 +46,7 @@ var ( users = []user.User{ { ID: 1, + UID: "u1", Email: "viewer1@example.org", Name: "viewer1", Login: "viewer1", @@ -55,6 +56,7 @@ var ( }, { ID: 2, + UID: "u2", Email: "viewer2@example.org", Name: "viewer2", Login: "viewer2", @@ -64,6 +66,7 @@ var ( }, { ID: 3, + UID: "u3", Email: "editor1@example.org", Name: "editor1", Login: "editor1", @@ -73,6 +76,7 @@ var ( }, { ID: 4, + UID: "u4", Email: "admin1@example.org", Name: "admin1", Login: "admin1", @@ -82,6 +86,7 @@ var ( }, { ID: 5, + UID: "u5", Email: "editor2@example.org", Name: "editor2", Login: "editor2", diff --git a/pkg/services/sqlstore/migrations/user_mig.go b/pkg/services/sqlstore/migrations/user_mig.go index 6edf5161f47..4f56666fcdb 100644 --- a/pkg/services/sqlstore/migrations/user_mig.go +++ b/pkg/services/sqlstore/migrations/user_mig.go @@ -140,6 +140,19 @@ func addUserMigrations(mg *Migrator) { SQLite(migSQLITEisServiceAccountNullable). Postgres("ALTER TABLE `user` ALTER COLUMN is_service_account DROP NOT NULL;"). Mysql("ALTER TABLE user MODIFY is_service_account BOOLEAN DEFAULT 0;")) + + mg.AddMigration("Add uid column to user", NewAddColumnMigration(userV2, &Column{ + Name: "uid", Type: DB_NVarchar, Length: 40, Nullable: true, + })) + + mg.AddMigration("Update uid column values for users", NewRawSQLMigration(""). + SQLite("UPDATE user SET uid=printf('u%09d',id) WHERE uid IS NULL;"). + Postgres("UPDATE `user` SET uid='u' || lpad('' || id::text,9,'0') WHERE uid IS NULL;"). + Mysql("UPDATE user SET uid=concat('u',lpad(id,9,'0')) WHERE uid IS NULL;")) + + mg.AddMigration("Add unique index user_uid", NewAddIndexMigration(userV2, &Index{ + Cols: []string{"uid"}, Type: UniqueIndex, + })) } const migSQLITEisServiceAccountNullable = `ALTER TABLE user ADD COLUMN tmp_service_account BOOLEAN DEFAULT 0; diff --git a/pkg/services/user/identity.go b/pkg/services/user/identity.go index 2ab4b1f3661..119bc5e9baa 100644 --- a/pkg/services/user/identity.go +++ b/pkg/services/user/identity.go @@ -13,8 +13,9 @@ const ( ) type SignedInUser struct { - UserID int64 `xorm:"user_id"` - OrgID int64 `xorm:"org_id"` + UserID int64 `xorm:"user_id"` + UserUID string `xorm:"user_uid"` + OrgID int64 `xorm:"org_id"` OrgName string OrgRole roletype.RoleType Login string @@ -58,6 +59,7 @@ func (u *SignedInUser) NameOrFallback() string { func (u *SignedInUser) ToUserDisplayDTO() *UserDisplayDTO { return &UserDisplayDTO{ ID: u.UserID, + UID: u.UserUID, Login: u.Login, Name: u.Name, // AvatarURL: dtos.GetGravatarUrl(u.GetEmail()), diff --git a/pkg/services/user/model.go b/pkg/services/user/model.go index 81b184286ab..430f0ddccf3 100644 --- a/pkg/services/user/model.go +++ b/pkg/services/user/model.go @@ -20,7 +20,8 @@ const ( ) type User struct { - ID int64 `xorm:"pk autoincr 'id'"` + ID int64 `xorm:"pk autoincr 'id'"` + UID string `json:"uid" xorm:"uid"` Version int Email string Name string @@ -44,6 +45,7 @@ type User struct { } type CreateUserCommand struct { + UID string Email string Login string Name string @@ -115,6 +117,7 @@ type SearchUserQueryResult struct { type UserSearchHitDTO struct { ID int64 `json:"id" xorm:"id"` + UID string `json:"uid" xorm:"id"` Name string `json:"name"` Login string `json:"login"` Email string `json:"email"` @@ -133,6 +136,7 @@ type GetUserProfileQuery struct { type UserProfileDTO struct { ID int64 `json:"id"` + UID string `json:"uid"` Email string `json:"email"` Name string `json:"name"` Login string `json:"login"` @@ -215,6 +219,7 @@ type ErrCaseInsensitiveLoginConflict struct { type UserDisplayDTO struct { ID int64 `json:"id,omitempty"` + UID string `json:"uid,omitempty"` Name string `json:"name,omitempty"` Login string `json:"login,omitempty"` AvatarURL string `json:"avatarUrl"` diff --git a/pkg/services/user/userimpl/store.go b/pkg/services/user/userimpl/store.go index cedd27fe3b7..8923e446348 100644 --- a/pkg/services/user/userimpl/store.go +++ b/pkg/services/user/userimpl/store.go @@ -63,6 +63,9 @@ func (ss *sqlStore) Insert(ctx context.Context, cmd *user.User) (int64, error) { var err error err = ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { sess.UseBool("is_admin") + if cmd.UID == "" { + cmd.UID = util.GenerateShortUID() + } if _, err = sess.Insert(cmd); err != nil { return err @@ -393,6 +396,7 @@ func (ss *sqlStore) GetSignedInUser(ctx context.Context, query *user.GetSignedIn var rawSQL = `SELECT u.id as user_id, + u.uid as user_uid, u.is_admin as is_grafana_admin, u.email as email, u.login as login, @@ -466,6 +470,7 @@ func (ss *sqlStore) GetProfile(ctx context.Context, query *user.GetUserProfileQu userProfile = user.UserProfileDTO{ ID: usr.ID, + UID: usr.UID, Name: usr.Name, Email: usr.Email, Login: usr.Login, diff --git a/pkg/services/user/userimpl/store_test.go b/pkg/services/user/userimpl/store_test.go index 4940bb7d15f..39d87e8d30d 100644 --- a/pkg/services/user/userimpl/store_test.go +++ b/pkg/services/user/userimpl/store_test.go @@ -104,6 +104,7 @@ func TestIntegrationUserGet(t *testing.T) { } else { require.NoError(t, err) require.NotNil(t, usr) + require.NotEmpty(t, usr.UID) } }) } @@ -150,6 +151,32 @@ func TestIntegrationUserDataAccess(t *testing.T) { require.NoError(t, err) }) + t.Run("insert user (with known UID)", func(t *testing.T) { + ctx := context.Background() + id, err := userStore.Insert(ctx, + &user.User{ + UID: "abcd", + Email: "next-test@email.com", + Name: "next-test1", + Login: "next-test1", + Created: time.Now(), + Updated: time.Now(), + }, + ) + require.NoError(t, err) + + found, err := userStore.GetByID(ctx, id) + require.NoError(t, err) + require.Equal(t, "abcd", found.UID) + + siu, err := userStore.GetSignedInUser(ctx, &user.GetSignedInUserQuery{ + UserID: id, + OrgID: found.OrgID, + }) + require.NoError(t, err) + require.Equal(t, "abcd", siu.UserUID) + }) + t.Run("get user", func(t *testing.T) { _, err := userStore.Get(context.Background(), &user.User{ diff --git a/pkg/services/user/userimpl/user.go b/pkg/services/user/userimpl/user.go index c3e64d4a8ee..eef5a1bbfb7 100644 --- a/pkg/services/user/userimpl/user.go +++ b/pkg/services/user/userimpl/user.go @@ -129,6 +129,7 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use // create user usr := &user.User{ + UID: cmd.UID, Email: cmd.Email, Name: cmd.Name, Login: cmd.Login, diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index a7d5a6d07b1..99da38d47d5 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -7951,6 +7951,9 @@ "theme": { "type": "string" }, + "uid": { + "type": "string" + }, "updatedAt": { "type": "string", "format": "date-time" @@ -7994,6 +7997,9 @@ }, "name": { "type": "string" + }, + "uid": { + "type": "string" } } }, diff --git a/public/api-merged.json b/public/api-merged.json index 7fb4e1c5b67..5828a0d7816 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -21361,6 +21361,9 @@ "theme": { "type": "string" }, + "uid": { + "type": "string" + }, "updatedAt": { "type": "string", "format": "date-time" @@ -21404,6 +21407,9 @@ }, "name": { "type": "string" + }, + "uid": { + "type": "string" } } }, diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index 6b49da8f97e..d305d2a4fbc 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -15,6 +15,7 @@ export const AutoRefreshInterval = 'auto'; export class User implements Omit { isSignedIn: boolean; id: number; + uid: string; login: string; email: string; name: string; @@ -39,6 +40,7 @@ export class User implements Omit { constructor() { this.id = 0; + this.uid = ''; this.isGrafanaAdmin = false; this.isSignedIn = false; this.orgRole = ''; diff --git a/public/openapi3.json b/public/openapi3.json index fd6a44c65d6..5b63c944acf 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -11843,6 +11843,9 @@ "theme": { "type": "string" }, + "uid": { + "type": "string" + }, "updatedAt": { "format": "date-time", "type": "string" @@ -11886,6 +11889,9 @@ }, "name": { "type": "string" + }, + "uid": { + "type": "string" } }, "type": "object"