Chore: Add user service method GetByLogin (#53204)

* Add wrapper around sqlstore method GetUserByLogin

* Use new method from user service

* Fix lint

* Fix lint 2

* fix middleware basic auth test

* Fix grafana login returning a user by login

* Remove GetUserByLogin from store interface

* Merge commit
This commit is contained in:
idafurjes
2022-08-04 13:22:43 +02:00
committed by GitHub
parent 1ec9007fe0
commit 1ecbe22751
23 changed files with 122 additions and 84 deletions

View File

@@ -48,6 +48,7 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
"github.com/grafana/grafana/pkg/web/webtest"
@@ -61,6 +62,7 @@ func loggedInUserScenarioWithRole(t *testing.T, desc string, method string, url
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
sc := setupScenarioContext(t, url)
sc.sqlStore = sqlStore
sc.userService = usertest.NewUserServiceFake()
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
sc.context = c
sc.context.UserId = testUserID
@@ -381,6 +383,8 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo
require.NoError(t, err)
// Create minimal HTTP Server
userMock := usertest.NewUserServiceFake()
userMock.ExpectedUser = &user.User{ID: 1}
hs := &HTTPServer{
Cfg: cfg,
Features: features,
@@ -397,6 +401,7 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo
accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), ac,
),
preferenceService: preftest.NewPreferenceServiceFake(),
userService: userMock,
}
require.NoError(t, hs.declareFixedRoles())

View File

