diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index 23cfc826ed2..a293032c200 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -9,16 +9,6 @@ import ( "github.com/grafana/grafana/pkg/util" ) -func AdminSearchUsers(c *middleware.Context) { - query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000} - if err := bus.Dispatch(&query); err != nil { - c.JsonApiErr(500, "Failed to fetch users", err) - return - } - - c.JSON(200, query.Result) -} - func AdminCreateUser(c *middleware.Context, form dtos.AdminCreateUserForm) { cmd := m.CreateUserCommand{ Login: form.Login, diff --git a/pkg/api/api.go b/pkg/api/api.go index 1c63ad3a541..2b0ad858f46 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -53,7 +53,8 @@ func Register(r *macaron.Macaron) { // authed api r.Group("/api", func() { - // user + + // user (signed in) r.Group("/user", func() { r.Get("/", wrap(GetSignedInUser)) r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser)) @@ -64,8 +65,9 @@ func Register(r *macaron.Macaron) { r.Put("/password", bind(m.ChangeUserPasswordCommand{}), ChangeUserPassword) }) - // users + // users (admin permission required) r.Group("/users", func() { + r.Get("/", wrap(SearchUsers)) r.Get("/:id", wrap(GetUserById)) r.Get("/:id/orgs", wrap(GetUserOrgList)) r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser)) @@ -84,6 +86,9 @@ func Register(r *macaron.Macaron) { // create new org r.Post("/orgs", bind(m.CreateOrgCommand{}), wrap(CreateOrg)) + // search all orgs + r.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs)) + // orgs (admin routes) r.Group("/orgs/:orgId", func() { r.Put("/", bind(m.UpdateOrgCommand{}), wrap(UpdateOrg)) @@ -133,7 +138,6 @@ func Register(r *macaron.Macaron) { // admin api r.Group("/api/admin", func() { r.Get("/settings", AdminGetSettings) - r.Get("/users", AdminSearchUsers) r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser) r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword) r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions) diff --git a/pkg/api/org.go b/pkg/api/org.go index 5b9e1a9aef8..75499f33f61 100644 --- a/pkg/api/org.go +++ b/pkg/api/org.go @@ -72,3 +72,18 @@ func updateOrgHelper(cmd m.UpdateOrgCommand) Response { return ApiSuccess("Organization updated") } + +func SearchOrgs(c *middleware.Context) Response { + query := m.SearchOrgsQuery{ + Query: c.Query("query"), + Name: c.Query("name"), + Page: 0, + Limit: 1000, + } + + if err := bus.Dispatch(&query); err != nil { + return ApiError(500, "Failed to search orgs", err) + } + + return Json(200, query.Result) +} diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go index c88df600450..03570619e6b 100644 --- a/pkg/api/org_users.go +++ b/pkg/api/org_users.go @@ -84,6 +84,9 @@ func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response { } if err := bus.Dispatch(&cmd); err != nil { + if err == m.ErrLastOrgAdmin { + return ApiError(400, "Cannot change role so that there is no organization admin left", nil) + } return ApiError(500, "Failed update org user", err) } diff --git a/pkg/api/user.go b/pkg/api/user.go index 1b5654ce1f0..4ce19d1f7ba 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -142,3 +142,13 @@ func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand) c.JsonOK("User password changed") } + +// GET /api/users +func SearchUsers(c *middleware.Context) Response { + query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000} + if err := bus.Dispatch(&query); err != nil { + return ApiError(500, "Failed to fetch users", err) + } + + return Json(200, query.Result) +} diff --git a/pkg/models/org.go b/pkg/models/org.go index a8ee08ea69b..b2d18be9537 100644 --- a/pkg/models/org.go +++ b/pkg/models/org.go @@ -48,8 +48,13 @@ type GetOrgByNameQuery struct { Result *Org } -type GetOrgListQuery struct { - Result []*Org +type SearchOrgsQuery struct { + Query string + Name string + Limit int + Page int + + Result []*OrgDTO } type OrgDTO struct { diff --git a/pkg/services/sqlstore/org.go b/pkg/services/sqlstore/org.go index 18284feccac..725a21d7fad 100644 --- a/pkg/services/sqlstore/org.go +++ b/pkg/services/sqlstore/org.go @@ -14,12 +14,23 @@ func init() { bus.AddHandler("sql", CreateOrg) bus.AddHandler("sql", UpdateOrg) bus.AddHandler("sql", GetOrgByName) - bus.AddHandler("sql", GetOrgList) + bus.AddHandler("sql", SearchOrgs) bus.AddHandler("sql", DeleteOrg) } -func GetOrgList(query *m.GetOrgListQuery) error { - return x.Find(&query.Result) +func SearchOrgs(query *m.SearchOrgsQuery) error { + query.Result = make([]*m.OrgDTO, 0) + sess := x.Table("org") + if query.Query != "" { + sess.Where("name LIKE ?", query.Query+"%") + } + if query.Name != "" { + sess.Where("name=?", query.Name) + } + sess.Limit(query.Limit, query.Limit*query.Page) + sess.Cols("id", "name") + err := sess.Find(&query.Result) + return err } func GetOrgById(query *m.GetOrgByIdQuery) error { diff --git a/pkg/services/sqlstore/org_test.go b/pkg/services/sqlstore/org_test.go index 1b230e349ff..f52175c2e5c 100644 --- a/pkg/services/sqlstore/org_test.go +++ b/pkg/services/sqlstore/org_test.go @@ -142,11 +142,18 @@ func TestAccountDataAccess(t *testing.T) { }) }) - Convey("Cannot delete last admin account user", func() { + Convey("Cannot delete last admin org user", func() { cmd := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac1.Id} err := RemoveOrgUser(&cmd) So(err, ShouldEqual, m.ErrLastOrgAdmin) }) + + Convey("Cannot update role so no one is admin user", func() { + cmd := m.UpdateOrgUserCommand{OrgId: ac1.OrgId, UserId: ac1.Id, Role: m.ROLE_VIEWER} + err := UpdateOrgUser(&cmd) + So(err, ShouldEqual, m.ErrLastOrgAdmin) + }) + }) }) }) diff --git a/pkg/services/sqlstore/org_users.go b/pkg/services/sqlstore/org_users.go index 3502cd97d60..2e8fc40cb7c 100644 --- a/pkg/services/sqlstore/org_users.go +++ b/pkg/services/sqlstore/org_users.go @@ -48,7 +48,11 @@ func UpdateOrgUser(cmd *m.UpdateOrgUserCommand) error { orgUser.Role = cmd.Role orgUser.Updated = time.Now() _, err = sess.Id(orgUser.Id).Update(&orgUser) - return err + if err != nil { + return err + } + + return validateOneAdminLeftInOrg(cmd.OrgId, sess) }) } @@ -72,16 +76,20 @@ func RemoveOrgUser(cmd *m.RemoveOrgUserCommand) error { return err } - // validate that there is an admin user left - res, err := sess.Query("SELECT 1 from org_user WHERE org_id=? and role='Admin'", cmd.OrgId) - if err != nil { - return err - } - - if len(res) == 0 { - return m.ErrLastOrgAdmin - } - - return err + return validateOneAdminLeftInOrg(cmd.OrgId, sess) }) } + +func validateOneAdminLeftInOrg(orgId int64, sess *xorm.Session) error { + // validate that there is an admin user left + res, err := sess.Query("SELECT 1 from org_user WHERE org_id=? and role='Admin'", orgId) + if err != nil { + return err + } + + if len(res) == 0 { + return m.ErrLastOrgAdmin + } + + return err +} diff --git a/public/app/features/admin/adminEditUserCtrl.js b/public/app/features/admin/adminEditUserCtrl.js index dc8839f0f84..d8500a845d3 100644 --- a/public/app/features/admin/adminEditUserCtrl.js +++ b/public/app/features/admin/adminEditUserCtrl.js @@ -1,13 +1,15 @@ define([ 'angular', + 'lodash', ], -function (angular) { +function (angular, _) { 'use strict'; var module = angular.module('grafana.controllers'); module.controller('AdminEditUserCtrl', function($scope, $routeParams, backendSrv, $location) { $scope.user = {}; + $scope.newOrg = { name: '', role: 'Editor' }; $scope.permissions = {}; $scope.init = function() { @@ -64,6 +66,44 @@ function (angular) { }); }; + $scope.updateOrgUser= function(orgUser) { + backendSrv.patch('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id, orgUser).then(function() { + }); + }; + + $scope.removeOrgUser = function(orgUser) { + backendSrv.delete('/api/orgs/' + orgUser.orgId + '/users/' + $scope.user_id).then(function() { + $scope.getUserOrgs($scope.user_id); + }); + }; + + $scope.orgsSearchCache = []; + + $scope.searchOrgs = function(queryStr, callback) { + if ($scope.orgsSearchCache.length > 0) { + callback(_.pluck($scope.orgsSearchCache, "name")); + return; + } + + backendSrv.get('/api/orgs', {query: ''}).then(function(result) { + $scope.orgsSearchCache = result; + callback(_.pluck(result, "name")); + }); + }; + + $scope.addOrgUser = function() { + if (!$scope.addOrgForm.$valid) { return; } + + var orgInfo = _.findWhere($scope.orgsSearchCache, {name: $scope.newOrg.name}); + if (!orgInfo) { return; } + + $scope.newOrg.loginOrEmail = $scope.user.login; + + backendSrv.post('/api/orgs/' + orgInfo.id + '/users/', $scope.newOrg).then(function() { + $scope.getUserOrgs($scope.user_id); + }); + }; + $scope.init(); }); diff --git a/public/app/features/admin/adminUsersCtrl.js b/public/app/features/admin/adminUsersCtrl.js index c9be0238fd4..737812c5474 100644 --- a/public/app/features/admin/adminUsersCtrl.js +++ b/public/app/features/admin/adminUsersCtrl.js @@ -13,7 +13,7 @@ function (angular) { }; $scope.getUsers = function() { - backendSrv.get('/api/admin/users').then(function(users) { + backendSrv.get('/api/users').then(function(users) { $scope.users = users; }); }; diff --git a/public/app/features/admin/partials/edit_user.html b/public/app/features/admin/partials/edit_user.html index e72bd5b57c9..5d08292605e 100644 --- a/public/app/features/admin/partials/edit_user.html +++ b/public/app/features/admin/partials/edit_user.html @@ -80,24 +80,52 @@ Permissions -
| Name | @@ -109,15 +137,16 @@ {{org.name}} Current- | - + |
|---|