From 8f1e2ed658cda0fa67679256c9da91328849ec8d Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Tue, 20 Sep 2022 11:29:17 +0200 Subject: [PATCH] Chore: Split temporary user (invite) service (#55397) * Chore: Split temporary users * change references to tempuser service * fix api tests * restore tests --- pkg/api/org_invite.go | 10 +- pkg/api/org_users_test.go | 2 + pkg/api/signup.go | 6 +- pkg/server/wire.go | 4 +- pkg/services/cleanup/cleanup.go | 7 +- pkg/services/sqlstore/login_attempt.go | 1 - pkg/services/sqlstore/login_attempt_test.go | 1 - pkg/services/sqlstore/store.go | 6 - pkg/services/sqlstore/temp_user.go | 143 ---------------- pkg/services/sqlstore/temp_user_test.go | 95 ----------- pkg/services/temp_user/tempuserimpl/store.go | 157 ++++++++++++++++++ .../temp_user/tempuserimpl/store_test.go | 95 +++++++++++ .../temp_user/tempuserimpl/temp_user.go | 21 ++- 13 files changed, 279 insertions(+), 269 deletions(-) delete mode 100644 pkg/services/sqlstore/login_attempt.go delete mode 100644 pkg/services/sqlstore/login_attempt_test.go delete mode 100644 pkg/services/sqlstore/temp_user.go delete mode 100644 pkg/services/sqlstore/temp_user_test.go diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index 93da59885f9..54555d8187d 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -31,7 +31,7 @@ import ( func (hs *HTTPServer) GetPendingOrgInvites(c *models.ReqContext) response.Response { query := models.GetTempUsersQuery{OrgId: c.OrgID, Status: models.TmpUserInvitePending} - if err := hs.SQLStore.GetTempUsersQuery(c.Req.Context(), &query); err != nil { + if err := hs.tempUserService.GetTempUsersQuery(c.Req.Context(), &query); err != nil { return response.Error(500, "Failed to get invites from db", err) } @@ -102,7 +102,7 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response { cmd.Role = inviteDto.Role cmd.RemoteAddr = c.Req.RemoteAddr - if err := hs.SQLStore.CreateTempUser(c.Req.Context(), &cmd); err != nil { + if err := hs.tempUserService.CreateTempUser(c.Req.Context(), &cmd); err != nil { return response.Error(500, "Failed to save invite to database", err) } @@ -129,7 +129,7 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response { } emailSentCmd := models.UpdateTempUserWithEmailSentCommand{Code: cmd.Result.Code} - if err := hs.SQLStore.UpdateTempUserWithEmailSent(c.Req.Context(), &emailSentCmd); err != nil { + if err := hs.tempUserService.UpdateTempUserWithEmailSent(c.Req.Context(), &emailSentCmd); err != nil { return response.Error(500, "Failed to update invite with email sent info", err) } @@ -194,7 +194,7 @@ func (hs *HTTPServer) RevokeInvite(c *models.ReqContext) response.Response { // If a (pending) invite is not found, 404 is returned. func (hs *HTTPServer) GetInviteInfoByCode(c *models.ReqContext) response.Response { query := models.GetTempUserByCodeQuery{Code: web.Params(c.Req)[":code"]} - if err := hs.SQLStore.GetTempUserByCode(c.Req.Context(), &query); err != nil { + if err := hs.tempUserService.GetTempUserByCode(c.Req.Context(), &query); err != nil { if errors.Is(err, models.ErrTempUserNotFound) { return response.Error(404, "Invite not found", nil) } @@ -221,7 +221,7 @@ func (hs *HTTPServer) CompleteInvite(c *models.ReqContext) response.Response { } query := models.GetTempUserByCodeQuery{Code: completeInvite.InviteCode} - if err := hs.SQLStore.GetTempUserByCode(c.Req.Context(), &query); err != nil { + if err := hs.tempUserService.GetTempUserByCode(c.Req.Context(), &query); err != nil { if errors.Is(err, models.ErrTempUserNotFound) { return response.Error(404, "Invite not found", nil) } diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index 6650c75bb56..41a905ae408 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" + "github.com/grafana/grafana/pkg/services/temp_user/tempuserimpl" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/userimpl" "github.com/grafana/grafana/pkg/setting" @@ -684,6 +685,7 @@ func TestOrgUsersAPIEndpointWithSetPerms_AccessControl(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { sc := setupHTTPServer(t, true, func(hs *HTTPServer) { + hs.tempUserService = tempuserimpl.ProvideService(hs.SQLStore) hs.userService = userimpl.ProvideService( hs.SQLStore, nil, nil, nil, nil, nil, nil, nil, nil, nil, hs.SQLStore.(*sqlstore.SQLStore), diff --git a/pkg/api/signup.go b/pkg/api/signup.go index 490983ed4bf..37b7ca407e5 100644 --- a/pkg/api/signup.go +++ b/pkg/api/signup.go @@ -51,7 +51,7 @@ func (hs *HTTPServer) SignUp(c *models.ReqContext) response.Response { } cmd.RemoteAddr = c.Req.RemoteAddr - if err := hs.SQLStore.CreateTempUser(c.Req.Context(), &cmd); err != nil { + if err := hs.tempUserService.CreateTempUser(c.Req.Context(), &cmd); err != nil { return response.Error(500, "Failed to create signup", err) } @@ -116,7 +116,7 @@ func (hs *HTTPServer) SignUpStep2(c *models.ReqContext) response.Response { // check for pending invites invitesQuery := models.GetTempUsersQuery{Email: form.Email, Status: models.TmpUserInvitePending} - if err := hs.SQLStore.GetTempUsersQuery(c.Req.Context(), &invitesQuery); err != nil { + if err := hs.tempUserService.GetTempUsersQuery(c.Req.Context(), &invitesQuery); err != nil { return response.Error(500, "Failed to query database for invites", err) } @@ -141,7 +141,7 @@ func (hs *HTTPServer) SignUpStep2(c *models.ReqContext) response.Response { func (hs *HTTPServer) verifyUserSignUpEmail(ctx context.Context, email string, code string) (bool, response.Response) { query := models.GetTempUserByCodeQuery{Code: code} - if err := hs.SQLStore.GetTempUserByCode(ctx, &query); err != nil { + if err := hs.tempUserService.GetTempUserByCode(ctx, &query); err != nil { if errors.Is(err, models.ErrTempUserNotFound) { return false, response.Error(404, "Invalid email verification code", nil) } diff --git a/pkg/server/wire.go b/pkg/server/wire.go index df89c225aae..f517128b7f1 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -126,6 +126,7 @@ import ( "github.com/grafana/grafana/pkg/services/teamguardian" teamguardianDatabase "github.com/grafana/grafana/pkg/services/teamguardian/database" teamguardianManager "github.com/grafana/grafana/pkg/services/teamguardian/manager" + tempuser "github.com/grafana/grafana/pkg/services/temp_user" "github.com/grafana/grafana/pkg/services/temp_user/tempuserimpl" "github.com/grafana/grafana/pkg/services/thumbs" "github.com/grafana/grafana/pkg/services/updatechecker" @@ -345,6 +346,7 @@ var wireBasicSet = wire.NewSet( userauthimpl.ProvideService, acimpl.ProvideAccessControl, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), + wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), ) var wireSet = wire.NewSet( @@ -352,7 +354,6 @@ var wireSet = wire.NewSet( sqlstore.ProvideService, wire.Bind(new(sqlstore.TeamStore), new(*sqlstore.SQLStore)), ngmetrics.ProvideService, - wire.Bind(new(notifications.TempUserStore), new(*sqlstore.SQLStore)), wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), @@ -369,7 +370,6 @@ var wireTestSet = wire.NewSet( wire.Bind(new(sqlstore.TeamStore), new(*sqlstore.SQLStore)), notifications.MockNotificationService, - wire.Bind(new(notifications.TempUserStore), new(*mockstore.SQLStoreMock)), wire.Bind(new(notifications.Service), new(*notifications.NotificationServiceMock)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationServiceMock)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationServiceMock)), diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index 5c24330c51f..e6b4b71f5ff 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -19,13 +19,14 @@ import ( "github.com/grafana/grafana/pkg/services/queryhistory" "github.com/grafana/grafana/pkg/services/shorturls" "github.com/grafana/grafana/pkg/services/sqlstore" + tempuser "github.com/grafana/grafana/pkg/services/temp_user" "github.com/grafana/grafana/pkg/setting" ) func ProvideService(cfg *setting.Cfg, serverLockService *serverlock.ServerLockService, shortURLService shorturls.Service, sqlstore *sqlstore.SQLStore, queryHistoryService queryhistory.Service, dashboardVersionService dashver.Service, dashSnapSvc dashboardsnapshots.Service, deleteExpiredImageService *image.DeleteExpiredService, - loginAttemptService loginattempt.Service) *CleanUpService { + loginAttemptService loginattempt.Service, tempUserService tempuser.Service) *CleanUpService { s := &CleanUpService{ Cfg: cfg, ServerLockService: serverLockService, @@ -37,6 +38,7 @@ func ProvideService(cfg *setting.Cfg, serverLockService *serverlock.ServerLockSe dashboardSnapshotService: dashSnapSvc, deleteExpiredImageService: deleteExpiredImageService, loginAttemptService: loginAttemptService, + tempUserService: tempUserService, } return s } @@ -52,6 +54,7 @@ type CleanUpService struct { dashboardSnapshotService dashboardsnapshots.Service deleteExpiredImageService *image.DeleteExpiredService loginAttemptService loginattempt.Service + tempUserService tempuser.Service } func (srv *CleanUpService) Run(ctx context.Context) error { @@ -201,7 +204,7 @@ func (srv *CleanUpService) expireOldUserInvites(ctx context.Context) { cmd := models.ExpireTempUsersCommand{ OlderThan: time.Now().Add(-maxInviteLifetime), } - if err := srv.store.ExpireOldUserInvites(ctx, &cmd); err != nil { + if err := srv.tempUserService.ExpireOldUserInvites(ctx, &cmd); err != nil { srv.log.Error("Problem expiring user invites", "error", err.Error()) } else { srv.log.Debug("Expired user invites", "rows affected", cmd.NumExpired) diff --git a/pkg/services/sqlstore/login_attempt.go b/pkg/services/sqlstore/login_attempt.go deleted file mode 100644 index f79e0bcddbe..00000000000 --- a/pkg/services/sqlstore/login_attempt.go +++ /dev/null @@ -1 +0,0 @@ -package sqlstore diff --git a/pkg/services/sqlstore/login_attempt_test.go b/pkg/services/sqlstore/login_attempt_test.go deleted file mode 100644 index f79e0bcddbe..00000000000 --- a/pkg/services/sqlstore/login_attempt_test.go +++ /dev/null @@ -1 +0,0 @@ -package sqlstore diff --git a/pkg/services/sqlstore/store.go b/pkg/services/sqlstore/store.go index ea18e2b7755..97ed5091c3e 100644 --- a/pkg/services/sqlstore/store.go +++ b/pkg/services/sqlstore/store.go @@ -62,12 +62,6 @@ type Store interface { Sync() error Reset() error Quote(value string) string - UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error - CreateTempUser(ctx context.Context, cmd *models.CreateTempUserCommand) error - UpdateTempUserWithEmailSent(ctx context.Context, cmd *models.UpdateTempUserWithEmailSentCommand) error - GetTempUsersQuery(ctx context.Context, query *models.GetTempUsersQuery) error - GetTempUserByCode(ctx context.Context, query *models.GetTempUserByCodeQuery) error - ExpireOldUserInvites(ctx context.Context, cmd *models.ExpireTempUsersCommand) error GetDBHealthQuery(ctx context.Context, query *models.GetDBHealthQuery) error IsAdminOfTeams(ctx context.Context, query *models.IsAdminOfTeamsQuery) error GetSqlxSession() *session.SessionDB diff --git a/pkg/services/sqlstore/temp_user.go b/pkg/services/sqlstore/temp_user.go deleted file mode 100644 index 80115ade956..00000000000 --- a/pkg/services/sqlstore/temp_user.go +++ /dev/null @@ -1,143 +0,0 @@ -package sqlstore - -import ( - "context" - "time" - - "github.com/grafana/grafana/pkg/models" -) - -func (ss *SQLStore) UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - var rawSQL = "UPDATE temp_user SET status=? WHERE code=?" - _, err := sess.Exec(rawSQL, string(cmd.Status), cmd.Code) - return err - }) -} - -func (ss *SQLStore) CreateTempUser(ctx context.Context, cmd *models.CreateTempUserCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - // create user - user := &models.TempUser{ - Email: cmd.Email, - Name: cmd.Name, - OrgId: cmd.OrgId, - Code: cmd.Code, - Role: cmd.Role, - Status: cmd.Status, - RemoteAddr: cmd.RemoteAddr, - InvitedByUserId: cmd.InvitedByUserId, - EmailSentOn: time.Now(), - Created: time.Now().Unix(), - Updated: time.Now().Unix(), - } - - if _, err := sess.Insert(user); err != nil { - return err - } - - cmd.Result = user - - return nil - }) -} - -func (ss *SQLStore) UpdateTempUserWithEmailSent(ctx context.Context, cmd *models.UpdateTempUserWithEmailSentCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - user := &models.TempUser{ - EmailSent: true, - EmailSentOn: time.Now(), - } - - _, err := sess.Where("code = ?", cmd.Code).Cols("email_sent", "email_sent_on").Update(user) - - return err - }) -} - -func (ss *SQLStore) GetTempUsersQuery(ctx context.Context, query *models.GetTempUsersQuery) error { - return ss.WithDbSession(ctx, func(dbSess *DBSession) error { - rawSQL := `SELECT - tu.id as id, - tu.org_id as org_id, - tu.email as email, - tu.name as name, - tu.role as role, - tu.code as code, - tu.status as status, - tu.email_sent as email_sent, - tu.email_sent_on as email_sent_on, - tu.created as created, - u.login as invited_by_login, - u.name as invited_by_name, - u.email as invited_by_email - FROM ` + dialect.Quote("temp_user") + ` as tu - LEFT OUTER JOIN ` + dialect.Quote("user") + ` as u on u.id = tu.invited_by_user_id - WHERE tu.status=?` - params := []interface{}{string(query.Status)} - - if query.OrgId > 0 { - rawSQL += ` AND tu.org_id=?` - params = append(params, query.OrgId) - } - - if query.Email != "" { - rawSQL += ` AND tu.email=?` - params = append(params, query.Email) - } - - rawSQL += " ORDER BY tu.created desc" - - query.Result = make([]*models.TempUserDTO, 0) - sess := dbSess.SQL(rawSQL, params...) - err := sess.Find(&query.Result) - return err - }) -} - -func (ss *SQLStore) GetTempUserByCode(ctx context.Context, query *models.GetTempUserByCodeQuery) error { - return ss.WithDbSession(ctx, func(dbSess *DBSession) error { - var rawSQL = `SELECT - tu.id as id, - tu.org_id as org_id, - tu.email as email, - tu.name as name, - tu.role as role, - tu.code as code, - tu.status as status, - tu.email_sent as email_sent, - tu.email_sent_on as email_sent_on, - tu.created as created, - u.login as invited_by_login, - u.name as invited_by_name, - u.email as invited_by_email - FROM ` + dialect.Quote("temp_user") + ` as tu - LEFT OUTER JOIN ` + dialect.Quote("user") + ` as u on u.id = tu.invited_by_user_id - WHERE tu.code=?` - - var tempUser models.TempUserDTO - sess := dbSess.SQL(rawSQL, query.Code) - has, err := sess.Get(&tempUser) - - if err != nil { - return err - } else if !has { - return models.ErrTempUserNotFound - } - - query.Result = &tempUser - return err - }) -} - -func (ss *SQLStore) ExpireOldUserInvites(ctx context.Context, cmd *models.ExpireTempUsersCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { - var rawSQL = "UPDATE temp_user SET status = ?, updated = ? WHERE created <= ? AND status in (?, ?)" - if result, err := sess.Exec(rawSQL, string(models.TmpUserExpired), time.Now().Unix(), cmd.OlderThan.Unix(), string(models.TmpUserSignUpStarted), string(models.TmpUserInvitePending)); err != nil { - return err - } else if cmd.NumExpired, err = result.RowsAffected(); err != nil { - return err - } - return nil - }) -} diff --git a/pkg/services/sqlstore/temp_user_test.go b/pkg/services/sqlstore/temp_user_test.go deleted file mode 100644 index fee989c22de..00000000000 --- a/pkg/services/sqlstore/temp_user_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package sqlstore - -import ( - "context" - "testing" - "time" - - "github.com/grafana/grafana/pkg/models" - - "github.com/stretchr/testify/require" -) - -func TestIntegrationTempUserCommandsAndQueries(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration test") - } - ss := InitTestDB(t) - cmd := models.CreateTempUserCommand{ - OrgId: 2256, - Name: "hello", - Code: "asd", - Email: "e@as.co", - Status: models.TmpUserInvitePending, - } - setup := func(t *testing.T) { - InitTestDB(t) - err := ss.CreateTempUser(context.Background(), &cmd) - require.Nil(t, err) - } - - t.Run("Should be able to get temp users by org id", func(t *testing.T) { - setup(t) - query := models.GetTempUsersQuery{OrgId: 2256, Status: models.TmpUserInvitePending} - err := ss.GetTempUsersQuery(context.Background(), &query) - - require.Nil(t, err) - require.Equal(t, 1, len(query.Result)) - }) - - t.Run("Should be able to get temp users by email", func(t *testing.T) { - setup(t) - query := models.GetTempUsersQuery{Email: "e@as.co", Status: models.TmpUserInvitePending} - err := ss.GetTempUsersQuery(context.Background(), &query) - - require.Nil(t, err) - require.Equal(t, 1, len(query.Result)) - }) - - t.Run("Should be able to get temp users by code", func(t *testing.T) { - setup(t) - query := models.GetTempUserByCodeQuery{Code: "asd"} - err := ss.GetTempUserByCode(context.Background(), &query) - - require.Nil(t, err) - require.Equal(t, "hello", query.Result.Name) - }) - - t.Run("Should be able update status", func(t *testing.T) { - setup(t) - cmd2 := models.UpdateTempUserStatusCommand{Code: "asd", Status: models.TmpUserRevoked} - err := ss.UpdateTempUserStatus(context.Background(), &cmd2) - require.Nil(t, err) - }) - - t.Run("Should be able update email sent and email sent on", func(t *testing.T) { - setup(t) - cmd2 := models.UpdateTempUserWithEmailSentCommand{Code: cmd.Result.Code} - err := ss.UpdateTempUserWithEmailSent(context.Background(), &cmd2) - require.Nil(t, err) - - query := models.GetTempUsersQuery{OrgId: 2256, Status: models.TmpUserInvitePending} - err = ss.GetTempUsersQuery(context.Background(), &query) - - require.Nil(t, err) - require.True(t, query.Result[0].EmailSent) - require.False(t, query.Result[0].EmailSentOn.UTC().Before(query.Result[0].Created.UTC())) - }) - - t.Run("Should be able expire temp user", func(t *testing.T) { - setup(t) - createdAt := time.Unix(cmd.Result.Created, 0) - cmd2 := models.ExpireTempUsersCommand{OlderThan: createdAt.Add(1 * time.Second)} - err := ss.ExpireOldUserInvites(context.Background(), &cmd2) - require.Nil(t, err) - require.Equal(t, int64(1), cmd2.NumExpired) - - t.Run("Should do nothing when no temp users to expire", func(t *testing.T) { - createdAt := time.Unix(cmd.Result.Created, 0) - cmd2 := models.ExpireTempUsersCommand{OlderThan: createdAt.Add(1 * time.Second)} - err := ss.ExpireOldUserInvites(context.Background(), &cmd2) - require.Nil(t, err) - require.Equal(t, int64(0), cmd2.NumExpired) - }) - }) -} diff --git a/pkg/services/temp_user/tempuserimpl/store.go b/pkg/services/temp_user/tempuserimpl/store.go index f790e452fbc..15e6815e621 100644 --- a/pkg/services/temp_user/tempuserimpl/store.go +++ b/pkg/services/temp_user/tempuserimpl/store.go @@ -1 +1,158 @@ package tempuserimpl + +import ( + "context" + "time" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/db" +) + +type store interface { + UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error + CreateTempUser(ctx context.Context, cmd *models.CreateTempUserCommand) error + UpdateTempUserWithEmailSent(ctx context.Context, cmd *models.UpdateTempUserWithEmailSentCommand) error + GetTempUsersQuery(ctx context.Context, query *models.GetTempUsersQuery) error + GetTempUserByCode(ctx context.Context, query *models.GetTempUserByCodeQuery) error + ExpireOldUserInvites(ctx context.Context, cmd *models.ExpireTempUsersCommand) error +} + +type xormStore struct { + db db.DB +} + +func (ss *xormStore) UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error { + return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + var rawSQL = "UPDATE temp_user SET status=? WHERE code=?" + _, err := sess.Exec(rawSQL, string(cmd.Status), cmd.Code) + return err + }) +} + +func (ss *xormStore) CreateTempUser(ctx context.Context, cmd *models.CreateTempUserCommand) error { + return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + // create user + user := &models.TempUser{ + Email: cmd.Email, + Name: cmd.Name, + OrgId: cmd.OrgId, + Code: cmd.Code, + Role: cmd.Role, + Status: cmd.Status, + RemoteAddr: cmd.RemoteAddr, + InvitedByUserId: cmd.InvitedByUserId, + EmailSentOn: time.Now(), + Created: time.Now().Unix(), + Updated: time.Now().Unix(), + } + + if _, err := sess.Insert(user); err != nil { + return err + } + + cmd.Result = user + + return nil + }) +} + +func (ss *xormStore) UpdateTempUserWithEmailSent(ctx context.Context, cmd *models.UpdateTempUserWithEmailSentCommand) error { + return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + user := &models.TempUser{ + EmailSent: true, + EmailSentOn: time.Now(), + } + + _, err := sess.Where("code = ?", cmd.Code).Cols("email_sent", "email_sent_on").Update(user) + + return err + }) +} + +func (ss *xormStore) GetTempUsersQuery(ctx context.Context, query *models.GetTempUsersQuery) error { + return ss.db.WithDbSession(ctx, func(dbSess *sqlstore.DBSession) error { + rawSQL := `SELECT + tu.id as id, + tu.org_id as org_id, + tu.email as email, + tu.name as name, + tu.role as role, + tu.code as code, + tu.status as status, + tu.email_sent as email_sent, + tu.email_sent_on as email_sent_on, + tu.created as created, + u.login as invited_by_login, + u.name as invited_by_name, + u.email as invited_by_email + FROM ` + ss.db.GetDialect().Quote("temp_user") + ` as tu + LEFT OUTER JOIN ` + ss.db.GetDialect().Quote("user") + ` as u on u.id = tu.invited_by_user_id + WHERE tu.status=?` + params := []interface{}{string(query.Status)} + + if query.OrgId > 0 { + rawSQL += ` AND tu.org_id=?` + params = append(params, query.OrgId) + } + + if query.Email != "" { + rawSQL += ` AND tu.email=?` + params = append(params, query.Email) + } + + rawSQL += " ORDER BY tu.created desc" + + query.Result = make([]*models.TempUserDTO, 0) + sess := dbSess.SQL(rawSQL, params...) + err := sess.Find(&query.Result) + return err + }) +} + +func (ss *xormStore) GetTempUserByCode(ctx context.Context, query *models.GetTempUserByCodeQuery) error { + return ss.db.WithDbSession(ctx, func(dbSess *sqlstore.DBSession) error { + var rawSQL = `SELECT + tu.id as id, + tu.org_id as org_id, + tu.email as email, + tu.name as name, + tu.role as role, + tu.code as code, + tu.status as status, + tu.email_sent as email_sent, + tu.email_sent_on as email_sent_on, + tu.created as created, + u.login as invited_by_login, + u.name as invited_by_name, + u.email as invited_by_email + FROM ` + ss.db.GetDialect().Quote("temp_user") + ` as tu + LEFT OUTER JOIN ` + ss.db.GetDialect().Quote("user") + ` as u on u.id = tu.invited_by_user_id + WHERE tu.code=?` + + var tempUser models.TempUserDTO + sess := dbSess.SQL(rawSQL, query.Code) + has, err := sess.Get(&tempUser) + + if err != nil { + return err + } else if !has { + return models.ErrTempUserNotFound + } + + query.Result = &tempUser + return err + }) +} + +func (ss *xormStore) ExpireOldUserInvites(ctx context.Context, cmd *models.ExpireTempUsersCommand) error { + return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + var rawSQL = "UPDATE temp_user SET status = ?, updated = ? WHERE created <= ? AND status in (?, ?)" + if result, err := sess.Exec(rawSQL, string(models.TmpUserExpired), time.Now().Unix(), cmd.OlderThan.Unix(), string(models.TmpUserSignUpStarted), string(models.TmpUserInvitePending)); err != nil { + return err + } else if cmd.NumExpired, err = result.RowsAffected(); err != nil { + return err + } + return nil + }) +} diff --git a/pkg/services/temp_user/tempuserimpl/store_test.go b/pkg/services/temp_user/tempuserimpl/store_test.go index f790e452fbc..e4a6013fd1a 100644 --- a/pkg/services/temp_user/tempuserimpl/store_test.go +++ b/pkg/services/temp_user/tempuserimpl/store_test.go @@ -1 +1,96 @@ package tempuserimpl + +import ( + "context" + "testing" + "time" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/stretchr/testify/require" +) + +func TestIntegrationTempUserCommandsAndQueries(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + var store store + cmd := models.CreateTempUserCommand{ + OrgId: 2256, + Name: "hello", + Code: "asd", + Email: "e@as.co", + Status: models.TmpUserInvitePending, + } + setup := func(t *testing.T) { + db := sqlstore.InitTestDB(t) + store = &xormStore{db: db} + err := store.CreateTempUser(context.Background(), &cmd) + require.Nil(t, err) + } + + t.Run("Should be able to get temp users by org id", func(t *testing.T) { + setup(t) + query := models.GetTempUsersQuery{OrgId: 2256, Status: models.TmpUserInvitePending} + err := store.GetTempUsersQuery(context.Background(), &query) + + require.Nil(t, err) + require.Equal(t, 1, len(query.Result)) + }) + + t.Run("Should be able to get temp users by email", func(t *testing.T) { + setup(t) + query := models.GetTempUsersQuery{Email: "e@as.co", Status: models.TmpUserInvitePending} + err := store.GetTempUsersQuery(context.Background(), &query) + + require.Nil(t, err) + require.Equal(t, 1, len(query.Result)) + }) + + t.Run("Should be able to get temp users by code", func(t *testing.T) { + setup(t) + query := models.GetTempUserByCodeQuery{Code: "asd"} + err := store.GetTempUserByCode(context.Background(), &query) + + require.Nil(t, err) + require.Equal(t, "hello", query.Result.Name) + }) + + t.Run("Should be able update status", func(t *testing.T) { + setup(t) + cmd2 := models.UpdateTempUserStatusCommand{Code: "asd", Status: models.TmpUserRevoked} + err := store.UpdateTempUserStatus(context.Background(), &cmd2) + require.Nil(t, err) + }) + + t.Run("Should be able update email sent and email sent on", func(t *testing.T) { + setup(t) + cmd2 := models.UpdateTempUserWithEmailSentCommand{Code: cmd.Result.Code} + err := store.UpdateTempUserWithEmailSent(context.Background(), &cmd2) + require.Nil(t, err) + + query := models.GetTempUsersQuery{OrgId: 2256, Status: models.TmpUserInvitePending} + err = store.GetTempUsersQuery(context.Background(), &query) + + require.Nil(t, err) + require.True(t, query.Result[0].EmailSent) + require.False(t, query.Result[0].EmailSentOn.UTC().Before(query.Result[0].Created.UTC())) + }) + + t.Run("Should be able expire temp user", func(t *testing.T) { + setup(t) + createdAt := time.Unix(cmd.Result.Created, 0) + cmd2 := models.ExpireTempUsersCommand{OlderThan: createdAt.Add(1 * time.Second)} + err := store.ExpireOldUserInvites(context.Background(), &cmd2) + require.Nil(t, err) + require.Equal(t, int64(1), cmd2.NumExpired) + + t.Run("Should do nothing when no temp users to expire", func(t *testing.T) { + createdAt := time.Unix(cmd.Result.Created, 0) + cmd2 := models.ExpireTempUsersCommand{OlderThan: createdAt.Add(1 * time.Second)} + err := store.ExpireOldUserInvites(context.Background(), &cmd2) + require.Nil(t, err) + require.Equal(t, int64(0), cmd2.NumExpired) + }) + }) +} diff --git a/pkg/services/temp_user/tempuserimpl/temp_user.go b/pkg/services/temp_user/tempuserimpl/temp_user.go index d9d3e61821a..a763456e992 100644 --- a/pkg/services/temp_user/tempuserimpl/temp_user.go +++ b/pkg/services/temp_user/tempuserimpl/temp_user.go @@ -4,25 +4,24 @@ import ( "context" "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/db" tempuser "github.com/grafana/grafana/pkg/services/temp_user" ) type Service struct { - // TODO remove sqlstore - sqlStore *sqlstore.SQLStore + store store } func ProvideService( - ss *sqlstore.SQLStore, + db db.DB, ) tempuser.Service { return &Service{ - sqlStore: ss, + store: &xormStore{db: db}, } } func (s *Service) UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error { - err := s.sqlStore.UpdateTempUserStatus(ctx, cmd) + err := s.store.UpdateTempUserStatus(ctx, cmd) if err != nil { return err } @@ -30,7 +29,7 @@ func (s *Service) UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTe } func (s *Service) CreateTempUser(ctx context.Context, cmd *models.CreateTempUserCommand) error { - err := s.sqlStore.CreateTempUser(ctx, cmd) + err := s.store.CreateTempUser(ctx, cmd) if err != nil { return err } @@ -38,7 +37,7 @@ func (s *Service) CreateTempUser(ctx context.Context, cmd *models.CreateTempUser } func (s *Service) UpdateTempUserWithEmailSent(ctx context.Context, cmd *models.UpdateTempUserWithEmailSentCommand) error { - err := s.sqlStore.UpdateTempUserWithEmailSent(ctx, cmd) + err := s.store.UpdateTempUserWithEmailSent(ctx, cmd) if err != nil { return err } @@ -46,7 +45,7 @@ func (s *Service) UpdateTempUserWithEmailSent(ctx context.Context, cmd *models.U } func (s *Service) GetTempUsersQuery(ctx context.Context, cmd *models.GetTempUsersQuery) error { - err := s.sqlStore.GetTempUsersQuery(ctx, cmd) + err := s.store.GetTempUsersQuery(ctx, cmd) if err != nil { return err } @@ -54,7 +53,7 @@ func (s *Service) GetTempUsersQuery(ctx context.Context, cmd *models.GetTempUser } func (s *Service) GetTempUserByCode(ctx context.Context, cmd *models.GetTempUserByCodeQuery) error { - err := s.sqlStore.GetTempUserByCode(ctx, cmd) + err := s.store.GetTempUserByCode(ctx, cmd) if err != nil { return err } @@ -62,7 +61,7 @@ func (s *Service) GetTempUserByCode(ctx context.Context, cmd *models.GetTempUser } func (s *Service) ExpireOldUserInvites(ctx context.Context, cmd *models.ExpireTempUsersCommand) error { - err := s.sqlStore.ExpireOldUserInvites(ctx, cmd) + err := s.store.ExpireOldUserInvites(ctx, cmd) if err != nil { return err }