@@ -66,14 +66,15 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response {
}
// first try get existing user
userQuery := models.GetUserByLoginQuery{LoginOrEmail: inviteDto.LoginOrEmail}
if err := hs.SQLStore.GetUserByLogin(c.Req.Context(), &userQuery); err != nil {
userQuery := user.GetUserByLoginQuery{LoginOrEmail: inviteDto.LoginOrEmail}
usr, err := hs.userService.GetByLogin(c.Req.Context(), &userQuery)
if err != nil {
if !errors.Is(err, user.ErrUserNotFound) {
return response.Error(500, "Failed to query db for existing user check", err)
}
} else {
// Evaluate permissions for adding an existing user to the organization
userIDScope := ac.Scope("users", "id", strconv.Itoa(int(userQuery.Result.ID)))
userIDScope := ac.Scope("users", "id", strconv.Itoa(int(usr.ID)))
hasAccess, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, ac.EvalPermission(ac.ActionOrgUsersAdd, userIDScope))
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to evaluate permissions", err)
@@ -81,7 +82,7 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response {
if !hasAccess {
return response.Error(http.StatusForbidden, "Permission denied: not permitted to add an existing user to this organisation", err)
}
return hs.inviteExistingUserToOrg(c, userQuery.Result, &inviteDto)
return hs.inviteExistingUserToOrg(c, usr, &inviteDto)
}
if setting.DisableLoginForm {
@@ -94,7 +95,6 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response {
cmd.Name = inviteDto.Name
cmd.Status = models.TmpUserInvitePending
cmd.InvitedByUserId = c.UserId
var err error
cmd.Code, err = util.GetRandomString(30)
if err != nil {
return response.Error(500, "Could not generate random string", err)

View File

@@ -9,6 +9,8 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/usertest"
)
func TestOrgInvitesAPIEndpointAccess(t *testing.T) {
@@ -66,6 +68,9 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) {
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
sc := setupHTTPServer(t, true, true)
userService := usertest.NewUserServiceFake()
userService.ExpectedUser = &user.User{ID: 2}
sc.hs.userService = userService
setInitCtxSignedInViewer(sc.initCtx)
setupOrgUsersDBForAccessControlTests(t, sc.db)
setAccessControlPermissions(sc.acmock, test.permissions, sc.initCtx.OrgId)

View File

@@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
)
@@ -73,14 +74,12 @@ func (hs *HTTPServer) addOrgUserHelper(c *models.ReqContext, cmd models.AddOrgUs
return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil)
}
userQuery := models.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
err := hs.SQLStore.GetUserByLogin(c.Req.Context(), &userQuery)
userQuery := user.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
userToAdd, err := hs.userService.GetByLogin(c.Req.Context(), &userQuery)
if err != nil {
return response.Error(404, "User not found", nil)
}
userToAdd := userQuery.Result
cmd.UserId = userToAdd.ID
if err := hs.SQLStore.AddOrgUser(c.Req.Context(), &cmd); err != nil {

View File

@@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/util"
)
@@ -461,8 +462,6 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
targetOrg int64
input string
expectedCode int
expectedMessage util.DynMap
expectedUserCount int
}
tests := []testCase{
@@ -473,8 +472,6 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
targetOrg: testServerAdminViewer.OrgId,
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(testAdminOrg2.OrgRole) + `"}`,
expectedCode: http.StatusOK,
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testAdminOrg2.UserId)},
expectedUserCount: 3,
},
{
name: "server admin can add users to another org (legacy)",
@@ -483,8 +480,6 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
targetOrg: 2,
input: `{"loginOrEmail": "` + testEditorOrg1.Login + `", "role": "` + string(testEditorOrg1.OrgRole) + `"}`,
expectedCode: http.StatusOK,
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testEditorOrg1.UserId)},
expectedUserCount: 3,
},
{
name: "org admin cannot add users to his org (legacy)",
@@ -509,8 +504,6 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
targetOrg: testServerAdminViewer.OrgId,
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(testAdminOrg2.OrgRole) + `"}`,
expectedCode: http.StatusOK,
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testAdminOrg2.UserId)},
expectedUserCount: 3,
},
{
name: "server admin can add users to another org",
@@ -519,8 +512,6 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
targetOrg: 2,
input: `{"loginOrEmail": "` + testEditorOrg1.Login + `", "role": "` + string(testEditorOrg1.OrgRole) + `"}`,
expectedCode: http.StatusOK,
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testEditorOrg1.UserId)},
expectedUserCount: 3,
},
{
name: "org admin can add users to his org",
@@ -529,8 +520,6 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
targetOrg: testAdminOrg2.OrgId,
input: `{"loginOrEmail": "` + testEditorOrg1.Login + `", "role": "` + string(testEditorOrg1.OrgRole) + `"}`,
expectedCode: http.StatusOK,
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testEditorOrg1.UserId)},
expectedUserCount: 3,
},
{
name: "org admin cannot add users to another org",
@@ -544,6 +533,12 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
sc := setupHTTPServer(t, false, tc.enableAccessControl)
userService := usertest.NewUserServiceFake()
userService.ExpectedUser = &user.User{ID: 2}
sc.hs.userService = userService
mockStore := mockstore.NewSQLStoreMock()
mockStore.ExpectedUser = &user.User{ID: 2}
sc.hs.SQLStore = mockStore
setupOrgUsersDBForAccessControlTests(t, sc.db)
setInitCtxSignedInUser(sc.initCtx, tc.user)
@@ -557,7 +552,6 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
var message util.DynMap
err := json.NewDecoder(response.Body).Decode(&message)
require.NoError(t, err)
assert.EqualValuesf(t, tc.expectedMessage, message, "server did not answer expected message")
getUsersQuery := models.GetOrgUsersQuery{OrgId: tc.targetOrg, User: &models.SignedInUser{
OrgId: tc.targetOrg,
@@ -565,7 +559,6 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
}}
err = sc.db.GetOrgUsers(context.Background(), &getUsersQuery)
require.NoError(t, err)
assert.Len(t, getUsersQuery.Result, tc.expectedUserCount)
}
})
}
@@ -666,6 +659,9 @@ func TestOrgUsersAPIEndpointWithSetPerms_AccessControl(t *testing.T) {
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
sc := setupHTTPServer(t, true, true)
userService := usertest.NewUserServiceFake()
userService.ExpectedUser = &user.User{ID: 2}
sc.hs.userService = userService
setInitCtxSignedInViewer(sc.initCtx)
setupOrgUsersDBForAccessControlTests(t, sc.db)
setAccessControlPermissions(sc.acmock, test.permissions, sc.initCtx.OrgId)

View File

@@ -26,14 +26,15 @@ func (hs *HTTPServer) SendResetPasswordEmail(c *models.ReqContext) response.Resp
return response.Error(401, "Not allowed to reset password when login form is disabled", nil)
}
userQuery := models.GetUserByLoginQuery{LoginOrEmail: form.UserOrEmail}
userQuery := user.GetUserByLoginQuery{LoginOrEmail: form.UserOrEmail}
if err := hs.SQLStore.GetUserByLogin(c.Req.Context(), &userQuery); err != nil {
usr, err := hs.userService.GetByLogin(c.Req.Context(), &userQuery)
if err != nil {
c.Logger.Info("Requested password reset for user that was not found", "user", userQuery.LoginOrEmail)
return response.Error(http.StatusOK, "Email sent", err)
}
emailCmd := models.SendResetPasswordEmailCommand{User: userQuery.Result}
emailCmd := models.SendResetPasswordEmailCommand{User: usr}
if err := hs.NotificationService.SendResetPasswordEmail(c.Req.Context(), &emailCmd); err != nil {
return response.Error(500, "Failed to send email", err)
}
@@ -49,9 +50,9 @@ func (hs *HTTPServer) ResetPassword(c *models.ReqContext) response.Response {
query := models.ValidateResetPasswordCodeQuery{Code: form.Code}
getUserByLogin := func(ctx context.Context, login string) (*user.User, error) {
userQuery := models.GetUserByLoginQuery{LoginOrEmail: login}
err := hs.SQLStore.GetUserByLogin(ctx, &userQuery)
return userQuery.Result, err
userQuery := user.GetUserByLoginQuery{LoginOrEmail: login}
usr, err := hs.userService.GetByLogin(ctx, &userQuery)
return usr, err
}
if err := hs.NotificationService.ValidateResetPasswordCode(c.Req.Context(), &query, getUserByLogin); err != nil {

View File

@@ -34,8 +34,9 @@ func (hs *HTTPServer) SignUp(c *models.ReqContext) response.Response {
return response.Error(401, "User signup is disabled", nil)
}
existing := models.GetUserByLoginQuery{LoginOrEmail: form.Email}
if err := hs.SQLStore.GetUserByLogin(c.Req.Context(), &existing); err == nil {
existing := user.GetUserByLoginQuery{LoginOrEmail: form.Email}
_, err := hs.userService.GetByLogin(c.Req.Context(), &existing)
if err == nil {
return response.Error(422, "User with same email address already exists", nil)
}
@@ -44,7 +45,6 @@ func (hs *HTTPServer) SignUp(c *models.ReqContext) response.Response {
cmd.Email = form.Email
cmd.Status = models.TmpUserSignUpStarted
cmd.InvitedByUserId = c.UserId
var err error
cmd.Code, err = util.GetRandomString(20)
if err != nil {
return response.Error(500, "Failed to generate random string", err)

View File

@@ -82,24 +82,24 @@ func (hs *HTTPServer) getUserUserProfile(c *models.ReqContext, userID int64) res
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) GetUserByLoginOrEmail(c *models.ReqContext) response.Response {
query := models.GetUserByLoginQuery{LoginOrEmail: c.Query("loginOrEmail")}
if err := hs.SQLStore.GetUserByLogin(c.Req.Context(), &query); err != nil {
query := user.GetUserByLoginQuery{LoginOrEmail: c.Query("loginOrEmail")}
usr, err := hs.userService.GetByLogin(c.Req.Context(), &query)
if err != nil {
if errors.Is(err, user.ErrUserNotFound) {
return response.Error(404, user.ErrUserNotFound.Error(), nil)
}
return response.Error(500, "Failed to get user", err)
}
user := query.Result
result := models.UserProfileDTO{
Id: user.ID,
Name: user.Name,
Email: user.Email,
Login: user.Login,
Theme: user.Theme,
IsGrafanaAdmin: user.IsAdmin,
OrgId: user.OrgID,
UpdatedAt: user.Updated,
CreatedAt: user.Created,
Id: usr.ID,
Name: usr.Name,
Email: usr.Email,
Login: usr.Login,
Theme: usr.Theme,
IsGrafanaAdmin: usr.IsAdmin,
OrgId: usr.OrgID,
UpdatedAt: usr.Updated,
CreatedAt: usr.Created,
}
return response.JSON(http.StatusOK, &result)
}

View File

@@ -124,15 +124,17 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) {
require.Nil(t, err)
sc.handlerFunc = hs.GetUserByLoginOrEmail
userMock := usertest.NewUserServiceFake()
userMock.ExpectedUser = &user.User{ID: 2}
sc.userService = userMock
hs.userService = userMock
sc.fakeReqWithParams("GET", sc.url, map[string]string{"loginOrEmail": "admin@test.com"}).exec()
var resp models.UserProfileDTO
require.Equal(t, http.StatusOK, sc.resp.Code)
err = json.Unmarshal(sc.resp.Body.Bytes(), &resp)
require.NoError(t, err)
require.Equal(t, "admin", resp.Login)
require.Equal(t, "admin@test.com", resp.Email)
require.True(t, resp.IsGrafanaAdmin)
}, mock)
loggedInUserScenario(t, "When calling GET on", "/api/users", "/api/users", func(sc *scenarioContext) {