mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Allow API to assign new user to a specific organization (#21775)
* Allow API to assign new user to a specific organization * Add defer block to test * Add API tests and return 400 instead of 500 for bad orgId * Minor test improvements
This commit is contained in:
@@ -224,10 +224,13 @@ Content-Type: application/json
|
|||||||
**Example Response**:
|
**Example Response**:
|
||||||
|
|
||||||
```http
|
```http
|
||||||
|
HTTP/1.1 200
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Logout User
|
||||||
|
|
||||||
`POST /api/admin/users/:id/logout`
|
`POST /api/admin/users/:id/logout`
|
||||||
|
|
||||||
Logout user revokes all auth tokens (devices) for the user. User of issued auth tokens (devices) will no longer be logged in
|
Logout user revokes all auth tokens (devices) for the user. User of issued auth tokens (devices) will no longer be logged in
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ func AdminCreateUser(c *models.ReqContext, form dtos.AdminCreateUserForm) {
|
|||||||
Email: form.Email,
|
Email: form.Email,
|
||||||
Password: form.Password,
|
Password: form.Password,
|
||||||
Name: form.Name,
|
Name: form.Name,
|
||||||
|
OrgId: form.OrgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cmd.Login) == 0 {
|
if len(cmd.Login) == 0 {
|
||||||
@@ -30,6 +31,11 @@ func AdminCreateUser(c *models.ReqContext, form dtos.AdminCreateUserForm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(&cmd); err != nil {
|
if err := bus.Dispatch(&cmd); err != nil {
|
||||||
|
if err == models.ErrOrgNotFound {
|
||||||
|
c.JsonApiErr(400, models.ErrOrgNotFound.Error(), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JsonApiErr(500, "failed to create user", err)
|
c.JsonApiErr(500, "failed to create user", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ import (
|
|||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TestLogin = "test@example.com"
|
||||||
|
TestPassword = "password"
|
||||||
|
nonExistingOrgID = 1000
|
||||||
|
)
|
||||||
|
|
||||||
func TestAdminApiEndpoint(t *testing.T) {
|
func TestAdminApiEndpoint(t *testing.T) {
|
||||||
role := models.ROLE_ADMIN
|
role := models.ROLE_ADMIN
|
||||||
Convey("Given a server admin attempts to remove themself as an admin", t, func() {
|
Convey("Given a server admin attempts to remove themself as an admin", t, func() {
|
||||||
@@ -175,6 +181,85 @@ func TestAdminApiEndpoint(t *testing.T) {
|
|||||||
So(userId, ShouldEqual, 42)
|
So(userId, ShouldEqual, 42)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("When a server admin attempts to create a user", t, func() {
|
||||||
|
var userLogin string
|
||||||
|
var orgId int64
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(cmd *models.CreateUserCommand) error {
|
||||||
|
userLogin = cmd.Login
|
||||||
|
orgId = cmd.OrgId
|
||||||
|
|
||||||
|
if orgId == nonExistingOrgID {
|
||||||
|
return models.ErrOrgNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Result = models.User{Id: TestUserID}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Without an organization", func() {
|
||||||
|
createCmd := dtos.AdminCreateUserForm{
|
||||||
|
Login: TestLogin,
|
||||||
|
Password: TestPassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
adminCreateUserScenario("Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
|
||||||
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||||
|
So(sc.resp.Code, ShouldEqual, 200)
|
||||||
|
|
||||||
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(respJSON.Get("id").MustInt64(), ShouldEqual, TestUserID)
|
||||||
|
So(respJSON.Get("message").MustString(), ShouldEqual, "User created")
|
||||||
|
|
||||||
|
// test that userLogin and orgId were transmitted correctly to the handler
|
||||||
|
So(userLogin, ShouldEqual, TestLogin)
|
||||||
|
So(orgId, ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("With an organization", func() {
|
||||||
|
createCmd := dtos.AdminCreateUserForm{
|
||||||
|
Login: TestLogin,
|
||||||
|
Password: TestPassword,
|
||||||
|
OrgId: TestOrgID,
|
||||||
|
}
|
||||||
|
|
||||||
|
adminCreateUserScenario("Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
|
||||||
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||||
|
So(sc.resp.Code, ShouldEqual, 200)
|
||||||
|
|
||||||
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(respJSON.Get("id").MustInt64(), ShouldEqual, TestUserID)
|
||||||
|
So(respJSON.Get("message").MustString(), ShouldEqual, "User created")
|
||||||
|
|
||||||
|
So(userLogin, ShouldEqual, TestLogin)
|
||||||
|
So(orgId, ShouldEqual, TestOrgID)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("With a nonexistent organization", func() {
|
||||||
|
createCmd := dtos.AdminCreateUserForm{
|
||||||
|
Login: TestLogin,
|
||||||
|
Password: TestPassword,
|
||||||
|
OrgId: nonExistingOrgID,
|
||||||
|
}
|
||||||
|
|
||||||
|
adminCreateUserScenario("Should create the user", "/api/admin/users", "/api/admin/users", createCmd, func(sc *scenarioContext) {
|
||||||
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||||
|
So(sc.resp.Code, ShouldEqual, 400)
|
||||||
|
|
||||||
|
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(respJSON.Get("message").MustString(), ShouldEqual, "Organization not found")
|
||||||
|
|
||||||
|
So(userLogin, ShouldEqual, TestLogin)
|
||||||
|
So(orgId, ShouldEqual, 1000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func putAdminScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc) {
|
func putAdminScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.AdminUpdateUserPermissionsForm, fn scenarioFunc) {
|
||||||
@@ -324,3 +409,21 @@ func adminDeleteUserScenario(desc string, url string, routePattern string, fn sc
|
|||||||
fn(sc)
|
fn(sc)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func adminCreateUserScenario(desc string, url string, routePattern string, cmd dtos.AdminCreateUserForm, fn scenarioFunc) {
|
||||||
|
Convey(desc+" "+url, func() {
|
||||||
|
defer bus.ClearBusHandlers()
|
||||||
|
|
||||||
|
sc := setupScenarioContext(url)
|
||||||
|
sc.defaultHandler = Wrap(func(c *models.ReqContext) {
|
||||||
|
sc.context = c
|
||||||
|
sc.context.UserId = TestUserID
|
||||||
|
|
||||||
|
AdminCreateUser(c, cmd)
|
||||||
|
})
|
||||||
|
|
||||||
|
sc.m.Post(routePattern, sc.defaultHandler)
|
||||||
|
|
||||||
|
fn(sc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type AdminCreateUserForm struct {
|
|||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Password string `json:"password" binding:"Required"`
|
Password string `json:"password" binding:"Required"`
|
||||||
|
OrgId int64 `json:"orgId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminUpdateUserForm struct {
|
type AdminUpdateUserForm struct {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ type CreateUserCommand struct {
|
|||||||
Login string
|
Login string
|
||||||
Name string
|
Name string
|
||||||
Company string
|
Company string
|
||||||
|
OrgId int64
|
||||||
OrgName string
|
OrgName string
|
||||||
Password string
|
Password string
|
||||||
EmailVerified bool
|
EmailVerified bool
|
||||||
|
|||||||
@@ -220,6 +220,18 @@ func DeleteOrg(cmd *models.DeleteOrgCommand) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyExistingOrg(sess *DBSession, orgId int64) error {
|
||||||
|
var org models.Org
|
||||||
|
has, err := sess.Where("id=?", orgId).Get(&org)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return models.ErrOrgNotFound
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func getOrCreateOrg(sess *DBSession, orgName string) (int64, error) {
|
func getOrCreateOrg(sess *DBSession, orgName string) (int64, error) {
|
||||||
var org models.Org
|
var org models.Org
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ func getOrgIdForNewUser(sess *DBSession, cmd *models.CreateUserCommand) (int64,
|
|||||||
return -1, nil
|
return -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if setting.AutoAssignOrg && cmd.OrgId != 0 {
|
||||||
|
err := verifyExistingOrg(sess, cmd.OrgId)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return cmd.OrgId, nil
|
||||||
|
}
|
||||||
|
|
||||||
orgName := cmd.OrgName
|
orgName := cmd.OrgName
|
||||||
if len(orgName) == 0 {
|
if len(orgName) == 0 {
|
||||||
orgName = util.StringsFallback2(cmd.Email, cmd.Login)
|
orgName = util.StringsFallback2(cmd.Email, cmd.Login)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@@ -63,6 +65,56 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Given an organization", func() {
|
||||||
|
autoAssignOrg := setting.AutoAssignOrg
|
||||||
|
setting.AutoAssignOrg = true
|
||||||
|
defer func() {
|
||||||
|
setting.AutoAssignOrg = autoAssignOrg
|
||||||
|
}()
|
||||||
|
|
||||||
|
orgCmd := &models.CreateOrgCommand{Name: "Some Test Org"}
|
||||||
|
err := CreateOrg(orgCmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Creates user assigned to other organization", func() {
|
||||||
|
cmd := &models.CreateUserCommand{
|
||||||
|
Email: "usertest@test.com",
|
||||||
|
Name: "user name",
|
||||||
|
Login: "user_test_login",
|
||||||
|
OrgId: orgCmd.Result.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := CreateUser(context.Background(), cmd)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Loading a user", func() {
|
||||||
|
query := models.GetUserByIdQuery{Id: cmd.Result.Id}
|
||||||
|
err := GetUserById(&query)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
So(query.Result.Email, ShouldEqual, "usertest@test.com")
|
||||||
|
So(query.Result.Password, ShouldEqual, "")
|
||||||
|
So(query.Result.Rands, ShouldHaveLength, 10)
|
||||||
|
So(query.Result.Salt, ShouldHaveLength, 10)
|
||||||
|
So(query.Result.IsDisabled, ShouldBeFalse)
|
||||||
|
So(query.Result.OrgId, ShouldEqual, orgCmd.Result.Id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Don't create user assigned to unknown organization", func() {
|
||||||
|
const nonExistingOrgID = 10000
|
||||||
|
cmd := &models.CreateUserCommand{
|
||||||
|
Email: "usertest@test.com",
|
||||||
|
Name: "user name",
|
||||||
|
Login: "user_test_login",
|
||||||
|
OrgId: nonExistingOrgID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := CreateUser(context.Background(), cmd)
|
||||||
|
So(err, ShouldEqual, models.ErrOrgNotFound)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Convey("Given 5 users", func() {
|
Convey("Given 5 users", func() {
|
||||||
users := createFiveTestUsers(func(i int) *models.CreateUserCommand {
|
users := createFiveTestUsers(func(i int) *models.CreateUserCommand {
|
||||||
return &models.CreateUserCommand{
|
return &models.CreateUserCommand{
|
||||||
|
|||||||
Reference in New Issue
Block a user