grafana/pkg/api/user_test.go

327 lines
11 KiB
Go
Raw Normal View History

package api
import (
"context"
"encoding/json"
Admin: New Admin User page (#20498) * admin: user page to react WIP * admin user page: basic view * admin user page: refactor, extract orgs and permissions components * admin user: change sessions actions styles * admin user: add disable button * user admin: add change grafana admin action * user admin: able to change org role and remove org * user admin: confirm force logout * user admin: change org button style * user admin: add confirm modals for critical actions * user admin: lock down ldap user info * user admin: align with latest design changes * user admin: add LDAP sync * admin user: confirm button * user admin: add to org modal * user admin: fix ConfirmButton story * admin user: handle grafana admin change * ConfirmButton: make styled component * ConfirmButton: completely styled component * User Admin: permissions section refactor * admin user: refactor (orgs and sessions) * ConfirmButton: able to set confirm variant * admin user: inline org removal * admin user: show ldap sync info only for ldap users * admin user: edit profile * ConfirmButton: some fixes after review * Chore: fix storybook build * admin user: rename handlers * admin user: remove LdapUserPage import from routes * Chore: fix ConfirmButton tests * Chore: fix user api endpoint tests * Chore: update failed test snapshots * admin user: redux actions WIP * admin user: use new ConfirmModal component for user profile * admin user: use new ConfirmModal component for sessions * admin user: use lockMessage * ConfirmButton: use primary button as default * admin user: fix ActionButton color * UI: use Icon component for Modal * UI: refactor ConfirmModal after Modal changes * UI: add link button variant * UI: able to use custom ConfirmButton * Chore: fix type errors after ConfirmButton refactor * Chore: revert Graph component changes (works with TS 3.7) * Chore: use Forms.Button instead of ActionButton * admin user: align items * admin user: align add to org modal * UI: organization picker component * admin user: use org picker for AddToOrgModal * admin user: org actions * admin user: connect sessions actions * admin user: updateUserPermissions action * admin user: enable delete user action * admin user: sync ldap user * Chore: refactor, remove unused code * Chore: refactor, move api calls to actions * admin user: set user password action * Chore: refactor, remove unused components * admin user: set input focus on edit * admin user: pass user into debug LDAP mapping * UserAdminPage: Ux changes * UserAdminPage: align buttons to the left * UserAdminPage: align delete user button * UserAdminPage: swap add to org modal buttons * UserAdminPage: set password field to empty when editing * UserAdminPage: fix tests * Updated button border * Chore: fix ConfirmButton after changes introduced in #21092 Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2020-01-13 10:10:19 -06:00
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
authinfostore "github.com/grafana/grafana/pkg/services/login/authinfoservice/database"
"github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/searchusers"
"github.com/grafana/grafana/pkg/services/searchusers/filters"
"github.com/grafana/grafana/pkg/services/secrets/database"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/userimpl"
Chore: Split get user by ID (#52442) * Remove user from preferences, stars, orguser, team member * Fix lint * Add Delete user from org and dashboard acl * Delete user from user auth * Add DeleteUser to quota * Add test files and adjust user auth store * Rename package in wire for user auth * Import Quota Service interface in other services * do the same in tests * fix lint tests * Fix tests * Add some tests * Rename InsertUser and DeleteUser to InsertOrgUser and DeleteOrgUser * Rename DeleteUser to DeleteByUser in quota * changing a method name in few additional places * Fix in other places * Fix lint * Fix tests * Chore: Split Delete User method * Add fakes for userauth * Add mock for access control Delete User permossion, use interface * Use interface for ream guardian * Add simple fake for dashboard acl * Add go routines, clean up, use interfaces * fix lint * Update pkg/services/user/userimpl/user_test.go Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> * Update pkg/services/user/userimpl/user_test.go Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> * Update pkg/services/user/userimpl/user_test.go Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> * Split get user by ID * Use new method in api * Add tests * Aplly emthod in auth info service * Fix lint and some tests * Fix get user by ID * Fix lint Remove unused fakes * Use split get user id in admin users * Use GetbyID in cli commands * Clean up after merge * Remove commented out code * Clena up imports * add back ) * Fix wire generation for runner after merge with main Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
2022-08-02 09:58:05 -05:00
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/setting"
)
func TestUserAPIEndpoint_userLoggedIn(t *testing.T) {
settings := setting.NewCfg()
sqlStore := db.InitTestDB(t)
hs := &HTTPServer{
Cfg: settings,
SQLStore: sqlStore,
AccessControl: acmock.New(),
}
mockResult := user.SearchUserQueryResult{
Users: []*user.UserSearchHitDTO{
{Name: "user1"},
{Name: "user2"},
},
TotalCount: 2,
}
mock := dbtest.NewFakeDB()
userMock := usertest.NewUserServiceFake()
loggedInUserScenario(t, "When calling GET on", "api/users/1", "api/users/:id", func(sc *scenarioContext) {
fakeNow := time.Date(2019, 2, 11, 17, 30, 40, 0, time.UTC)
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
authInfoStore := authinfostore.ProvideAuthInfoStore(sqlStore, secretsService, userMock)
srv := authinfoservice.ProvideAuthInfoService(
&authinfoservice.OSSUserProtectionImpl{},
authInfoStore,
&usagestats.UsageStatsMock{},
)
hs.authInfoService = srv
orgSvc, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotatest.New(false, nil))
require.NoError(t, err)
userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sc.cfg, nil, nil, quotatest.New(false, nil))
require.NoError(t, err)
hs.userService = userSvc
createUserCmd := user.CreateUserCommand{
Email: fmt.Sprint("user", "@test.com"),
Name: "user",
Login: "loginuser",
IsAdmin: true,
}
usr, err := userSvc.CreateUserForTests(context.Background(), &createUserCmd)
require.NoError(t, err)
sc.handlerFunc = hs.GetUserByID
token := &oauth2.Token{
AccessToken: "testaccess",
RefreshToken: "testrefresh",
Expiry: time.Now(),
TokenType: "Bearer",
}
idToken := "testidtoken"
token = token.WithExtra(map[string]interface{}{"id_token": idToken})
login := "loginuser"
query := &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test", UserLookupParams: models.UserLookupParams{Login: &login}}
cmd := &models.UpdateAuthInfoCommand{
UserId: usr.ID,
AuthId: query.AuthId,
AuthModule: query.AuthModule,
OAuthToken: token,
}
err = srv.UpdateAuthInfo(context.Background(), cmd)
require.NoError(t, err)
avatarUrl := dtos.GetGravatarUrl("@test.com")
sc.fakeReqWithParams("GET", sc.url, map[string]string{"id": fmt.Sprintf("%v", usr.ID)}).exec()
expected := user.UserProfileDTO{
ID: 1,
Email: "user@test.com",
Name: "user",
Login: "loginuser",
OrgID: 1,
IsGrafanaAdmin: true,
AuthLabels: []string{},
CreatedAt: fakeNow,
UpdatedAt: fakeNow,
AvatarUrl: avatarUrl,
}
var resp user.UserProfileDTO
require.Equal(t, http.StatusOK, sc.resp.Code)
err = json.Unmarshal(sc.resp.Body.Bytes(), &resp)
require.NoError(t, err)
resp.CreatedAt = fakeNow
resp.UpdatedAt = fakeNow
resp.AvatarUrl = avatarUrl
require.EqualValues(t, expected, resp)
}, mock)
loggedInUserScenario(t, "When calling GET on", "/api/users/lookup", "/api/users/lookup", func(sc *scenarioContext) {
createUserCmd := user.CreateUserCommand{
Email: fmt.Sprint("admin", "@test.com"),
Name: "admin",
Login: "admin",
IsAdmin: true,
}
orgSvc, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotatest.New(false, nil))
require.NoError(t, err)
userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, sc.cfg, nil, nil, quotatest.New(false, nil))
require.NoError(t, err)
_, err = userSvc.Create(context.Background(), &createUserCmd)
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 user.UserProfileDTO
require.Equal(t, http.StatusOK, sc.resp.Code)
err = json.Unmarshal(sc.resp.Body.Bytes(), &resp)
require.NoError(t, err)
}, mock)
loggedInUserScenario(t, "When calling GET on", "/api/users", "/api/users", func(sc *scenarioContext) {
userMock.ExpectedSearchUsers = mockResult
searchUsersService := searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), userMock)
sc.handlerFunc = searchUsersService.SearchUsers
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, 2, len(respJSON.MustArray()))
}, mock)
loggedInUserScenario(t, "When calling GET with page and limit querystring parameters on", "/api/users", "/api/users", func(sc *scenarioContext) {
userMock.ExpectedSearchUsers = mockResult
searchUsersService := searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), userMock)
sc.handlerFunc = searchUsersService.SearchUsers
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, 2, len(respJSON.MustArray()))
}, mock)
loggedInUserScenario(t, "When calling GET on", "/api/users/search", "/api/users/search", func(sc *scenarioContext) {
userMock.ExpectedSearchUsers = mockResult
searchUsersService := searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), userMock)
sc.handlerFunc = searchUsersService.SearchUsersWithPaging
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, 1, respJSON.Get("page").MustInt())
assert.Equal(t, 1000, respJSON.Get("perPage").MustInt())
assert.Equal(t, 2, respJSON.Get("totalCount").MustInt())
assert.Equal(t, 2, len(respJSON.Get("users").MustArray()))
}, mock)
loggedInUserScenario(t, "When calling GET with page and perpage querystring parameters on", "/api/users/search", "/api/users/search", func(sc *scenarioContext) {
userMock.ExpectedSearchUsers = mockResult
searchUsersService := searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), userMock)
sc.handlerFunc = searchUsersService.SearchUsersWithPaging
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "10", "page": "2"}).exec()
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
require.NoError(t, err)
assert.Equal(t, 2, respJSON.Get("page").MustInt())
assert.Equal(t, 10, respJSON.Get("perPage").MustInt())
}, mock)
}
func TestHTTPServer_UpdateUser(t *testing.T) {
settings := setting.NewCfg()
sqlStore := db.InitTestDB(t)
hs := &HTTPServer{
Cfg: settings,
SQLStore: sqlStore,
AccessControl: acmock.New(),
}
updateUserCommand := user.UpdateUserCommand{
Email: fmt.Sprint("admin", "@test.com"),
Name: "admin",
Login: "admin",
UserID: 1,
}
updateUserScenario(t, updateUserContext{
desc: "Should return 403 when the current User is an external user",
url: "/api/users/1",
routePattern: "/api/users/:id",
cmd: updateUserCommand,
fn: func(sc *scenarioContext) {
sc.authInfoService.ExpectedUserAuth = &models.UserAuth{}
sc.fakeReqWithParams("PUT", sc.url, map[string]string{"id": "1"}).exec()
assert.Equal(t, 403, sc.resp.Code)
},
}, hs)
}
type updateUserContext struct {
desc string
url string
routePattern string
cmd user.UpdateUserCommand
fn scenarioFunc
}
func updateUserScenario(t *testing.T, ctx updateUserContext, hs *HTTPServer) {
t.Run(fmt.Sprintf("%s %s", ctx.desc, ctx.url), func(t *testing.T) {
sc := setupScenarioContext(t, ctx.url)
sc.authInfoService = &logintest.AuthInfoServiceFake{}
hs.authInfoService = sc.authInfoService
sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
c.Req.Body = mockRequestBody(ctx.cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.OrgID = testOrgID
sc.context.UserID = testUserID
return hs.UpdateUser(c)
})
sc.m.Put(ctx.routePattern, sc.defaultHandler)
ctx.fn(sc)
})
}
func TestHTTPServer_UpdateSignedInUser(t *testing.T) {
settings := setting.NewCfg()
sqlStore := db.InitTestDB(t)
hs := &HTTPServer{
Cfg: settings,
SQLStore: sqlStore,
AccessControl: acmock.New(),
}
updateUserCommand := user.UpdateUserCommand{
Email: fmt.Sprint("admin", "@test.com"),
Name: "admin",
Login: "admin",
UserID: 1,
}
updateSignedInUserScenario(t, updateUserContext{
desc: "Should return 403 when the current User is an external user",
url: "/api/users/",
routePattern: "/api/users/",
cmd: updateUserCommand,
fn: func(sc *scenarioContext) {
sc.authInfoService.ExpectedUserAuth = &models.UserAuth{}
sc.fakeReqWithParams("PUT", sc.url, map[string]string{"id": "1"}).exec()
assert.Equal(t, 403, sc.resp.Code)
},
}, hs)
}
func updateSignedInUserScenario(t *testing.T, ctx updateUserContext, hs *HTTPServer) {
t.Run(fmt.Sprintf("%s %s", ctx.desc, ctx.url), func(t *testing.T) {
sc := setupScenarioContext(t, ctx.url)
sc.authInfoService = &logintest.AuthInfoServiceFake{}
hs.authInfoService = sc.authInfoService
sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
c.Req.Body = mockRequestBody(ctx.cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.OrgID = testOrgID
sc.context.UserID = testUserID
return hs.UpdateSignedInUser(c)
})
sc.m.Put(ctx.routePattern, sc.defaultHandler)
ctx.fn(sc)
})
}