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:
Émile Fugulin 2020-04-15 05:11:45 -04:00 committed by GitHub
parent da6056d5e1
commit d721dd13cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 187 additions and 1 deletions

View File

@ -224,10 +224,13 @@ Content-Type: application/json
"name":"User",
"email":"user@graf.com",
"login":"user",
"password":"userpassword"
"password":"userpassword",
"OrgId": 1
}
```
Note that `OrgId` is an optional parameter that can be used to assign a new user to a different organization when [auto_assign_org](https://grafana.com/docs/grafana/latest/installation/configuration/#auto-assign-org) is set to `true`.
**Example Response**:
```http

View File

@ -14,6 +14,7 @@ func AdminCreateUser(c *models.ReqContext, form dtos.AdminCreateUserForm) {
Email: form.Email,
Password: form.Password,
Name: form.Name,
OrgId: form.OrgId,
}
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 == models.ErrOrgNotFound {
c.JsonApiErr(400, models.ErrOrgNotFound.Error(), nil)
return
}
c.JsonApiErr(500, "failed to create user", err)
return
}

View File

@ -12,6 +12,12 @@ import (
. "github.com/smartystreets/goconvey/convey"
)
const (
TestLogin = "test@example.com"
TestPassword = "password"
nonExistingOrgID = 1000
)
func TestAdminApiEndpoint(t *testing.T) {
role := models.ROLE_ADMIN
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)
})
})
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) {
@ -324,3 +409,21 @@ func adminDeleteUserScenario(desc string, url string, routePattern string, 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)
})
}

View File

@ -18,6 +18,7 @@ type AdminCreateUserForm struct {
Login string `json:"login"`
Name string `json:"name"`
Password string `json:"password" binding:"Required"`
OrgId int64 `json:"orgId"`
}
type AdminUpdateUserForm struct {

View File

@ -58,6 +58,7 @@ type CreateUserCommand struct {
Login string
Name string
Company string
OrgId int64
OrgName string
Password string
EmailVerified bool

View File

@ -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) {
var org models.Org

View File

@ -41,6 +41,14 @@ func getOrgIdForNewUser(sess *DBSession, cmd *models.CreateUserCommand) (int64,
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
if len(orgName) == 0 {
orgName = util.StringsFallback2(cmd.Email, cmd.Login)

View File

@ -6,6 +6,8 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
"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() {
users := createFiveTestUsers(func(i int) *models.CreateUserCommand {
return &models.CreateUserCommand{