mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 18:30:41 -06:00
701f6d5436
* UserService: update callers to use the UserService instead of calling sqlstore directly There is one major change hiding in this PR. UserService.Delete originally called a number of services to delete user-related records. I moved everything except the actual call to the user table, and moved those into the API. This was done to avoid dependencies cycles; many of our services depend on the user service, so the user service itself should have as few dependencies as possible.
425 lines
15 KiB
Go
425 lines
15 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"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/models"
|
|
"github.com/grafana/grafana/pkg/services/auth"
|
|
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
|
"github.com/grafana/grafana/pkg/services/login/logintest"
|
|
"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/user"
|
|
"github.com/grafana/grafana/pkg/services/user/usertest"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
const (
|
|
testLogin = "test@example.com"
|
|
testPassword = "password"
|
|
nonExistingOrgID = 1000
|
|
existingTestLogin = "existing@example.com"
|
|
)
|
|
|
|
func TestAdminAPIEndpoint(t *testing.T) {
|
|
const role = org.RoleAdmin
|
|
userService := usertest.NewUserServiceFake()
|
|
t.Run("Given a server admin attempts to remove themselves as an admin", func(t *testing.T) {
|
|
updateCmd := dtos.AdminUpdateUserPermissionsForm{
|
|
IsGrafanaAdmin: false,
|
|
}
|
|
userService := usertest.FakeUserService{ExpectedError: user.ErrLastGrafanaAdmin}
|
|
putAdminScenario(t, "When calling PUT on", "/api/admin/users/1/permissions",
|
|
"/api/admin/users/:id/permissions", role, updateCmd, func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 400, sc.resp.Code)
|
|
}, nil, &userService)
|
|
})
|
|
|
|
t.Run("When a server admin attempts to logout himself from all devices", func(t *testing.T) {
|
|
adminLogoutUserScenario(t, "Should not be allowed when calling POST on",
|
|
"/api/admin/users/1/logout", "/api/admin/users/:id/logout", func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 400, sc.resp.Code)
|
|
}, userService)
|
|
})
|
|
|
|
t.Run("When a server admin attempts to logout a non-existing user from all devices", func(t *testing.T) {
|
|
mockUserService := usertest.NewUserServiceFake()
|
|
mockUserService.ExpectedError = user.ErrUserNotFound
|
|
|
|
adminLogoutUserScenario(t, "Should return not found when calling POST on", "/api/admin/users/200/logout",
|
|
"/api/admin/users/:id/logout", func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 404, sc.resp.Code)
|
|
}, mockUserService)
|
|
})
|
|
|
|
t.Run("When a server admin attempts to revoke an auth token for a non-existing user", func(t *testing.T) {
|
|
cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2}
|
|
mockUser := usertest.NewUserServiceFake()
|
|
mockUser.ExpectedError = user.ErrUserNotFound
|
|
adminRevokeUserAuthTokenScenario(t, "Should return not found when calling POST on",
|
|
"/api/admin/users/200/revoke-auth-token", "/api/admin/users/:id/revoke-auth-token", cmd, func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 404, sc.resp.Code)
|
|
}, mockUser)
|
|
})
|
|
|
|
t.Run("When a server admin gets auth tokens for a non-existing user", func(t *testing.T) {
|
|
mockUserService := usertest.NewUserServiceFake()
|
|
mockUserService.ExpectedError = user.ErrUserNotFound
|
|
adminGetUserAuthTokensScenario(t, "Should return not found when calling GET on",
|
|
"/api/admin/users/200/auth-tokens", "/api/admin/users/:id/auth-tokens", func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 404, sc.resp.Code)
|
|
}, mockUserService)
|
|
})
|
|
|
|
t.Run("When a server admin attempts to enable/disable a nonexistent user", func(t *testing.T) {
|
|
adminDisableUserScenario(t, "Should return user not found on a POST request", "enable",
|
|
"/api/admin/users/42/enable", "/api/admin/users/:id/enable", func(sc *scenarioContext) {
|
|
userService := sc.userService.(*usertest.FakeUserService)
|
|
sc.authInfoService.ExpectedError = user.ErrUserNotFound
|
|
|
|
userService.ExpectedError = user.ErrUserNotFound
|
|
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
|
|
assert.Equal(t, 404, sc.resp.Code)
|
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "user not found", respJSON.Get("message").MustString())
|
|
})
|
|
|
|
adminDisableUserScenario(t, "Should return user not found on a POST request", "disable",
|
|
"/api/admin/users/42/disable", "/api/admin/users/:id/disable", func(sc *scenarioContext) {
|
|
userService := sc.userService.(*usertest.FakeUserService)
|
|
sc.authInfoService.ExpectedError = user.ErrUserNotFound
|
|
userService.ExpectedError = user.ErrUserNotFound
|
|
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
|
|
assert.Equal(t, 404, sc.resp.Code)
|
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "user not found", respJSON.Get("message").MustString())
|
|
})
|
|
})
|
|
|
|
t.Run("When a server admin attempts to disable/enable external user", func(t *testing.T) {
|
|
adminDisableUserScenario(t, "Should return Could not disable external user error", "disable",
|
|
"/api/admin/users/42/disable", "/api/admin/users/:id/disable", func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 500, sc.resp.Code)
|
|
|
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Could not disable external user", respJSON.Get("message").MustString())
|
|
|
|
assert.Equal(t, int64(42), sc.authInfoService.LatestUserID)
|
|
})
|
|
|
|
adminDisableUserScenario(t, "Should return Could not enable external user error", "enable",
|
|
"/api/admin/users/42/enable", "/api/admin/users/:id/enable", func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 500, sc.resp.Code)
|
|
|
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Could not enable external user", respJSON.Get("message").MustString())
|
|
|
|
userID := sc.authInfoService.LatestUserID
|
|
assert.Equal(t, int64(42), userID)
|
|
})
|
|
})
|
|
|
|
t.Run("When a server admin attempts to delete a nonexistent user", func(t *testing.T) {
|
|
adminDeleteUserScenario(t, "Should return user not found error", "/api/admin/users/42",
|
|
"/api/admin/users/:id", func(sc *scenarioContext) {
|
|
sc.sqlStore.(*mockstore.SQLStoreMock).ExpectedError = user.ErrUserNotFound
|
|
sc.userService.(*usertest.FakeUserService).ExpectedError = user.ErrUserNotFound
|
|
sc.authInfoService.ExpectedError = user.ErrUserNotFound
|
|
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
|
|
|
assert.Equal(t, 404, sc.resp.Code)
|
|
|
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "user not found", respJSON.Get("message").MustString())
|
|
})
|
|
})
|
|
|
|
t.Run("When a server admin attempts to create a user", func(t *testing.T) {
|
|
t.Run("Without an organization", func(t *testing.T) {
|
|
createCmd := dtos.AdminCreateUserForm{
|
|
Login: testLogin,
|
|
Password: testPassword,
|
|
}
|
|
|
|
adminCreateUserScenario(t, "Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 200, sc.resp.Code)
|
|
|
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, testUserID, respJSON.Get("id").MustInt64())
|
|
assert.Equal(t, "User created", respJSON.Get("message").MustString())
|
|
})
|
|
})
|
|
|
|
t.Run("With an organization", func(t *testing.T) {
|
|
createCmd := dtos.AdminCreateUserForm{
|
|
Login: testLogin,
|
|
Password: testPassword,
|
|
OrgId: testOrgID,
|
|
}
|
|
|
|
adminCreateUserScenario(t, "Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 200, sc.resp.Code)
|
|
|
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, testUserID, respJSON.Get("id").MustInt64())
|
|
assert.Equal(t, "User created", respJSON.Get("message").MustString())
|
|
})
|
|
})
|
|
|
|
t.Run("With a nonexistent organization", func(t *testing.T) {
|
|
createCmd := dtos.AdminCreateUserForm{
|
|
Login: testLogin,
|
|
Password: testPassword,
|
|
OrgId: nonExistingOrgID,
|
|
}
|
|
|
|
adminCreateUserScenario(t, "Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 400, sc.resp.Code)
|
|
|
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "organization not found", respJSON.Get("message").MustString())
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("When a server admin attempts to create a user with an already existing email/login", func(t *testing.T) {
|
|
createCmd := dtos.AdminCreateUserForm{
|
|
Login: existingTestLogin,
|
|
Password: testPassword,
|
|
}
|
|
|
|
adminCreateUserScenario(t, "Should return an error", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
assert.Equal(t, 412, sc.resp.Code)
|
|
|
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "user already exists", respJSON.Get("error").MustString())
|
|
})
|
|
})
|
|
}
|
|
|
|
func putAdminScenario(t *testing.T, desc string, url string, routePattern string, role org.RoleType,
|
|
cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc, sqlStore sqlstore.Store, userSvc user.Service) {
|
|
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
|
hs := &HTTPServer{
|
|
Cfg: setting.NewCfg(),
|
|
SQLStore: sqlStore,
|
|
authInfoService: &logintest.AuthInfoServiceFake{},
|
|
userService: userSvc,
|
|
}
|
|
|
|
sc := setupScenarioContext(t, url)
|
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
|
c.Req.Body = mockRequestBody(cmd)
|
|
c.Req.Header.Add("Content-Type", "application/json")
|
|
sc.context = c
|
|
sc.context.UserID = testUserID
|
|
sc.context.OrgID = testOrgID
|
|
sc.context.OrgRole = role
|
|
|
|
return hs.AdminUpdateUserPermissions(c)
|
|
})
|
|
|
|
sc.m.Put(routePattern, sc.defaultHandler)
|
|
|
|
fn(sc)
|
|
})
|
|
}
|
|
|
|
func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, userService *usertest.FakeUserService) {
|
|
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
|
hs := HTTPServer{
|
|
AuthTokenService: auth.NewFakeUserAuthTokenService(),
|
|
userService: userService,
|
|
}
|
|
|
|
sc := setupScenarioContext(t, url)
|
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
|
t.Log("Route handler invoked", "url", c.Req.URL)
|
|
|
|
sc.context = c
|
|
sc.context.UserID = testUserID
|
|
sc.context.OrgID = testOrgID
|
|
sc.context.OrgRole = org.RoleAdmin
|
|
|
|
return hs.AdminLogoutUser(c)
|
|
})
|
|
|
|
sc.m.Post(routePattern, sc.defaultHandler)
|
|
|
|
fn(sc)
|
|
})
|
|
}
|
|
|
|
func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd, fn scenarioFunc, userService user.Service) {
|
|
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
|
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
|
|
|
hs := HTTPServer{
|
|
AuthTokenService: fakeAuthTokenService,
|
|
userService: userService,
|
|
}
|
|
|
|
sc := setupScenarioContext(t, url)
|
|
sc.userAuthTokenService = fakeAuthTokenService
|
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
|
c.Req.Body = mockRequestBody(cmd)
|
|
c.Req.Header.Add("Content-Type", "application/json")
|
|
sc.context = c
|
|
sc.context.UserID = testUserID
|
|
sc.context.OrgID = testOrgID
|
|
sc.context.OrgRole = org.RoleAdmin
|
|
|
|
return hs.AdminRevokeUserAuthToken(c)
|
|
})
|
|
|
|
sc.m.Post(routePattern, sc.defaultHandler)
|
|
|
|
fn(sc)
|
|
})
|
|
}
|
|
|
|
func adminGetUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, userService *usertest.FakeUserService) {
|
|
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
|
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
|
|
|
hs := HTTPServer{
|
|
AuthTokenService: fakeAuthTokenService,
|
|
userService: userService,
|
|
}
|
|
|
|
sc := setupScenarioContext(t, url)
|
|
sc.userAuthTokenService = fakeAuthTokenService
|
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
|
sc.context = c
|
|
sc.context.UserID = testUserID
|
|
sc.context.OrgID = testOrgID
|
|
sc.context.OrgRole = org.RoleAdmin
|
|
|
|
return hs.AdminGetUserAuthTokens(c)
|
|
})
|
|
|
|
sc.m.Get(routePattern, sc.defaultHandler)
|
|
|
|
fn(sc)
|
|
})
|
|
}
|
|
|
|
func adminDisableUserScenario(t *testing.T, desc string, action string, url string, routePattern string, fn scenarioFunc) {
|
|
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
|
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
|
|
|
authInfoService := &logintest.AuthInfoServiceFake{}
|
|
|
|
hs := HTTPServer{
|
|
SQLStore: mockstore.NewSQLStoreMock(),
|
|
AuthTokenService: fakeAuthTokenService,
|
|
authInfoService: authInfoService,
|
|
userService: usertest.NewUserServiceFake(),
|
|
}
|
|
|
|
sc := setupScenarioContext(t, url)
|
|
sc.sqlStore = hs.SQLStore
|
|
sc.authInfoService = authInfoService
|
|
sc.userService = hs.userService
|
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
|
sc.context = c
|
|
sc.context.UserID = testUserID
|
|
|
|
if action == "enable" {
|
|
return hs.AdminEnableUser(c)
|
|
}
|
|
|
|
return hs.AdminDisableUser(c)
|
|
})
|
|
|
|
sc.m.Post(routePattern, sc.defaultHandler)
|
|
|
|
fn(sc)
|
|
})
|
|
}
|
|
|
|
func adminDeleteUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc) {
|
|
hs := HTTPServer{
|
|
SQLStore: mockstore.NewSQLStoreMock(),
|
|
userService: usertest.NewUserServiceFake(),
|
|
}
|
|
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
|
sc := setupScenarioContext(t, url)
|
|
sc.sqlStore = hs.SQLStore
|
|
sc.authInfoService = &logintest.AuthInfoServiceFake{}
|
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
|
sc.context = c
|
|
sc.context.UserID = testUserID
|
|
|
|
return hs.AdminDeleteUser(c)
|
|
})
|
|
sc.userService = hs.userService
|
|
|
|
sc.m.Delete(routePattern, sc.defaultHandler)
|
|
|
|
fn(sc)
|
|
})
|
|
}
|
|
|
|
func adminCreateUserScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.AdminCreateUserForm, fn scenarioFunc) {
|
|
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
|
hs := HTTPServer{
|
|
Login: loginservice.LoginServiceMock{
|
|
ExpectedUserForm: cmd,
|
|
NoExistingOrgId: nonExistingOrgID,
|
|
AlreadyExitingLogin: existingTestLogin,
|
|
GeneratedUserId: testUserID,
|
|
},
|
|
}
|
|
|
|
sc := setupScenarioContext(t, url)
|
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
|
c.Req.Body = mockRequestBody(cmd)
|
|
c.Req.Header.Add("Content-Type", "application/json")
|
|
sc.context = c
|
|
sc.context.UserID = testUserID
|
|
|
|
return hs.AdminCreateUser(c)
|
|
})
|
|
|
|
sc.m.Post(routePattern, sc.defaultHandler)
|
|
|
|
fn(sc)
|
|
})
|
|
}
|