Browse Source

API: Restrict anonymous user information access (#18422)

Existing /api/alert-notifications now requires at least editor access.
Existing /api/alert-notifiers now requires at least editor access.
New /api/alert-notifications/lookup returns less information than
/api/alert-notifications and can be access by any authenticated user.
Existing /api/org/users now requires org admin role.
New /api/org/users/lookup returns less information than
/api/org/users and can be access by users that are org admins,
admin in any folder or admin of any team.
UserPicker component now uses /api/org/users/lookup instead
of /api/org/users.

Fixes #17318
Marcus Efraimsson 6 years ago
parent
commit
8fd153edb7

+ 42 - 0
docs/sources/http_api/alerting_notification_channels.md

@@ -63,6 +63,48 @@ Content-Type: application/json
 
 
 ```
 ```
 
 
+## Get all notification channels (lookup)
+
+Returns all notification channels, but with less detailed information.
+Accessible by any authenticated user and is mainly used by providing
+alert notification channels in Grafana UI when configuring alert rule.
+
+`GET /api/alert-notifications/lookup`
+
+**Example Request**:
+
+```http
+GET /api/alert-notifications/lookup HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+```
+
+**Example Response**:
+
+```http
+HTTP/1.1 200
+Content-Type: application/json
+
+[
+  {
+    "id": 1,
+    "uid": "000000001",
+    "name": "Test",
+    "type": "email",
+    "isDefault": false
+  },
+  {
+    "id": 2,
+    "uid": "000000002",
+    "name": "Slack",
+    "type": "slack",
+    "isDefault": false
+  }
+]
+
+```
+
 ## Get notification channel by uid
 ## Get notification channel by uid
 
 
 `GET /api/alert-notifications/uid/:uid`
 `GET /api/alert-notifications/uid/:uid`

+ 44 - 5
docs/sources/http_api/org.md

@@ -47,6 +47,9 @@ Content-Type: application/json
 
 
 `GET /api/org/users`
 `GET /api/org/users`
 
 
+Returns all org users within the current organization.
+Accessible to users with org admin role.
+
 **Example Request**:
 **Example Request**:
 
 
 ```http
 ```http
@@ -64,11 +67,47 @@ Content-Type: application/json
 
 
 [
 [
   {
   {
-    "orgId":1,
-    "userId":1,
-    "email":"admin@mygraf.com",
-    "login":"admin",
-    "role":"Admin"
+    "orgId": 1,
+    "userId": 1,
+    "email": "admin@localhost",
+    "avatarUrl": "/avatar/46d229b033af06a191ff2267bca9ae56",
+    "login": "admin",
+    "role": "Admin",
+    "lastSeenAt": "2019-08-09T11:02:49+02:00",
+    "lastSeenAtAge": "< 1m"
+  }
+]
+```
+
+### Get all users within the current organization (lookup)
+
+`GET /api/org/users/lookup`
+
+Returns all org users within the current organization, but with less detailed information.
+Accessible to users with org admin role, admin in any folder or admin of any team.
+Mainly used by Grafana UI for providing list of users when adding team members and
+when editing folder/dashboard permissions.
+
+**Example Request**:
+
+```http
+GET /api/org/users/lookup HTTP/1.1
+Accept: application/json
+Content-Type: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+```
+
+**Example Response**:
+
+```http
+HTTP/1.1 200
+Content-Type: application/json
+
+[
+  {
+    "userId": 1,
+    "login": "admin",
+    "avatarUrl": "/avatar/46d229b033af06a191ff2267bca9ae56"
   }
   }
 ]
 ]
 ```
 ```

+ 65 - 41
pkg/api/alerting.go

@@ -6,15 +6,15 @@ import (
 
 
 	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
-	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/alerting"
 	"github.com/grafana/grafana/pkg/services/alerting"
 	"github.com/grafana/grafana/pkg/services/guardian"
 	"github.com/grafana/grafana/pkg/services/guardian"
 	"github.com/grafana/grafana/pkg/services/search"
 	"github.com/grafana/grafana/pkg/services/search"
 )
 )
 
 
-func ValidateOrgAlert(c *m.ReqContext) {
+func ValidateOrgAlert(c *models.ReqContext) {
 	id := c.ParamsInt64(":alertId")
 	id := c.ParamsInt64(":alertId")
-	query := m.GetAlertByIdQuery{Id: id}
+	query := models.GetAlertByIdQuery{Id: id}
 
 
 	if err := bus.Dispatch(&query); err != nil {
 	if err := bus.Dispatch(&query); err != nil {
 		c.JsonApiErr(404, "Alert not found", nil)
 		c.JsonApiErr(404, "Alert not found", nil)
@@ -27,14 +27,14 @@ func ValidateOrgAlert(c *m.ReqContext) {
 	}
 	}
 }
 }
 
 
-func GetAlertStatesForDashboard(c *m.ReqContext) Response {
+func GetAlertStatesForDashboard(c *models.ReqContext) Response {
 	dashboardID := c.QueryInt64("dashboardId")
 	dashboardID := c.QueryInt64("dashboardId")
 
 
 	if dashboardID == 0 {
 	if dashboardID == 0 {
 		return Error(400, "Missing query parameter dashboardId", nil)
 		return Error(400, "Missing query parameter dashboardId", nil)
 	}
 	}
 
 
-	query := m.GetAlertStatesForDashboardQuery{
+	query := models.GetAlertStatesForDashboardQuery{
 		OrgId:       c.OrgId,
 		OrgId:       c.OrgId,
 		DashboardId: c.QueryInt64("dashboardId"),
 		DashboardId: c.QueryInt64("dashboardId"),
 	}
 	}
@@ -47,7 +47,7 @@ func GetAlertStatesForDashboard(c *m.ReqContext) Response {
 }
 }
 
 
 // GET /api/alerts
 // GET /api/alerts
-func GetAlerts(c *m.ReqContext) Response {
+func GetAlerts(c *models.ReqContext) Response {
 	dashboardQuery := c.Query("dashboardQuery")
 	dashboardQuery := c.Query("dashboardQuery")
 	dashboardTags := c.QueryStrings("dashboardTag")
 	dashboardTags := c.QueryStrings("dashboardTag")
 	stringDashboardIDs := c.QueryStrings("dashboardId")
 	stringDashboardIDs := c.QueryStrings("dashboardId")
@@ -79,7 +79,7 @@ func GetAlerts(c *m.ReqContext) Response {
 			DashboardIds: dashboardIDs,
 			DashboardIds: dashboardIDs,
 			Type:         string(search.DashHitDB),
 			Type:         string(search.DashHitDB),
 			FolderIds:    folderIDs,
 			FolderIds:    folderIDs,
-			Permission:   m.PERMISSION_VIEW,
+			Permission:   models.PERMISSION_VIEW,
 		}
 		}
 
 
 		err := bus.Dispatch(&searchQuery)
 		err := bus.Dispatch(&searchQuery)
@@ -95,11 +95,11 @@ func GetAlerts(c *m.ReqContext) Response {
 
 
 		// if we didn't find any dashboards, return empty result
 		// if we didn't find any dashboards, return empty result
 		if len(dashboardIDs) == 0 {
 		if len(dashboardIDs) == 0 {
-			return JSON(200, []*m.AlertListItemDTO{})
+			return JSON(200, []*models.AlertListItemDTO{})
 		}
 		}
 	}
 	}
 
 
-	query := m.GetAlertsQuery{
+	query := models.GetAlertsQuery{
 		OrgId:        c.OrgId,
 		OrgId:        c.OrgId,
 		DashboardIDs: dashboardIDs,
 		DashboardIDs: dashboardIDs,
 		PanelId:      c.QueryInt64("panelId"),
 		PanelId:      c.QueryInt64("panelId"),
@@ -118,14 +118,14 @@ func GetAlerts(c *m.ReqContext) Response {
 	}
 	}
 
 
 	for _, alert := range query.Result {
 	for _, alert := range query.Result {
-		alert.Url = m.GetDashboardUrl(alert.DashboardUid, alert.DashboardSlug)
+		alert.Url = models.GetDashboardUrl(alert.DashboardUid, alert.DashboardSlug)
 	}
 	}
 
 
 	return JSON(200, query.Result)
 	return JSON(200, query.Result)
 }
 }
 
 
 // POST /api/alerts/test
 // POST /api/alerts/test
-func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
+func AlertTest(c *models.ReqContext, dto dtos.AlertTestCommand) Response {
 	if _, idErr := dto.Dashboard.Get("id").Int64(); idErr != nil {
 	if _, idErr := dto.Dashboard.Get("id").Int64(); idErr != nil {
 		return Error(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil)
 		return Error(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil)
 	}
 	}
@@ -141,7 +141,7 @@ func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
 		if validationErr, ok := err.(alerting.ValidationError); ok {
 		if validationErr, ok := err.(alerting.ValidationError); ok {
 			return Error(422, validationErr.Error(), nil)
 			return Error(422, validationErr.Error(), nil)
 		}
 		}
-		if err == m.ErrDataSourceAccessDenied {
+		if err == models.ErrDataSourceAccessDenied {
 			return Error(403, "Access denied to datasource", err)
 			return Error(403, "Access denied to datasource", err)
 		}
 		}
 		return Error(500, "Failed to test rule", err)
 		return Error(500, "Failed to test rule", err)
@@ -171,9 +171,9 @@ func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
 }
 }
 
 
 // GET /api/alerts/:id
 // GET /api/alerts/:id
-func GetAlert(c *m.ReqContext) Response {
+func GetAlert(c *models.ReqContext) Response {
 	id := c.ParamsInt64(":alertId")
 	id := c.ParamsInt64(":alertId")
-	query := m.GetAlertByIdQuery{Id: id}
+	query := models.GetAlertByIdQuery{Id: id}
 
 
 	if err := bus.Dispatch(&query); err != nil {
 	if err := bus.Dispatch(&query); err != nil {
 		return Error(500, "List alerts failed", err)
 		return Error(500, "List alerts failed", err)
@@ -182,28 +182,52 @@ func GetAlert(c *m.ReqContext) Response {
 	return JSON(200, &query.Result)
 	return JSON(200, &query.Result)
 }
 }
 
 
-func GetAlertNotifiers(c *m.ReqContext) Response {
+func GetAlertNotifiers(c *models.ReqContext) Response {
 	return JSON(200, alerting.GetNotifiers())
 	return JSON(200, alerting.GetNotifiers())
 }
 }
 
 
-func GetAlertNotifications(c *m.ReqContext) Response {
-	query := &m.GetAllAlertNotificationsQuery{OrgId: c.OrgId}
+func GetAlertNotificationLookup(c *models.ReqContext) Response {
+	alertNotifications, err := getAlertNotificationsInternal(c)
+	if err != nil {
+		return Error(500, "Failed to get alert notifications", err)
+	}
 
 
-	if err := bus.Dispatch(query); err != nil {
+	result := make([]*dtos.AlertNotificationLookup, 0)
+
+	for _, notification := range alertNotifications {
+		result = append(result, dtos.NewAlertNotificationLookup(notification))
+	}
+
+	return JSON(200, result)
+}
+
+func GetAlertNotifications(c *models.ReqContext) Response {
+	alertNotifications, err := getAlertNotificationsInternal(c)
+	if err != nil {
 		return Error(500, "Failed to get alert notifications", err)
 		return Error(500, "Failed to get alert notifications", err)
 	}
 	}
 
 
 	result := make([]*dtos.AlertNotification, 0)
 	result := make([]*dtos.AlertNotification, 0)
 
 
-	for _, notification := range query.Result {
+	for _, notification := range alertNotifications {
 		result = append(result, dtos.NewAlertNotification(notification))
 		result = append(result, dtos.NewAlertNotification(notification))
 	}
 	}
 
 
 	return JSON(200, result)
 	return JSON(200, result)
 }
 }
 
 
-func GetAlertNotificationByID(c *m.ReqContext) Response {
-	query := &m.GetAlertNotificationsQuery{
+func getAlertNotificationsInternal(c *models.ReqContext) ([]*models.AlertNotification, error) {
+	query := &models.GetAllAlertNotificationsQuery{OrgId: c.OrgId}
+
+	if err := bus.Dispatch(query); err != nil {
+		return nil, err
+	}
+
+	return query.Result, nil
+}
+
+func GetAlertNotificationByID(c *models.ReqContext) Response {
+	query := &models.GetAlertNotificationsQuery{
 		OrgId: c.OrgId,
 		OrgId: c.OrgId,
 		Id:    c.ParamsInt64("notificationId"),
 		Id:    c.ParamsInt64("notificationId"),
 	}
 	}
@@ -223,8 +247,8 @@ func GetAlertNotificationByID(c *m.ReqContext) Response {
 	return JSON(200, dtos.NewAlertNotification(query.Result))
 	return JSON(200, dtos.NewAlertNotification(query.Result))
 }
 }
 
 
-func GetAlertNotificationByUID(c *m.ReqContext) Response {
-	query := &m.GetAlertNotificationsWithUidQuery{
+func GetAlertNotificationByUID(c *models.ReqContext) Response {
+	query := &models.GetAlertNotificationsWithUidQuery{
 		OrgId: c.OrgId,
 		OrgId: c.OrgId,
 		Uid:   c.Params("uid"),
 		Uid:   c.Params("uid"),
 	}
 	}
@@ -244,7 +268,7 @@ func GetAlertNotificationByUID(c *m.ReqContext) Response {
 	return JSON(200, dtos.NewAlertNotification(query.Result))
 	return JSON(200, dtos.NewAlertNotification(query.Result))
 }
 }
 
 
-func CreateAlertNotification(c *m.ReqContext, cmd m.CreateAlertNotificationCommand) Response {
+func CreateAlertNotification(c *models.ReqContext, cmd models.CreateAlertNotificationCommand) Response {
 	cmd.OrgId = c.OrgId
 	cmd.OrgId = c.OrgId
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
@@ -254,7 +278,7 @@ func CreateAlertNotification(c *m.ReqContext, cmd m.CreateAlertNotificationComma
 	return JSON(200, dtos.NewAlertNotification(cmd.Result))
 	return JSON(200, dtos.NewAlertNotification(cmd.Result))
 }
 }
 
 
-func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationCommand) Response {
+func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) Response {
 	cmd.OrgId = c.OrgId
 	cmd.OrgId = c.OrgId
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
@@ -268,7 +292,7 @@ func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationComma
 	return JSON(200, dtos.NewAlertNotification(cmd.Result))
 	return JSON(200, dtos.NewAlertNotification(cmd.Result))
 }
 }
 
 
-func UpdateAlertNotificationByUID(c *m.ReqContext, cmd m.UpdateAlertNotificationWithUidCommand) Response {
+func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) Response {
 	cmd.OrgId = c.OrgId
 	cmd.OrgId = c.OrgId
 	cmd.Uid = c.Params("uid")
 	cmd.Uid = c.Params("uid")
 
 
@@ -283,8 +307,8 @@ func UpdateAlertNotificationByUID(c *m.ReqContext, cmd m.UpdateAlertNotification
 	return JSON(200, dtos.NewAlertNotification(cmd.Result))
 	return JSON(200, dtos.NewAlertNotification(cmd.Result))
 }
 }
 
 
-func DeleteAlertNotification(c *m.ReqContext) Response {
-	cmd := m.DeleteAlertNotificationCommand{
+func DeleteAlertNotification(c *models.ReqContext) Response {
+	cmd := models.DeleteAlertNotificationCommand{
 		OrgId: c.OrgId,
 		OrgId: c.OrgId,
 		Id:    c.ParamsInt64("notificationId"),
 		Id:    c.ParamsInt64("notificationId"),
 	}
 	}
@@ -296,8 +320,8 @@ func DeleteAlertNotification(c *m.ReqContext) Response {
 	return Success("Notification deleted")
 	return Success("Notification deleted")
 }
 }
 
 
-func DeleteAlertNotificationByUID(c *m.ReqContext) Response {
-	cmd := m.DeleteAlertNotificationWithUidCommand{
+func DeleteAlertNotificationByUID(c *models.ReqContext) Response {
+	cmd := models.DeleteAlertNotificationWithUidCommand{
 		OrgId: c.OrgId,
 		OrgId: c.OrgId,
 		Uid:   c.Params("uid"),
 		Uid:   c.Params("uid"),
 	}
 	}
@@ -310,7 +334,7 @@ func DeleteAlertNotificationByUID(c *m.ReqContext) Response {
 }
 }
 
 
 //POST /api/alert-notifications/test
 //POST /api/alert-notifications/test
-func NotificationTest(c *m.ReqContext, dto dtos.NotificationTestCommand) Response {
+func NotificationTest(c *models.ReqContext, dto dtos.NotificationTestCommand) Response {
 	cmd := &alerting.NotificationTestCommand{
 	cmd := &alerting.NotificationTestCommand{
 		Name:     dto.Name,
 		Name:     dto.Name,
 		Type:     dto.Type,
 		Type:     dto.Type,
@@ -318,7 +342,7 @@ func NotificationTest(c *m.ReqContext, dto dtos.NotificationTestCommand) Respons
 	}
 	}
 
 
 	if err := bus.Dispatch(cmd); err != nil {
 	if err := bus.Dispatch(cmd); err != nil {
-		if err == m.ErrSmtpNotEnabled {
+		if err == models.ErrSmtpNotEnabled {
 			return Error(412, err.Error(), err)
 			return Error(412, err.Error(), err)
 		}
 		}
 		return Error(500, "Failed to send alert notifications", err)
 		return Error(500, "Failed to send alert notifications", err)
@@ -328,10 +352,10 @@ func NotificationTest(c *m.ReqContext, dto dtos.NotificationTestCommand) Respons
 }
 }
 
 
 //POST /api/alerts/:alertId/pause
 //POST /api/alerts/:alertId/pause
-func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
+func PauseAlert(c *models.ReqContext, dto dtos.PauseAlertCommand) Response {
 	alertID := c.ParamsInt64("alertId")
 	alertID := c.ParamsInt64("alertId")
 
 
-	query := m.GetAlertByIdQuery{Id: alertID}
+	query := models.GetAlertByIdQuery{Id: alertID}
 
 
 	if err := bus.Dispatch(&query); err != nil {
 	if err := bus.Dispatch(&query); err != nil {
 		return Error(500, "Get Alert failed", err)
 		return Error(500, "Get Alert failed", err)
@@ -346,7 +370,7 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
 		return Error(403, "Access denied to this dashboard and alert", nil)
 		return Error(403, "Access denied to this dashboard and alert", nil)
 	}
 	}
 
 
-	cmd := m.PauseAlertCommand{
+	cmd := models.PauseAlertCommand{
 		OrgId:    c.OrgId,
 		OrgId:    c.OrgId,
 		AlertIds: []int64{alertID},
 		AlertIds: []int64{alertID},
 		Paused:   dto.Paused,
 		Paused:   dto.Paused,
@@ -356,10 +380,10 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
 		return Error(500, "", err)
 		return Error(500, "", err)
 	}
 	}
 
 
-	var response m.AlertStateType = m.AlertStateUnknown
+	var response models.AlertStateType = models.AlertStateUnknown
 	pausedState := "un-paused"
 	pausedState := "un-paused"
 	if cmd.Paused {
 	if cmd.Paused {
-		response = m.AlertStatePaused
+		response = models.AlertStatePaused
 		pausedState = "paused"
 		pausedState = "paused"
 	}
 	}
 
 
@@ -373,8 +397,8 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
 }
 }
 
 
 //POST /api/admin/pause-all-alerts
 //POST /api/admin/pause-all-alerts
-func PauseAllAlerts(c *m.ReqContext, dto dtos.PauseAllAlertsCommand) Response {
-	updateCmd := m.PauseAllAlertCommand{
+func PauseAllAlerts(c *models.ReqContext, dto dtos.PauseAllAlertsCommand) Response {
+	updateCmd := models.PauseAllAlertCommand{
 		Paused: dto.Paused,
 		Paused: dto.Paused,
 	}
 	}
 
 
@@ -382,10 +406,10 @@ func PauseAllAlerts(c *m.ReqContext, dto dtos.PauseAllAlertsCommand) Response {
 		return Error(500, "Failed to pause alerts", err)
 		return Error(500, "Failed to pause alerts", err)
 	}
 	}
 
 
-	var response m.AlertStateType = m.AlertStatePending
+	var response models.AlertStateType = models.AlertStatePending
 	pausedState := "un paused"
 	pausedState := "un paused"
 	if updateCmd.Paused {
 	if updateCmd.Paused {
-		response = m.AlertStatePaused
+		response = models.AlertStatePaused
 		pausedState = "paused"
 		pausedState = "paused"
 	}
 	}
 
 

+ 24 - 24
pkg/api/alerting_test.go

@@ -5,7 +5,7 @@ import (
 
 
 	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
-	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/search"
 	"github.com/grafana/grafana/pkg/services/search"
 
 
 	. "github.com/smartystreets/goconvey/convey"
 	. "github.com/smartystreets/goconvey/convey"
@@ -14,24 +14,24 @@ import (
 func TestAlertingApiEndpoint(t *testing.T) {
 func TestAlertingApiEndpoint(t *testing.T) {
 	Convey("Given an alert in a dashboard with an acl", t, func() {
 	Convey("Given an alert in a dashboard with an acl", t, func() {
 
 
-		singleAlert := &m.Alert{Id: 1, DashboardId: 1, Name: "singlealert"}
+		singleAlert := &models.Alert{Id: 1, DashboardId: 1, Name: "singlealert"}
 
 
-		bus.AddHandler("test", func(query *m.GetAlertByIdQuery) error {
+		bus.AddHandler("test", func(query *models.GetAlertByIdQuery) error {
 			query.Result = singleAlert
 			query.Result = singleAlert
 			return nil
 			return nil
 		})
 		})
 
 
-		viewerRole := m.ROLE_VIEWER
-		editorRole := m.ROLE_EDITOR
+		viewerRole := models.ROLE_VIEWER
+		editorRole := models.ROLE_EDITOR
 
 
-		aclMockResp := []*m.DashboardAclInfoDTO{}
-		bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
+		aclMockResp := []*models.DashboardAclInfoDTO{}
+		bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
 			query.Result = aclMockResp
 			query.Result = aclMockResp
 			return nil
 			return nil
 		})
 		})
 
 
-		bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
-			query.Result = []*m.TeamDTO{}
+		bus.AddHandler("test", func(query *models.GetTeamsByUserQuery) error {
+			query.Result = []*models.TeamDTO{}
 			return nil
 			return nil
 		})
 		})
 
 
@@ -41,7 +41,7 @@ func TestAlertingApiEndpoint(t *testing.T) {
 					AlertId: 1,
 					AlertId: 1,
 					Paused:  true,
 					Paused:  true,
 				}
 				}
-				postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
+				postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", models.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
 					CallPauseAlert(sc)
 					CallPauseAlert(sc)
 					So(sc.resp.Code, ShouldEqual, 403)
 					So(sc.resp.Code, ShouldEqual, 403)
 				})
 				})
@@ -49,9 +49,9 @@ func TestAlertingApiEndpoint(t *testing.T) {
 		})
 		})
 
 
 		Convey("When user is editor and dashboard has default ACL", func() {
 		Convey("When user is editor and dashboard has default ACL", func() {
-			aclMockResp = []*m.DashboardAclInfoDTO{
-				{Role: &viewerRole, Permission: m.PERMISSION_VIEW},
-				{Role: &editorRole, Permission: m.PERMISSION_EDIT},
+			aclMockResp = []*models.DashboardAclInfoDTO{
+				{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
+				{Role: &editorRole, Permission: models.PERMISSION_EDIT},
 			}
 			}
 
 
 			Convey("Should be able to pause the alert", func() {
 			Convey("Should be able to pause the alert", func() {
@@ -59,22 +59,22 @@ func TestAlertingApiEndpoint(t *testing.T) {
 					AlertId: 1,
 					AlertId: 1,
 					Paused:  true,
 					Paused:  true,
 				}
 				}
-				postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", m.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
+				postAlertScenario("When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", models.ROLE_EDITOR, cmd, func(sc *scenarioContext) {
 					CallPauseAlert(sc)
 					CallPauseAlert(sc)
 					So(sc.resp.Code, ShouldEqual, 200)
 					So(sc.resp.Code, ShouldEqual, 200)
 				})
 				})
 			})
 			})
 		})
 		})
 
 
-		loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1", "/api/alerts", m.ROLE_EDITOR, func(sc *scenarioContext) {
+		loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1", "/api/alerts", models.ROLE_EDITOR, func(sc *scenarioContext) {
 			var searchQuery *search.Query
 			var searchQuery *search.Query
 			bus.AddHandler("test", func(query *search.Query) error {
 			bus.AddHandler("test", func(query *search.Query) error {
 				searchQuery = query
 				searchQuery = query
 				return nil
 				return nil
 			})
 			})
 
 
-			var getAlertsQuery *m.GetAlertsQuery
-			bus.AddHandler("test", func(query *m.GetAlertsQuery) error {
+			var getAlertsQuery *models.GetAlertsQuery
+			bus.AddHandler("test", func(query *models.GetAlertsQuery) error {
 				getAlertsQuery = query
 				getAlertsQuery = query
 				return nil
 				return nil
 			})
 			})
@@ -86,7 +86,7 @@ func TestAlertingApiEndpoint(t *testing.T) {
 			So(getAlertsQuery, ShouldNotBeNil)
 			So(getAlertsQuery, ShouldNotBeNil)
 		})
 		})
 
 
-		loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery", "/api/alerts", m.ROLE_EDITOR, func(sc *scenarioContext) {
+		loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery", "/api/alerts", models.ROLE_EDITOR, func(sc *scenarioContext) {
 			var searchQuery *search.Query
 			var searchQuery *search.Query
 			bus.AddHandler("test", func(query *search.Query) error {
 			bus.AddHandler("test", func(query *search.Query) error {
 				searchQuery = query
 				searchQuery = query
@@ -97,8 +97,8 @@ func TestAlertingApiEndpoint(t *testing.T) {
 				return nil
 				return nil
 			})
 			})
 
 
-			var getAlertsQuery *m.GetAlertsQuery
-			bus.AddHandler("test", func(query *m.GetAlertsQuery) error {
+			var getAlertsQuery *models.GetAlertsQuery
+			bus.AddHandler("test", func(query *models.GetAlertsQuery) error {
 				getAlertsQuery = query
 				getAlertsQuery = query
 				return nil
 				return nil
 			})
 			})
@@ -120,7 +120,7 @@ func TestAlertingApiEndpoint(t *testing.T) {
 			So(getAlertsQuery.Query, ShouldEqual, "alertQuery")
 			So(getAlertsQuery.Query, ShouldEqual, "alertQuery")
 		})
 		})
 
 
-		loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alert-notifications/1", "/alert-notifications/:notificationId", m.ROLE_ADMIN, func(sc *scenarioContext) {
+		loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/alert-notifications/1", "/alert-notifications/:notificationId", models.ROLE_ADMIN, func(sc *scenarioContext) {
 			sc.handlerFunc = GetAlertNotificationByID
 			sc.handlerFunc = GetAlertNotificationByID
 			sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
 			sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
 			So(sc.resp.Code, ShouldEqual, 404)
 			So(sc.resp.Code, ShouldEqual, 404)
@@ -129,19 +129,19 @@ func TestAlertingApiEndpoint(t *testing.T) {
 }
 }
 
 
 func CallPauseAlert(sc *scenarioContext) {
 func CallPauseAlert(sc *scenarioContext) {
-	bus.AddHandler("test", func(cmd *m.PauseAlertCommand) error {
+	bus.AddHandler("test", func(cmd *models.PauseAlertCommand) error {
 		return nil
 		return nil
 	})
 	})
 
 
 	sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
 	sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
 }
 }
 
 
-func postAlertScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.PauseAlertCommand, fn scenarioFunc) {
+func postAlertScenario(desc string, url string, routePattern string, role models.RoleType, cmd dtos.PauseAlertCommand, fn scenarioFunc) {
 	Convey(desc+" "+url, func() {
 	Convey(desc+" "+url, func() {
 		defer bus.ClearBusHandlers()
 		defer bus.ClearBusHandlers()
 
 
 		sc := setupScenarioContext(url)
 		sc := setupScenarioContext(url)
-		sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
+		sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
 			sc.context = c
 			sc.context = c
 			sc.context.UserId = TestUserID
 			sc.context.UserId = TestUserID
 			sc.context.OrgId = TestOrgID
 			sc.context.OrgId = TestOrgID

+ 9 - 3
pkg/api/api.go

@@ -183,6 +183,7 @@ func (hs *HTTPServer) registerRoutes() {
 		apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
 		apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
 			orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrgCurrent))
 			orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrgCurrent))
 			orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddressCurrent))
 			orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddressCurrent))
+			orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg))
 			orgRoute.Post("/users", quota("user"), bind(models.AddOrgUserCommand{}), Wrap(AddOrgUserToCurrentOrg))
 			orgRoute.Post("/users", quota("user"), bind(models.AddOrgUserCommand{}), Wrap(AddOrgUserToCurrentOrg))
 			orgRoute.Patch("/users/:userId", bind(models.UpdateOrgUserCommand{}), Wrap(UpdateOrgUserForCurrentOrg))
 			orgRoute.Patch("/users/:userId", bind(models.UpdateOrgUserCommand{}), Wrap(UpdateOrgUserForCurrentOrg))
 			orgRoute.Delete("/users/:userId", Wrap(RemoveOrgUserForCurrentOrg))
 			orgRoute.Delete("/users/:userId", Wrap(RemoveOrgUserForCurrentOrg))
@@ -199,7 +200,7 @@ func (hs *HTTPServer) registerRoutes() {
 
 
 		// current org without requirement of user to be org admin
 		// current org without requirement of user to be org admin
 		apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
 		apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
-			orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg))
+			orgRoute.Get("/users/lookup", Wrap(GetOrgUsersForCurrentOrgLookup))
 		})
 		})
 
 
 		// create new org
 		// create new org
@@ -343,10 +344,10 @@ func (hs *HTTPServer) registerRoutes() {
 			alertsRoute.Get("/states-for-dashboard", Wrap(GetAlertStatesForDashboard))
 			alertsRoute.Get("/states-for-dashboard", Wrap(GetAlertStatesForDashboard))
 		})
 		})
 
 
-		apiRoute.Get("/alert-notifications", Wrap(GetAlertNotifications))
-		apiRoute.Get("/alert-notifiers", Wrap(GetAlertNotifiers))
+		apiRoute.Get("/alert-notifiers", reqEditorRole, Wrap(GetAlertNotifiers))
 
 
 		apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) {
 		apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) {
+			alertNotifications.Get("/", Wrap(GetAlertNotifications))
 			alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), Wrap(NotificationTest))
 			alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), Wrap(NotificationTest))
 			alertNotifications.Post("/", bind(models.CreateAlertNotificationCommand{}), Wrap(CreateAlertNotification))
 			alertNotifications.Post("/", bind(models.CreateAlertNotificationCommand{}), Wrap(CreateAlertNotification))
 			alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), Wrap(UpdateAlertNotification))
 			alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), Wrap(UpdateAlertNotification))
@@ -357,6 +358,11 @@ func (hs *HTTPServer) registerRoutes() {
 			alertNotifications.Delete("/uid/:uid", Wrap(DeleteAlertNotificationByUID))
 			alertNotifications.Delete("/uid/:uid", Wrap(DeleteAlertNotificationByUID))
 		}, reqEditorRole)
 		}, reqEditorRole)
 
 
+		// alert notifications without requirement of user to be org editor
+		apiRoute.Group("/alert-notifications", func(orgRoute routing.RouteRegister) {
+			orgRoute.Get("/lookup", Wrap(GetAlertNotificationLookup))
+		})
+
 		apiRoute.Get("/annotations", Wrap(GetAnnotations))
 		apiRoute.Get("/annotations", Wrap(GetAnnotations))
 		apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), Wrap(DeleteAnnotations))
 		apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), Wrap(DeleteAnnotations))
 
 

+ 18 - 0
pkg/api/dtos/alerting.go

@@ -77,6 +77,24 @@ type AlertNotification struct {
 	Settings              *simplejson.Json `json:"settings"`
 	Settings              *simplejson.Json `json:"settings"`
 }
 }
 
 
+func NewAlertNotificationLookup(notification *models.AlertNotification) *AlertNotificationLookup {
+	return &AlertNotificationLookup{
+		Id:        notification.Id,
+		Uid:       notification.Uid,
+		Name:      notification.Name,
+		Type:      notification.Type,
+		IsDefault: notification.IsDefault,
+	}
+}
+
+type AlertNotificationLookup struct {
+	Id        int64  `json:"id"`
+	Uid       string `json:"uid"`
+	Name      string `json:"name"`
+	Type      string `json:"type"`
+	IsDefault bool   `json:"isDefault"`
+}
+
 type AlertTestCommand struct {
 type AlertTestCommand struct {
 	Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
 	Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
 	PanelId   int64            `json:"panelId" binding:"Required"`
 	PanelId   int64            `json:"panelId" binding:"Required"`

+ 6 - 0
pkg/api/dtos/user.go

@@ -50,3 +50,9 @@ type ResetUserPasswordForm struct {
 	NewPassword     string `json:"newPassword"`
 	NewPassword     string `json:"newPassword"`
 	ConfirmPassword string `json:"confirmPassword"`
 	ConfirmPassword string `json:"confirmPassword"`
 }
 }
+
+type UserLookupDTO struct {
+	UserID    int64  `json:"userId"`
+	Login     string `json:"login"`
+	AvatarURL string `json:"avatarUrl"`
+}

+ 85 - 24
pkg/api/org_users.go

@@ -3,27 +3,27 @@ package api
 import (
 import (
 	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
-	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/models"
 )
 )
 
 
 // POST /api/org/users
 // POST /api/org/users
-func AddOrgUserToCurrentOrg(c *m.ReqContext, cmd m.AddOrgUserCommand) Response {
+func AddOrgUserToCurrentOrg(c *models.ReqContext, cmd models.AddOrgUserCommand) Response {
 	cmd.OrgId = c.OrgId
 	cmd.OrgId = c.OrgId
 	return addOrgUserHelper(cmd)
 	return addOrgUserHelper(cmd)
 }
 }
 
 
 // POST /api/orgs/:orgId/users
 // POST /api/orgs/:orgId/users
-func AddOrgUser(c *m.ReqContext, cmd m.AddOrgUserCommand) Response {
+func AddOrgUser(c *models.ReqContext, cmd models.AddOrgUserCommand) Response {
 	cmd.OrgId = c.ParamsInt64(":orgId")
 	cmd.OrgId = c.ParamsInt64(":orgId")
 	return addOrgUserHelper(cmd)
 	return addOrgUserHelper(cmd)
 }
 }
 
 
-func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
+func addOrgUserHelper(cmd models.AddOrgUserCommand) Response {
 	if !cmd.Role.IsValid() {
 	if !cmd.Role.IsValid() {
 		return Error(400, "Invalid role specified", nil)
 		return Error(400, "Invalid role specified", nil)
 	}
 	}
 
 
-	userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
+	userQuery := models.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
 	err := bus.Dispatch(&userQuery)
 	err := bus.Dispatch(&userQuery)
 	if err != nil {
 	if err != nil {
 		return Error(404, "User not found", nil)
 		return Error(404, "User not found", nil)
@@ -34,7 +34,7 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
 	cmd.UserId = userToAdd.Id
 	cmd.UserId = userToAdd.Id
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
-		if err == m.ErrOrgUserAlreadyAdded {
+		if err == models.ErrOrgUserAlreadyAdded {
 			return Error(409, "User is already member of this organization", nil)
 			return Error(409, "User is already member of this organization", nil)
 		}
 		}
 		return Error(500, "Could not add user to organization", err)
 		return Error(500, "Could not add user to organization", err)
@@ -44,54 +44,115 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
 }
 }
 
 
 // GET /api/org/users
 // GET /api/org/users
-func GetOrgUsersForCurrentOrg(c *m.ReqContext) Response {
-	return getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
+func GetOrgUsersForCurrentOrg(c *models.ReqContext) Response {
+	result, err := getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
+	if err != nil {
+		return Error(500, "Failed to get users for current organization", err)
+	}
+
+	return JSON(200, result)
+}
+
+// GET /api/org/users/lookup
+func GetOrgUsersForCurrentOrgLookup(c *models.ReqContext) Response {
+	isAdmin, err := isOrgAdminFolderAdminOrTeamAdmin(c)
+	if err != nil {
+		return Error(500, "Failed to get users for current organization", err)
+	}
+
+	if !isAdmin {
+		return Error(403, "Permission denied", nil)
+	}
+
+	orgUsers, err := getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
+	if err != nil {
+		return Error(500, "Failed to get users for current organization", err)
+	}
+
+	result := make([]*dtos.UserLookupDTO, 0)
+
+	for _, u := range orgUsers {
+		result = append(result, &dtos.UserLookupDTO{
+			UserID:    u.UserId,
+			Login:     u.Login,
+			AvatarURL: u.AvatarUrl,
+		})
+	}
+
+	return JSON(200, result)
+}
+
+func isOrgAdminFolderAdminOrTeamAdmin(c *models.ReqContext) (bool, error) {
+	if c.OrgRole == models.ROLE_ADMIN {
+		return true, nil
+	}
+
+	hasAdminPermissionInFoldersQuery := models.HasAdminPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
+	if err := bus.Dispatch(&hasAdminPermissionInFoldersQuery); err != nil {
+		return false, err
+	}
+
+	if hasAdminPermissionInFoldersQuery.Result {
+		return true, nil
+	}
+
+	isAdminOfTeamsQuery := models.IsAdminOfTeamsQuery{SignedInUser: c.SignedInUser}
+	if err := bus.Dispatch(&isAdminOfTeamsQuery); err != nil {
+		return false, err
+	}
+
+	return isAdminOfTeamsQuery.Result, nil
 }
 }
 
 
 // GET /api/orgs/:orgId/users
 // GET /api/orgs/:orgId/users
-func GetOrgUsers(c *m.ReqContext) Response {
-	return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
+func GetOrgUsers(c *models.ReqContext) Response {
+	result, err := getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
+	if err != nil {
+		return Error(500, "Failed to get users for organization", err)
+	}
+
+	return JSON(200, result)
 }
 }
 
 
-func getOrgUsersHelper(orgID int64, query string, limit int) Response {
-	q := m.GetOrgUsersQuery{
+func getOrgUsersHelper(orgID int64, query string, limit int) ([]*models.OrgUserDTO, error) {
+	q := models.GetOrgUsersQuery{
 		OrgId: orgID,
 		OrgId: orgID,
 		Query: query,
 		Query: query,
 		Limit: limit,
 		Limit: limit,
 	}
 	}
 
 
 	if err := bus.Dispatch(&q); err != nil {
 	if err := bus.Dispatch(&q); err != nil {
-		return Error(500, "Failed to get account user", err)
+		return nil, err
 	}
 	}
 
 
 	for _, user := range q.Result {
 	for _, user := range q.Result {
 		user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
 		user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
 	}
 	}
 
 
-	return JSON(200, q.Result)
+	return q.Result, nil
 }
 }
 
 
 // PATCH /api/org/users/:userId
 // PATCH /api/org/users/:userId
-func UpdateOrgUserForCurrentOrg(c *m.ReqContext, cmd m.UpdateOrgUserCommand) Response {
+func UpdateOrgUserForCurrentOrg(c *models.ReqContext, cmd models.UpdateOrgUserCommand) Response {
 	cmd.OrgId = c.OrgId
 	cmd.OrgId = c.OrgId
 	cmd.UserId = c.ParamsInt64(":userId")
 	cmd.UserId = c.ParamsInt64(":userId")
 	return updateOrgUserHelper(cmd)
 	return updateOrgUserHelper(cmd)
 }
 }
 
 
 // PATCH /api/orgs/:orgId/users/:userId
 // PATCH /api/orgs/:orgId/users/:userId
-func UpdateOrgUser(c *m.ReqContext, cmd m.UpdateOrgUserCommand) Response {
+func UpdateOrgUser(c *models.ReqContext, cmd models.UpdateOrgUserCommand) Response {
 	cmd.OrgId = c.ParamsInt64(":orgId")
 	cmd.OrgId = c.ParamsInt64(":orgId")
 	cmd.UserId = c.ParamsInt64(":userId")
 	cmd.UserId = c.ParamsInt64(":userId")
 	return updateOrgUserHelper(cmd)
 	return updateOrgUserHelper(cmd)
 }
 }
 
 
-func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
+func updateOrgUserHelper(cmd models.UpdateOrgUserCommand) Response {
 	if !cmd.Role.IsValid() {
 	if !cmd.Role.IsValid() {
 		return Error(400, "Invalid role specified", nil)
 		return Error(400, "Invalid role specified", nil)
 	}
 	}
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
-		if err == m.ErrLastOrgAdmin {
+		if err == models.ErrLastOrgAdmin {
 			return Error(400, "Cannot change role so that there is no organization admin left", nil)
 			return Error(400, "Cannot change role so that there is no organization admin left", nil)
 		}
 		}
 		return Error(500, "Failed update org user", err)
 		return Error(500, "Failed update org user", err)
@@ -101,8 +162,8 @@ func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
 }
 }
 
 
 // DELETE /api/org/users/:userId
 // DELETE /api/org/users/:userId
-func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response {
-	return removeOrgUserHelper(&m.RemoveOrgUserCommand{
+func RemoveOrgUserForCurrentOrg(c *models.ReqContext) Response {
+	return removeOrgUserHelper(&models.RemoveOrgUserCommand{
 		UserId:                   c.ParamsInt64(":userId"),
 		UserId:                   c.ParamsInt64(":userId"),
 		OrgId:                    c.OrgId,
 		OrgId:                    c.OrgId,
 		ShouldDeleteOrphanedUser: true,
 		ShouldDeleteOrphanedUser: true,
@@ -110,16 +171,16 @@ func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response {
 }
 }
 
 
 // DELETE /api/orgs/:orgId/users/:userId
 // DELETE /api/orgs/:orgId/users/:userId
-func RemoveOrgUser(c *m.ReqContext) Response {
-	return removeOrgUserHelper(&m.RemoveOrgUserCommand{
+func RemoveOrgUser(c *models.ReqContext) Response {
+	return removeOrgUserHelper(&models.RemoveOrgUserCommand{
 		UserId: c.ParamsInt64(":userId"),
 		UserId: c.ParamsInt64(":userId"),
 		OrgId:  c.ParamsInt64(":orgId"),
 		OrgId:  c.ParamsInt64(":orgId"),
 	})
 	})
 }
 }
 
 
-func removeOrgUserHelper(cmd *m.RemoveOrgUserCommand) Response {
+func removeOrgUserHelper(cmd *models.RemoveOrgUserCommand) Response {
 	if err := bus.Dispatch(cmd); err != nil {
 	if err := bus.Dispatch(cmd); err != nil {
-		if err == m.ErrLastOrgAdmin {
+		if err == models.ErrLastOrgAdmin {
 			return Error(400, "Cannot remove last organization admin", nil)
 			return Error(400, "Cannot remove last organization admin", nil)
 		}
 		}
 		return Error(500, "Failed to remove user from organization", err)
 		return Error(500, "Failed to remove user from organization", err)

+ 5 - 0
pkg/models/folders.go

@@ -98,3 +98,8 @@ type HasEditPermissionInFoldersQuery struct {
 	SignedInUser *SignedInUser
 	SignedInUser *SignedInUser
 	Result       bool
 	Result       bool
 }
 }
+
+type HasAdminPermissionInFoldersQuery struct {
+	SignedInUser *SignedInUser
+	Result       bool
+}

+ 5 - 0
pkg/models/team.go

@@ -88,3 +88,8 @@ type SearchTeamQueryResult struct {
 	Page       int        `json:"page"`
 	Page       int        `json:"page"`
 	PerPage    int        `json:"perPage"`
 	PerPage    int        `json:"perPage"`
 }
 }
+
+type IsAdminOfTeamsQuery struct {
+	SignedInUser *SignedInUser
+	Result       bool
+}

+ 86 - 61
pkg/services/sqlstore/dashboard.go

@@ -6,7 +6,7 @@ import (
 
 
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/infra/metrics"
 	"github.com/grafana/grafana/pkg/infra/metrics"
-	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/search"
 	"github.com/grafana/grafana/pkg/services/search"
 	"github.com/grafana/grafana/pkg/util"
 	"github.com/grafana/grafana/pkg/util"
 )
 )
@@ -25,17 +25,18 @@ func init() {
 	bus.AddHandler("sql", GetDashboardsBySlug)
 	bus.AddHandler("sql", GetDashboardsBySlug)
 	bus.AddHandler("sql", ValidateDashboardBeforeSave)
 	bus.AddHandler("sql", ValidateDashboardBeforeSave)
 	bus.AddHandler("sql", HasEditPermissionInFolders)
 	bus.AddHandler("sql", HasEditPermissionInFolders)
+	bus.AddHandler("sql", HasAdminPermissionInFolders)
 }
 }
 
 
 var generateNewUid func() string = util.GenerateShortUID
 var generateNewUid func() string = util.GenerateShortUID
 
 
-func SaveDashboard(cmd *m.SaveDashboardCommand) error {
+func SaveDashboard(cmd *models.SaveDashboardCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
 		return saveDashboard(sess, cmd)
 		return saveDashboard(sess, cmd)
 	})
 	})
 }
 }
 
 
-func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
+func saveDashboard(sess *DBSession, cmd *models.SaveDashboardCommand) error {
 	dash := cmd.GetDashboardModel()
 	dash := cmd.GetDashboardModel()
 
 
 	userId := cmd.UserId
 	userId := cmd.UserId
@@ -45,13 +46,13 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
 	}
 	}
 
 
 	if dash.Id > 0 {
 	if dash.Id > 0 {
-		var existing m.Dashboard
+		var existing models.Dashboard
 		dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
 		dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 		if !dashWithIdExists {
 		if !dashWithIdExists {
-			return m.ErrDashboardNotFound
+			return models.ErrDashboardNotFound
 		}
 		}
 
 
 		// check for is someone else has written in between
 		// check for is someone else has written in between
@@ -59,13 +60,13 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
 			if cmd.Overwrite {
 			if cmd.Overwrite {
 				dash.SetVersion(existing.Version)
 				dash.SetVersion(existing.Version)
 			} else {
 			} else {
-				return m.ErrDashboardVersionMismatch
+				return models.ErrDashboardVersionMismatch
 			}
 			}
 		}
 		}
 
 
 		// do not allow plugin dashboard updates without overwrite flag
 		// do not allow plugin dashboard updates without overwrite flag
 		if existing.PluginId != "" && !cmd.Overwrite {
 		if existing.PluginId != "" && !cmd.Overwrite {
-			return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
+			return models.UpdatePluginDashboardError{PluginId: existing.PluginId}
 		}
 		}
 	}
 	}
 
 
@@ -108,10 +109,10 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
 	}
 	}
 
 
 	if affectedRows == 0 {
 	if affectedRows == 0 {
-		return m.ErrDashboardNotFound
+		return models.ErrDashboardNotFound
 	}
 	}
 
 
-	dashVersion := &m.DashboardVersion{
+	dashVersion := &models.DashboardVersion{
 		DashboardId:   dash.Id,
 		DashboardId:   dash.Id,
 		ParentVersion: parentVersion,
 		ParentVersion: parentVersion,
 		RestoredFrom:  cmd.RestoredFrom,
 		RestoredFrom:  cmd.RestoredFrom,
@@ -126,7 +127,7 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
 	if affectedRows, err = sess.Insert(dashVersion); err != nil {
 	if affectedRows, err = sess.Insert(dashVersion); err != nil {
 		return err
 		return err
 	} else if affectedRows == 0 {
 	} else if affectedRows == 0 {
-		return m.ErrDashboardNotFound
+		return models.ErrDashboardNotFound
 	}
 	}
 
 
 	// delete existing tags
 	// delete existing tags
@@ -154,7 +155,7 @@ func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
 	for i := 0; i < 3; i++ {
 	for i := 0; i < 3; i++ {
 		uid := generateNewUid()
 		uid := generateNewUid()
 
 
-		exists, err := sess.Where("org_id=? AND uid=?", orgId, uid).Get(&m.Dashboard{})
+		exists, err := sess.Where("org_id=? AND uid=?", orgId, uid).Get(&models.Dashboard{})
 		if err != nil {
 		if err != nil {
 			return "", err
 			return "", err
 		}
 		}
@@ -164,17 +165,17 @@ func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
 		}
 		}
 	}
 	}
 
 
-	return "", m.ErrDashboardFailedGenerateUniqueUid
+	return "", models.ErrDashboardFailedGenerateUniqueUid
 }
 }
 
 
-func GetDashboard(query *m.GetDashboardQuery) error {
-	dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid}
+func GetDashboard(query *models.GetDashboardQuery) error {
+	dashboard := models.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid}
 	has, err := x.Get(&dashboard)
 	has, err := x.Get(&dashboard)
 
 
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	} else if !has {
 	} else if !has {
-		return m.ErrDashboardNotFound
+		return models.ErrDashboardNotFound
 	}
 	}
 
 
 	dashboard.SetId(dashboard.Id)
 	dashboard.SetId(dashboard.Id)
@@ -262,7 +263,7 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard
 				Uid:         item.Uid,
 				Uid:         item.Uid,
 				Title:       item.Title,
 				Title:       item.Title,
 				Uri:         "db/" + item.Slug,
 				Uri:         "db/" + item.Slug,
-				Url:         m.GetDashboardFolderUrl(item.IsFolder, item.Uid, item.Slug),
+				Url:         models.GetDashboardFolderUrl(item.IsFolder, item.Uid, item.Slug),
 				Type:        getHitType(item),
 				Type:        getHitType(item),
 				FolderId:    item.FolderId,
 				FolderId:    item.FolderId,
 				FolderUid:   item.FolderUid,
 				FolderUid:   item.FolderUid,
@@ -271,7 +272,7 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard
 			}
 			}
 
 
 			if item.FolderId > 0 {
 			if item.FolderId > 0 {
-				hit.FolderUrl = m.GetFolderUrl(item.FolderUid, item.FolderSlug)
+				hit.FolderUrl = models.GetFolderUrl(item.FolderUid, item.FolderSlug)
 			}
 			}
 
 
 			query.Result = append(query.Result, hit)
 			query.Result = append(query.Result, hit)
@@ -283,7 +284,7 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard
 	}
 	}
 }
 }
 
 
-func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
+func GetDashboardTags(query *models.GetDashboardTagsQuery) error {
 	sql := `SELECT
 	sql := `SELECT
 					  COUNT(*) as count,
 					  COUNT(*) as count,
 						term
 						term
@@ -293,20 +294,20 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
 					GROUP BY term
 					GROUP BY term
 					ORDER BY term`
 					ORDER BY term`
 
 
-	query.Result = make([]*m.DashboardTagCloudItem, 0)
+	query.Result = make([]*models.DashboardTagCloudItem, 0)
 	sess := x.SQL(sql, query.OrgId)
 	sess := x.SQL(sql, query.OrgId)
 	err := sess.Find(&query.Result)
 	err := sess.Find(&query.Result)
 	return err
 	return err
 }
 }
 
 
-func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
+func DeleteDashboard(cmd *models.DeleteDashboardCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
-		dashboard := m.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
+		dashboard := models.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
 		has, err := sess.Get(&dashboard)
 		has, err := sess.Get(&dashboard)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		} else if !has {
 		} else if !has {
-			return m.ErrDashboardNotFound
+			return models.ErrDashboardNotFound
 		}
 		}
 
 
 		deletes := []string{
 		deletes := []string{
@@ -354,12 +355,12 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
 	})
 	})
 }
 }
 
 
-func GetDashboards(query *m.GetDashboardsQuery) error {
+func GetDashboards(query *models.GetDashboardsQuery) error {
 	if len(query.DashboardIds) == 0 {
 	if len(query.DashboardIds) == 0 {
-		return m.ErrCommandValidationFailed
+		return models.ErrCommandValidationFailed
 	}
 	}
 
 
-	var dashboards = make([]*m.Dashboard, 0)
+	var dashboards = make([]*models.Dashboard, 0)
 
 
 	err := x.In("id", query.DashboardIds).Find(&dashboards)
 	err := x.In("id", query.DashboardIds).Find(&dashboards)
 	query.Result = dashboards
 	query.Result = dashboards
@@ -368,18 +369,18 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
 
 
 // GetDashboardPermissionsForUser returns the maximum permission the specified user has for a dashboard(s)
 // GetDashboardPermissionsForUser returns the maximum permission the specified user has for a dashboard(s)
 // The function takes in a list of dashboard ids and the user id and role
 // The function takes in a list of dashboard ids and the user id and role
-func GetDashboardPermissionsForUser(query *m.GetDashboardPermissionsForUserQuery) error {
+func GetDashboardPermissionsForUser(query *models.GetDashboardPermissionsForUserQuery) error {
 	if len(query.DashboardIds) == 0 {
 	if len(query.DashboardIds) == 0 {
-		return m.ErrCommandValidationFailed
+		return models.ErrCommandValidationFailed
 	}
 	}
 
 
-	if query.OrgRole == m.ROLE_ADMIN {
-		var permissions = make([]*m.DashboardPermissionForUser, 0)
+	if query.OrgRole == models.ROLE_ADMIN {
+		var permissions = make([]*models.DashboardPermissionForUser, 0)
 		for _, d := range query.DashboardIds {
 		for _, d := range query.DashboardIds {
-			permissions = append(permissions, &m.DashboardPermissionForUser{
+			permissions = append(permissions, &models.DashboardPermissionForUser{
 				DashboardId:    d,
 				DashboardId:    d,
-				Permission:     m.PERMISSION_ADMIN,
-				PermissionName: m.PERMISSION_ADMIN.String(),
+				Permission:     models.PERMISSION_ADMIN,
+				PermissionName: models.PERMISSION_ADMIN.String(),
 			})
 			})
 		}
 		}
 		query.Result = permissions
 		query.Result = permissions
@@ -436,8 +437,8 @@ func GetDashboardPermissionsForUser(query *m.GetDashboardPermissionsForUserQuery
 	return err
 	return err
 }
 }
 
 
-func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error {
-	var dashboards = make([]*m.Dashboard, 0)
+func GetDashboardsByPluginId(query *models.GetDashboardsByPluginIdQuery) error {
+	var dashboards = make([]*models.Dashboard, 0)
 	whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false)
 	whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false)
 
 
 	err := x.Where(whereExpr, query.OrgId, query.PluginId).Find(&dashboards)
 	err := x.Where(whereExpr, query.OrgId, query.PluginId).Find(&dashboards)
@@ -449,7 +450,7 @@ type DashboardSlugDTO struct {
 	Slug string
 	Slug string
 }
 }
 
 
-func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error {
+func GetDashboardSlugById(query *models.GetDashboardSlugByIdQuery) error {
 	var rawSql = `SELECT slug from dashboard WHERE Id=?`
 	var rawSql = `SELECT slug from dashboard WHERE Id=?`
 	var slug = DashboardSlugDTO{}
 	var slug = DashboardSlugDTO{}
 
 
@@ -458,15 +459,15 @@ func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	} else if !exists {
 	} else if !exists {
-		return m.ErrDashboardNotFound
+		return models.ErrDashboardNotFound
 	}
 	}
 
 
 	query.Result = slug.Slug
 	query.Result = slug.Slug
 	return nil
 	return nil
 }
 }
 
 
-func GetDashboardsBySlug(query *m.GetDashboardsBySlugQuery) error {
-	var dashboards []*m.Dashboard
+func GetDashboardsBySlug(query *models.GetDashboardsBySlugQuery) error {
+	var dashboards []*models.Dashboard
 
 
 	if err := x.Where("org_id=? AND slug=?", query.OrgId, query.Slug).Find(&dashboards); err != nil {
 	if err := x.Where("org_id=? AND slug=?", query.OrgId, query.Slug).Find(&dashboards); err != nil {
 		return err
 		return err
@@ -476,28 +477,28 @@ func GetDashboardsBySlug(query *m.GetDashboardsBySlugQuery) error {
 	return nil
 	return nil
 }
 }
 
 
-func GetDashboardUIDById(query *m.GetDashboardRefByIdQuery) error {
+func GetDashboardUIDById(query *models.GetDashboardRefByIdQuery) error {
 	var rawSql = `SELECT uid, slug from dashboard WHERE Id=?`
 	var rawSql = `SELECT uid, slug from dashboard WHERE Id=?`
 
 
-	us := &m.DashboardRef{}
+	us := &models.DashboardRef{}
 
 
 	exists, err := x.SQL(rawSql, query.Id).Get(us)
 	exists, err := x.SQL(rawSql, query.Id).Get(us)
 
 
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	} else if !exists {
 	} else if !exists {
-		return m.ErrDashboardNotFound
+		return models.ErrDashboardNotFound
 	}
 	}
 
 
 	query.Result = us
 	query.Result = us
 	return nil
 	return nil
 }
 }
 
 
-func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
+func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *models.ValidateDashboardBeforeSaveCommand) (err error) {
 	dash := cmd.Dashboard
 	dash := cmd.Dashboard
 
 
 	dashWithIdExists := false
 	dashWithIdExists := false
-	var existingById m.Dashboard
+	var existingById models.Dashboard
 
 
 	if dash.Id > 0 {
 	if dash.Id > 0 {
 		dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
 		dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
@@ -506,7 +507,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
 		}
 		}
 
 
 		if !dashWithIdExists {
 		if !dashWithIdExists {
-			return m.ErrDashboardNotFound
+			return models.ErrDashboardNotFound
 		}
 		}
 
 
 		if dash.Uid == "" {
 		if dash.Uid == "" {
@@ -515,7 +516,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
 	}
 	}
 
 
 	dashWithUidExists := false
 	dashWithUidExists := false
-	var existingByUid m.Dashboard
+	var existingByUid models.Dashboard
 
 
 	if dash.Uid != "" {
 	if dash.Uid != "" {
 		dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
 		dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
@@ -525,14 +526,14 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
 	}
 	}
 
 
 	if dash.FolderId > 0 {
 	if dash.FolderId > 0 {
-		var existingFolder m.Dashboard
+		var existingFolder models.Dashboard
 		folderExists, folderErr := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId, dialect.BooleanStr(true)).Get(&existingFolder)
 		folderExists, folderErr := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId, dialect.BooleanStr(true)).Get(&existingFolder)
 		if folderErr != nil {
 		if folderErr != nil {
 			return folderErr
 			return folderErr
 		}
 		}
 
 
 		if !folderExists {
 		if !folderExists {
-			return m.ErrDashboardFolderNotFound
+			return models.ErrDashboardFolderNotFound
 		}
 		}
 	}
 	}
 
 
@@ -541,7 +542,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
 	}
 	}
 
 
 	if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
 	if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
-		return m.ErrDashboardWithSameUIDExists
+		return models.ErrDashboardWithSameUIDExists
 	}
 	}
 
 
 	existing := existingById
 	existing := existingById
@@ -558,7 +559,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
 
 
 	if (existing.IsFolder && !dash.IsFolder) ||
 	if (existing.IsFolder && !dash.IsFolder) ||
 		(!existing.IsFolder && dash.IsFolder) {
 		(!existing.IsFolder && dash.IsFolder) {
-		return m.ErrDashboardTypeMismatch
+		return models.ErrDashboardTypeMismatch
 	}
 	}
 
 
 	if !dash.IsFolder && dash.FolderId != existing.FolderId {
 	if !dash.IsFolder && dash.FolderId != existing.FolderId {
@@ -570,21 +571,21 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
 		if cmd.Overwrite {
 		if cmd.Overwrite {
 			dash.SetVersion(existing.Version)
 			dash.SetVersion(existing.Version)
 		} else {
 		} else {
-			return m.ErrDashboardVersionMismatch
+			return models.ErrDashboardVersionMismatch
 		}
 		}
 	}
 	}
 
 
 	// do not allow plugin dashboard updates without overwrite flag
 	// do not allow plugin dashboard updates without overwrite flag
 	if existing.PluginId != "" && !cmd.Overwrite {
 	if existing.PluginId != "" && !cmd.Overwrite {
-		return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
+		return models.UpdatePluginDashboardError{PluginId: existing.PluginId}
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) error {
+func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *models.ValidateDashboardBeforeSaveCommand) error {
 	dash := cmd.Dashboard
 	dash := cmd.Dashboard
-	var existing m.Dashboard
+	var existing models.Dashboard
 
 
 	exists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existing)
 	exists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existing)
 	if err != nil {
 	if err != nil {
@@ -593,11 +594,11 @@ func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashbo
 
 
 	if exists && dash.Id != existing.Id {
 	if exists && dash.Id != existing.Id {
 		if existing.IsFolder && !dash.IsFolder {
 		if existing.IsFolder && !dash.IsFolder {
-			return m.ErrDashboardWithSameNameAsFolder
+			return models.ErrDashboardWithSameNameAsFolder
 		}
 		}
 
 
 		if !existing.IsFolder && dash.IsFolder {
 		if !existing.IsFolder && dash.IsFolder {
-			return m.ErrDashboardFolderWithSameNameAsDashboard
+			return models.ErrDashboardFolderWithSameNameAsDashboard
 		}
 		}
 
 
 		if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) {
 		if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) {
@@ -609,15 +610,15 @@ func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashbo
 			dash.SetUid(existing.Uid)
 			dash.SetUid(existing.Uid)
 			dash.SetVersion(existing.Version)
 			dash.SetVersion(existing.Version)
 		} else {
 		} else {
-			return m.ErrDashboardWithSameNameInFolderExists
+			return models.ErrDashboardWithSameNameInFolderExists
 		}
 		}
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
-func ValidateDashboardBeforeSave(cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
-	cmd.Result = &m.ValidateDashboardBeforeSaveResult{}
+func ValidateDashboardBeforeSave(cmd *models.ValidateDashboardBeforeSaveCommand) (err error) {
+	cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
 		if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil {
 		if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil {
 			return err
 			return err
@@ -631,15 +632,39 @@ func ValidateDashboardBeforeSave(cmd *m.ValidateDashboardBeforeSaveCommand) (err
 	})
 	})
 }
 }
 
 
-func HasEditPermissionInFolders(query *m.HasEditPermissionInFoldersQuery) error {
-	if query.SignedInUser.HasRole(m.ROLE_EDITOR) {
+func HasEditPermissionInFolders(query *models.HasEditPermissionInFoldersQuery) error {
+	if query.SignedInUser.HasRole(models.ROLE_EDITOR) {
 		query.Result = true
 		query.Result = true
 		return nil
 		return nil
 	}
 	}
 
 
 	builder := &SqlBuilder{}
 	builder := &SqlBuilder{}
 	builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgId, dialect.BooleanStr(true))
 	builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgId, dialect.BooleanStr(true))
-	builder.writeDashboardPermissionFilter(query.SignedInUser, m.PERMISSION_EDIT)
+	builder.writeDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_EDIT)
+
+	type folderCount struct {
+		Count int64
+	}
+
+	resp := make([]*folderCount, 0)
+	if err := x.SQL(builder.GetSqlString(), builder.params...).Find(&resp); err != nil {
+		return err
+	}
+
+	query.Result = len(resp) > 0 && resp[0].Count > 0
+
+	return nil
+}
+
+func HasAdminPermissionInFolders(query *models.HasAdminPermissionInFoldersQuery) error {
+	if query.SignedInUser.HasRole(models.ROLE_ADMIN) {
+		query.Result = true
+		return nil
+	}
+
+	builder := &SqlBuilder{}
+	builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgId, dialect.BooleanStr(true))
+	builder.writeDashboardPermissionFilter(query.SignedInUser, models.PERMISSION_ADMIN)
 
 
 	type folderCount struct {
 	type folderCount struct {
 		Count int64
 		Count int64

+ 79 - 52
pkg/services/sqlstore/dashboard_folder_test.go

@@ -5,7 +5,7 @@ import (
 
 
 	. "github.com/smartystreets/goconvey/convey"
 	. "github.com/smartystreets/goconvey/convey"
 
 
-	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/search"
 	"github.com/grafana/grafana/pkg/services/search"
 )
 )
 
 
@@ -24,7 +24,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 			Convey("and no acls are set", func() {
 			Convey("and no acls are set", func() {
 				Convey("should return all dashboards", func() {
 				Convey("should return all dashboards", func() {
 					query := &search.FindPersistedDashboardsQuery{
 					query := &search.FindPersistedDashboardsQuery{
-						SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
+						SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
 						OrgId:        1,
 						OrgId:        1,
 						DashboardIds: []int64{folder.Id, dashInRoot.Id},
 						DashboardIds: []int64{folder.Id, dashInRoot.Id},
 					}
 					}
@@ -38,11 +38,11 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 
 
 			Convey("and acl is set for dashboard folder", func() {
 			Convey("and acl is set for dashboard folder", func() {
 				var otherUser int64 = 999
 				var otherUser int64 = 999
-				testHelperUpdateDashboardAcl(folder.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
+				testHelperUpdateDashboardAcl(folder.Id, models.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: models.PERMISSION_EDIT})
 
 
 				Convey("should not return folder", func() {
 				Convey("should not return folder", func() {
 					query := &search.FindPersistedDashboardsQuery{
 					query := &search.FindPersistedDashboardsQuery{
-						SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
+						SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
 						OrgId:        1, DashboardIds: []int64{folder.Id, dashInRoot.Id},
 						OrgId:        1, DashboardIds: []int64{folder.Id, dashInRoot.Id},
 					}
 					}
 					err := SearchDashboards(query)
 					err := SearchDashboards(query)
@@ -53,11 +53,11 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("when the user is given permission", func() {
 				Convey("when the user is given permission", func() {
-					testHelperUpdateDashboardAcl(folder.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: currentUser.Id, Permission: m.PERMISSION_EDIT})
+					testHelperUpdateDashboardAcl(folder.Id, models.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: currentUser.Id, Permission: models.PERMISSION_EDIT})
 
 
 					Convey("should be able to access folder", func() {
 					Convey("should be able to access folder", func() {
 						query := &search.FindPersistedDashboardsQuery{
 						query := &search.FindPersistedDashboardsQuery{
-							SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
+							SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
 							OrgId:        1,
 							OrgId:        1,
 							DashboardIds: []int64{folder.Id, dashInRoot.Id},
 							DashboardIds: []int64{folder.Id, dashInRoot.Id},
 						}
 						}
@@ -72,10 +72,10 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				Convey("when the user is an admin", func() {
 				Convey("when the user is an admin", func() {
 					Convey("should be able to access folder", func() {
 					Convey("should be able to access folder", func() {
 						query := &search.FindPersistedDashboardsQuery{
 						query := &search.FindPersistedDashboardsQuery{
-							SignedInUser: &m.SignedInUser{
+							SignedInUser: &models.SignedInUser{
 								UserId:  currentUser.Id,
 								UserId:  currentUser.Id,
 								OrgId:   1,
 								OrgId:   1,
-								OrgRole: m.ROLE_ADMIN,
+								OrgRole: models.ROLE_ADMIN,
 							},
 							},
 							OrgId:        1,
 							OrgId:        1,
 							DashboardIds: []int64{folder.Id, dashInRoot.Id},
 							DashboardIds: []int64{folder.Id, dashInRoot.Id},
@@ -92,10 +92,10 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 			Convey("and acl is set for dashboard child and folder has all permissions removed", func() {
 			Convey("and acl is set for dashboard child and folder has all permissions removed", func() {
 				var otherUser int64 = 999
 				var otherUser int64 = 999
 				testHelperUpdateDashboardAcl(folder.Id)
 				testHelperUpdateDashboardAcl(folder.Id)
-				testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
+				testHelperUpdateDashboardAcl(childDash.Id, models.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: models.PERMISSION_EDIT})
 
 
 				Convey("should not return folder or child", func() {
 				Convey("should not return folder or child", func() {
-					query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
+					query := &search.FindPersistedDashboardsQuery{SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
 					err := SearchDashboards(query)
 					err := SearchDashboards(query)
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
 					So(len(query.Result), ShouldEqual, 1)
 					So(len(query.Result), ShouldEqual, 1)
@@ -103,10 +103,10 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("when the user is given permission to child", func() {
 				Convey("when the user is given permission to child", func() {
-					testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{DashboardId: childDash.Id, OrgId: 1, UserId: currentUser.Id, Permission: m.PERMISSION_EDIT})
+					testHelperUpdateDashboardAcl(childDash.Id, models.DashboardAcl{DashboardId: childDash.Id, OrgId: 1, UserId: currentUser.Id, Permission: models.PERMISSION_EDIT})
 
 
 					Convey("should be able to search for child dashboard but not folder", func() {
 					Convey("should be able to search for child dashboard but not folder", func() {
-						query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
+						query := &search.FindPersistedDashboardsQuery{SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
 						err := SearchDashboards(query)
 						err := SearchDashboards(query)
 						So(err, ShouldBeNil)
 						So(err, ShouldBeNil)
 						So(len(query.Result), ShouldEqual, 2)
 						So(len(query.Result), ShouldEqual, 2)
@@ -118,10 +118,10 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				Convey("when the user is an admin", func() {
 				Convey("when the user is an admin", func() {
 					Convey("should be able to search for child dash and folder", func() {
 					Convey("should be able to search for child dash and folder", func() {
 						query := &search.FindPersistedDashboardsQuery{
 						query := &search.FindPersistedDashboardsQuery{
-							SignedInUser: &m.SignedInUser{
+							SignedInUser: &models.SignedInUser{
 								UserId:  currentUser.Id,
 								UserId:  currentUser.Id,
 								OrgId:   1,
 								OrgId:   1,
-								OrgRole: m.ROLE_ADMIN,
+								OrgRole: models.ROLE_ADMIN,
 							},
 							},
 							OrgId:        1,
 							OrgId:        1,
 							DashboardIds: []int64{folder.Id, dashInRoot.Id, childDash.Id},
 							DashboardIds: []int64{folder.Id, dashInRoot.Id, childDash.Id},
@@ -149,7 +149,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 
 
 			Convey("and one folder is expanded, the other collapsed", func() {
 			Convey("and one folder is expanded, the other collapsed", func() {
 				Convey("should return dashboards in root and expanded folder", func() {
 				Convey("should return dashboards in root and expanded folder", func() {
-					query := &search.FindPersistedDashboardsQuery{FolderIds: []int64{rootFolderId, folder1.Id}, SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1}
+					query := &search.FindPersistedDashboardsQuery{FolderIds: []int64{rootFolderId, folder1.Id}, SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER}, OrgId: 1}
 					err := SearchDashboards(query)
 					err := SearchDashboards(query)
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
 					So(len(query.Result), ShouldEqual, 4)
 					So(len(query.Result), ShouldEqual, 4)
@@ -162,14 +162,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 
 
 			Convey("and acl is set for one dashboard folder", func() {
 			Convey("and acl is set for one dashboard folder", func() {
 				var otherUser int64 = 999
 				var otherUser int64 = 999
-				testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
+				testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: otherUser, Permission: models.PERMISSION_EDIT})
 
 
 				Convey("and a dashboard is moved from folder without acl to the folder with an acl", func() {
 				Convey("and a dashboard is moved from folder without acl to the folder with an acl", func() {
 					moveDashboard(1, childDash2.Data, folder1.Id)
 					moveDashboard(1, childDash2.Data, folder1.Id)
 
 
 					Convey("should not return folder with acl or its children", func() {
 					Convey("should not return folder with acl or its children", func() {
 						query := &search.FindPersistedDashboardsQuery{
 						query := &search.FindPersistedDashboardsQuery{
-							SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
+							SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
 							OrgId:        1,
 							OrgId:        1,
 							DashboardIds: []int64{folder1.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
 							DashboardIds: []int64{folder1.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
 						}
 						}
@@ -184,7 +184,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 
 
 					Convey("should return folder without acl and its children", func() {
 					Convey("should return folder without acl and its children", func() {
 						query := &search.FindPersistedDashboardsQuery{
 						query := &search.FindPersistedDashboardsQuery{
-							SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
+							SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
 							OrgId:        1,
 							OrgId:        1,
 							DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
 							DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
 						}
 						}
@@ -199,12 +199,12 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("and a dashboard with an acl is moved to the folder without an acl", func() {
 				Convey("and a dashboard with an acl is moved to the folder without an acl", func() {
-					testHelperUpdateDashboardAcl(childDash1.Id, m.DashboardAcl{DashboardId: childDash1.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
+					testHelperUpdateDashboardAcl(childDash1.Id, models.DashboardAcl{DashboardId: childDash1.Id, OrgId: 1, UserId: otherUser, Permission: models.PERMISSION_EDIT})
 					moveDashboard(1, childDash1.Data, folder2.Id)
 					moveDashboard(1, childDash1.Data, folder2.Id)
 
 
 					Convey("should return folder without acl but not the dashboard with acl", func() {
 					Convey("should return folder without acl but not the dashboard with acl", func() {
 						query := &search.FindPersistedDashboardsQuery{
 						query := &search.FindPersistedDashboardsQuery{
-							SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
+							SignedInUser: &models.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
 							OrgId:        1,
 							OrgId:        1,
 							DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
 							DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
 						}
 						}
@@ -233,8 +233,8 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				Convey("Should have write access to all dashboard folders in their org", func() {
 				Convey("Should have write access to all dashboard folders in their org", func() {
 					query := search.FindPersistedDashboardsQuery{
 					query := search.FindPersistedDashboardsQuery{
 						OrgId:        1,
 						OrgId:        1,
-						SignedInUser: &m.SignedInUser{UserId: adminUser.Id, OrgRole: m.ROLE_ADMIN, OrgId: 1},
-						Permission:   m.PERMISSION_VIEW,
+						SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgRole: models.ROLE_ADMIN, OrgId: 1},
+						Permission:   models.PERMISSION_VIEW,
 						Type:         "dash-folder",
 						Type:         "dash-folder",
 					}
 					}
 
 
@@ -247,11 +247,11 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("should have write access to all folders and dashboards", func() {
 				Convey("should have write access to all folders and dashboards", func() {
-					query := m.GetDashboardPermissionsForUserQuery{
+					query := models.GetDashboardPermissionsForUserQuery{
 						DashboardIds: []int64{folder1.Id, folder2.Id},
 						DashboardIds: []int64{folder1.Id, folder2.Id},
 						OrgId:        1,
 						OrgId:        1,
 						UserId:       adminUser.Id,
 						UserId:       adminUser.Id,
-						OrgRole:      m.ROLE_ADMIN,
+						OrgRole:      models.ROLE_ADMIN,
 					}
 					}
 
 
 					err := GetDashboardPermissionsForUser(&query)
 					err := GetDashboardPermissionsForUser(&query)
@@ -259,26 +259,35 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 
 
 					So(len(query.Result), ShouldEqual, 2)
 					So(len(query.Result), ShouldEqual, 2)
 					So(query.Result[0].DashboardId, ShouldEqual, folder1.Id)
 					So(query.Result[0].DashboardId, ShouldEqual, folder1.Id)
-					So(query.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
+					So(query.Result[0].Permission, ShouldEqual, models.PERMISSION_ADMIN)
 					So(query.Result[1].DashboardId, ShouldEqual, folder2.Id)
 					So(query.Result[1].DashboardId, ShouldEqual, folder2.Id)
-					So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_ADMIN)
+					So(query.Result[1].Permission, ShouldEqual, models.PERMISSION_ADMIN)
 				})
 				})
 
 
 				Convey("should have edit permission in folders", func() {
 				Convey("should have edit permission in folders", func() {
-					query := &m.HasEditPermissionInFoldersQuery{
-						SignedInUser: &m.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: m.ROLE_ADMIN},
+					query := &models.HasEditPermissionInFoldersQuery{
+						SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: models.ROLE_ADMIN},
 					}
 					}
 					err := HasEditPermissionInFolders(query)
 					err := HasEditPermissionInFolders(query)
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
 					So(query.Result, ShouldBeTrue)
 					So(query.Result, ShouldBeTrue)
 				})
 				})
+
+				Convey("should have admin permission in folders", func() {
+					query := &models.HasAdminPermissionInFoldersQuery{
+						SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: models.ROLE_ADMIN},
+					}
+					err := HasAdminPermissionInFolders(query)
+					So(err, ShouldBeNil)
+					So(query.Result, ShouldBeTrue)
+				})
 			})
 			})
 
 
 			Convey("Editor users", func() {
 			Convey("Editor users", func() {
 				query := search.FindPersistedDashboardsQuery{
 				query := search.FindPersistedDashboardsQuery{
 					OrgId:        1,
 					OrgId:        1,
-					SignedInUser: &m.SignedInUser{UserId: editorUser.Id, OrgRole: m.ROLE_EDITOR, OrgId: 1},
-					Permission:   m.PERMISSION_EDIT,
+					SignedInUser: &models.SignedInUser{UserId: editorUser.Id, OrgRole: models.ROLE_EDITOR, OrgId: 1},
+					Permission:   models.PERMISSION_EDIT,
 				}
 				}
 
 
 				Convey("Should have write access to all dashboard folders with default ACL", func() {
 				Convey("Should have write access to all dashboard folders with default ACL", func() {
@@ -291,11 +300,11 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("should have edit access to folders with default ACL", func() {
 				Convey("should have edit access to folders with default ACL", func() {
-					query := m.GetDashboardPermissionsForUserQuery{
+					query := models.GetDashboardPermissionsForUserQuery{
 						DashboardIds: []int64{folder1.Id, folder2.Id},
 						DashboardIds: []int64{folder1.Id, folder2.Id},
 						OrgId:        1,
 						OrgId:        1,
 						UserId:       editorUser.Id,
 						UserId:       editorUser.Id,
-						OrgRole:      m.ROLE_EDITOR,
+						OrgRole:      models.ROLE_EDITOR,
 					}
 					}
 
 
 					err := GetDashboardPermissionsForUser(&query)
 					err := GetDashboardPermissionsForUser(&query)
@@ -303,13 +312,13 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 
 
 					So(len(query.Result), ShouldEqual, 2)
 					So(len(query.Result), ShouldEqual, 2)
 					So(query.Result[0].DashboardId, ShouldEqual, folder1.Id)
 					So(query.Result[0].DashboardId, ShouldEqual, folder1.Id)
-					So(query.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT)
+					So(query.Result[0].Permission, ShouldEqual, models.PERMISSION_EDIT)
 					So(query.Result[1].DashboardId, ShouldEqual, folder2.Id)
 					So(query.Result[1].DashboardId, ShouldEqual, folder2.Id)
-					So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_EDIT)
+					So(query.Result[1].Permission, ShouldEqual, models.PERMISSION_EDIT)
 				})
 				})
 
 
 				Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() {
 				Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() {
-					testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: editorUser.Id, Permission: m.PERMISSION_VIEW})
+					testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: editorUser.Id, Permission: models.PERMISSION_VIEW})
 
 
 					err := SearchDashboards(&query)
 					err := SearchDashboards(&query)
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
@@ -319,20 +328,29 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("should have edit permission in folders", func() {
 				Convey("should have edit permission in folders", func() {
-					query := &m.HasEditPermissionInFoldersQuery{
-						SignedInUser: &m.SignedInUser{UserId: editorUser.Id, OrgId: 1, OrgRole: m.ROLE_EDITOR},
+					query := &models.HasEditPermissionInFoldersQuery{
+						SignedInUser: &models.SignedInUser{UserId: editorUser.Id, OrgId: 1, OrgRole: models.ROLE_EDITOR},
 					}
 					}
 					err := HasEditPermissionInFolders(query)
 					err := HasEditPermissionInFolders(query)
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
 					So(query.Result, ShouldBeTrue)
 					So(query.Result, ShouldBeTrue)
 				})
 				})
+
+				Convey("should not have admin permission in folders", func() {
+					query := &models.HasAdminPermissionInFoldersQuery{
+						SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: models.ROLE_EDITOR},
+					}
+					err := HasAdminPermissionInFolders(query)
+					So(err, ShouldBeNil)
+					So(query.Result, ShouldBeFalse)
+				})
 			})
 			})
 
 
 			Convey("Viewer users", func() {
 			Convey("Viewer users", func() {
 				query := search.FindPersistedDashboardsQuery{
 				query := search.FindPersistedDashboardsQuery{
 					OrgId:        1,
 					OrgId:        1,
-					SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgRole: m.ROLE_VIEWER, OrgId: 1},
-					Permission:   m.PERMISSION_EDIT,
+					SignedInUser: &models.SignedInUser{UserId: viewerUser.Id, OrgRole: models.ROLE_VIEWER, OrgId: 1},
+					Permission:   models.PERMISSION_EDIT,
 				}
 				}
 
 
 				Convey("Should have no write access to any dashboard folders with default ACL", func() {
 				Convey("Should have no write access to any dashboard folders with default ACL", func() {
@@ -343,11 +361,11 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("should have view access to folders with default ACL", func() {
 				Convey("should have view access to folders with default ACL", func() {
-					query := m.GetDashboardPermissionsForUserQuery{
+					query := models.GetDashboardPermissionsForUserQuery{
 						DashboardIds: []int64{folder1.Id, folder2.Id},
 						DashboardIds: []int64{folder1.Id, folder2.Id},
 						OrgId:        1,
 						OrgId:        1,
 						UserId:       viewerUser.Id,
 						UserId:       viewerUser.Id,
-						OrgRole:      m.ROLE_VIEWER,
+						OrgRole:      models.ROLE_VIEWER,
 					}
 					}
 
 
 					err := GetDashboardPermissionsForUser(&query)
 					err := GetDashboardPermissionsForUser(&query)
@@ -355,13 +373,13 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 
 
 					So(len(query.Result), ShouldEqual, 2)
 					So(len(query.Result), ShouldEqual, 2)
 					So(query.Result[0].DashboardId, ShouldEqual, folder1.Id)
 					So(query.Result[0].DashboardId, ShouldEqual, folder1.Id)
-					So(query.Result[0].Permission, ShouldEqual, m.PERMISSION_VIEW)
+					So(query.Result[0].Permission, ShouldEqual, models.PERMISSION_VIEW)
 					So(query.Result[1].DashboardId, ShouldEqual, folder2.Id)
 					So(query.Result[1].DashboardId, ShouldEqual, folder2.Id)
-					So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_VIEW)
+					So(query.Result[1].Permission, ShouldEqual, models.PERMISSION_VIEW)
 				})
 				})
 
 
 				Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() {
 				Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() {
-					testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: m.PERMISSION_EDIT})
+					testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: models.PERMISSION_EDIT})
 
 
 					err := SearchDashboards(&query)
 					err := SearchDashboards(&query)
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
@@ -371,20 +389,29 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("should not have edit permission in folders", func() {
 				Convey("should not have edit permission in folders", func() {
-					query := &m.HasEditPermissionInFoldersQuery{
-						SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
+					query := &models.HasEditPermissionInFoldersQuery{
+						SignedInUser: &models.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
 					}
 					}
 					err := HasEditPermissionInFolders(query)
 					err := HasEditPermissionInFolders(query)
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
 					So(query.Result, ShouldBeFalse)
 					So(query.Result, ShouldBeFalse)
 				})
 				})
 
 
+				Convey("should not have admin permission in folders", func() {
+					query := &models.HasAdminPermissionInFoldersQuery{
+						SignedInUser: &models.SignedInUser{UserId: adminUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
+					}
+					err := HasAdminPermissionInFolders(query)
+					So(err, ShouldBeNil)
+					So(query.Result, ShouldBeFalse)
+				})
+
 				Convey("and admin permission is given for user with org role viewer in one dashboard folder", func() {
 				Convey("and admin permission is given for user with org role viewer in one dashboard folder", func() {
-					testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: m.PERMISSION_ADMIN})
+					testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: models.PERMISSION_ADMIN})
 
 
 					Convey("should have edit permission in folders", func() {
 					Convey("should have edit permission in folders", func() {
-						query := &m.HasEditPermissionInFoldersQuery{
-							SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
+						query := &models.HasEditPermissionInFoldersQuery{
+							SignedInUser: &models.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
 						}
 						}
 						err := HasEditPermissionInFolders(query)
 						err := HasEditPermissionInFolders(query)
 						So(err, ShouldBeNil)
 						So(err, ShouldBeNil)
@@ -393,11 +420,11 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("and edit permission is given for user with org role viewer in one dashboard folder", func() {
 				Convey("and edit permission is given for user with org role viewer in one dashboard folder", func() {
-					testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: m.PERMISSION_EDIT})
+					testHelperUpdateDashboardAcl(folder1.Id, models.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: models.PERMISSION_EDIT})
 
 
 					Convey("should have edit permission in folders", func() {
 					Convey("should have edit permission in folders", func() {
-						query := &m.HasEditPermissionInFoldersQuery{
-							SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
+						query := &models.HasEditPermissionInFoldersQuery{
+							SignedInUser: &models.SignedInUser{UserId: viewerUser.Id, OrgId: 1, OrgRole: models.ROLE_VIEWER},
 						}
 						}
 						err := HasEditPermissionInFolders(query)
 						err := HasEditPermissionInFolders(query)
 						So(err, ShouldBeNil)
 						So(err, ShouldBeNil)

+ 28 - 28
pkg/services/sqlstore/datasource_test.go

@@ -5,7 +5,7 @@ import (
 
 
 	. "github.com/smartystreets/goconvey/convey"
 	. "github.com/smartystreets/goconvey/convey"
 
 
-	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/models"
 )
 )
 
 
 type Test struct {
 type Test struct {
@@ -17,11 +17,11 @@ func TestDataAccess(t *testing.T) {
 	Convey("Testing DB", t, func() {
 	Convey("Testing DB", t, func() {
 		InitTestDB(t)
 		InitTestDB(t)
 		Convey("Can add datasource", func() {
 		Convey("Can add datasource", func() {
-			err := AddDataSource(&m.AddDataSourceCommand{
+			err := AddDataSource(&models.AddDataSourceCommand{
 				OrgId:    10,
 				OrgId:    10,
 				Name:     "laban",
 				Name:     "laban",
-				Type:     m.DS_INFLUXDB,
-				Access:   m.DS_ACCESS_DIRECT,
+				Type:     models.DS_INFLUXDB,
+				Access:   models.DS_ACCESS_DIRECT,
 				Url:      "http://test",
 				Url:      "http://test",
 				Database: "site",
 				Database: "site",
 				ReadOnly: true,
 				ReadOnly: true,
@@ -29,7 +29,7 @@ func TestDataAccess(t *testing.T) {
 
 
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 
 
-			query := m.GetDataSourcesQuery{OrgId: 10}
+			query := models.GetDataSourcesQuery{OrgId: 10}
 			err = GetDataSources(&query)
 			err = GetDataSources(&query)
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 
 
@@ -43,28 +43,28 @@ func TestDataAccess(t *testing.T) {
 		})
 		})
 
 
 		Convey("Given a datasource", func() {
 		Convey("Given a datasource", func() {
-			err := AddDataSource(&m.AddDataSourceCommand{
+			err := AddDataSource(&models.AddDataSourceCommand{
 				OrgId:  10,
 				OrgId:  10,
 				Name:   "nisse",
 				Name:   "nisse",
-				Type:   m.DS_GRAPHITE,
-				Access: m.DS_ACCESS_DIRECT,
+				Type:   models.DS_GRAPHITE,
+				Access: models.DS_ACCESS_DIRECT,
 				Url:    "http://test",
 				Url:    "http://test",
 			})
 			})
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 
 
-			query := m.GetDataSourcesQuery{OrgId: 10}
+			query := models.GetDataSourcesQuery{OrgId: 10}
 			err = GetDataSources(&query)
 			err = GetDataSources(&query)
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 
 
 			ds := query.Result[0]
 			ds := query.Result[0]
 
 
 			Convey(" updated ", func() {
 			Convey(" updated ", func() {
-				cmd := &m.UpdateDataSourceCommand{
+				cmd := &models.UpdateDataSourceCommand{
 					Id:      ds.Id,
 					Id:      ds.Id,
 					OrgId:   10,
 					OrgId:   10,
 					Name:    "nisse",
 					Name:    "nisse",
-					Type:    m.DS_GRAPHITE,
-					Access:  m.DS_ACCESS_PROXY,
+					Type:    models.DS_GRAPHITE,
+					Access:  models.DS_ACCESS_PROXY,
 					Url:     "http://test",
 					Url:     "http://test",
 					Version: ds.Version,
 					Version: ds.Version,
 				}
 				}
@@ -75,27 +75,27 @@ func TestDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("when someone else updated between read and update", func() {
 				Convey("when someone else updated between read and update", func() {
-					query := m.GetDataSourcesQuery{OrgId: 10}
+					query := models.GetDataSourcesQuery{OrgId: 10}
 					err = GetDataSources(&query)
 					err = GetDataSources(&query)
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
 
 
 					ds := query.Result[0]
 					ds := query.Result[0]
-					intendedUpdate := &m.UpdateDataSourceCommand{
+					intendedUpdate := &models.UpdateDataSourceCommand{
 						Id:      ds.Id,
 						Id:      ds.Id,
 						OrgId:   10,
 						OrgId:   10,
 						Name:    "nisse",
 						Name:    "nisse",
-						Type:    m.DS_GRAPHITE,
-						Access:  m.DS_ACCESS_PROXY,
+						Type:    models.DS_GRAPHITE,
+						Access:  models.DS_ACCESS_PROXY,
 						Url:     "http://test",
 						Url:     "http://test",
 						Version: ds.Version,
 						Version: ds.Version,
 					}
 					}
 
 
-					updateFromOtherUser := &m.UpdateDataSourceCommand{
+					updateFromOtherUser := &models.UpdateDataSourceCommand{
 						Id:      ds.Id,
 						Id:      ds.Id,
 						OrgId:   10,
 						OrgId:   10,
 						Name:    "nisse",
 						Name:    "nisse",
-						Type:    m.DS_GRAPHITE,
-						Access:  m.DS_ACCESS_PROXY,
+						Type:    models.DS_GRAPHITE,
+						Access:  models.DS_ACCESS_PROXY,
 						Url:     "http://test",
 						Url:     "http://test",
 						Version: ds.Version,
 						Version: ds.Version,
 					}
 					}
@@ -108,12 +108,12 @@ func TestDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("updating datasource without version", func() {
 				Convey("updating datasource without version", func() {
-					cmd := &m.UpdateDataSourceCommand{
+					cmd := &models.UpdateDataSourceCommand{
 						Id:     ds.Id,
 						Id:     ds.Id,
 						OrgId:  10,
 						OrgId:  10,
 						Name:   "nisse",
 						Name:   "nisse",
-						Type:   m.DS_GRAPHITE,
-						Access: m.DS_ACCESS_PROXY,
+						Type:   models.DS_GRAPHITE,
+						Access: models.DS_ACCESS_PROXY,
 						Url:    "http://test",
 						Url:    "http://test",
 					}
 					}
 
 
@@ -124,12 +124,12 @@ func TestDataAccess(t *testing.T) {
 				})
 				})
 
 
 				Convey("updating datasource without higher version", func() {
 				Convey("updating datasource without higher version", func() {
-					cmd := &m.UpdateDataSourceCommand{
+					cmd := &models.UpdateDataSourceCommand{
 						Id:      ds.Id,
 						Id:      ds.Id,
 						OrgId:   10,
 						OrgId:   10,
 						Name:    "nisse",
 						Name:    "nisse",
-						Type:    m.DS_GRAPHITE,
-						Access:  m.DS_ACCESS_PROXY,
+						Type:    models.DS_GRAPHITE,
+						Access:  models.DS_ACCESS_PROXY,
 						Url:     "http://test",
 						Url:     "http://test",
 						Version: 90000,
 						Version: 90000,
 					}
 					}
@@ -142,7 +142,7 @@ func TestDataAccess(t *testing.T) {
 			})
 			})
 
 
 			Convey("Can delete datasource by id", func() {
 			Convey("Can delete datasource by id", func() {
-				err := DeleteDataSourceById(&m.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: ds.OrgId})
+				err := DeleteDataSourceById(&models.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: ds.OrgId})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
 				GetDataSources(&query)
 				GetDataSources(&query)
@@ -150,7 +150,7 @@ func TestDataAccess(t *testing.T) {
 			})
 			})
 
 
 			Convey("Can delete datasource by name", func() {
 			Convey("Can delete datasource by name", func() {
-				err := DeleteDataSourceByName(&m.DeleteDataSourceByNameCommand{Name: ds.Name, OrgId: ds.OrgId})
+				err := DeleteDataSourceByName(&models.DeleteDataSourceByNameCommand{Name: ds.Name, OrgId: ds.OrgId})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
 				GetDataSources(&query)
 				GetDataSources(&query)
@@ -158,7 +158,7 @@ func TestDataAccess(t *testing.T) {
 			})
 			})
 
 
 			Convey("Can not delete datasource with wrong orgId", func() {
 			Convey("Can not delete datasource with wrong orgId", func() {
-				err := DeleteDataSourceById(&m.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: 123123})
+				err := DeleteDataSourceById(&models.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: 123123})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
 				GetDataSources(&query)
 				GetDataSources(&query)

+ 53 - 34
pkg/services/sqlstore/team.go

@@ -6,7 +6,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
-	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/models"
 )
 )
 
 
 func init() {
 func init() {
@@ -21,6 +21,7 @@ func init() {
 	bus.AddHandler("sql", UpdateTeamMember)
 	bus.AddHandler("sql", UpdateTeamMember)
 	bus.AddHandler("sql", RemoveTeamMember)
 	bus.AddHandler("sql", RemoveTeamMember)
 	bus.AddHandler("sql", GetTeamMembers)
 	bus.AddHandler("sql", GetTeamMembers)
+	bus.AddHandler("sql", IsAdminOfTeams)
 }
 }
 
 
 func getTeamSearchSqlBase() string {
 func getTeamSearchSqlBase() string {
@@ -45,16 +46,16 @@ func getTeamSelectSqlBase() string {
 		FROM team as team `
 		FROM team as team `
 }
 }
 
 
-func CreateTeam(cmd *m.CreateTeamCommand) error {
+func CreateTeam(cmd *models.CreateTeamCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
 
 
 		if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, 0, sess); err != nil {
 		if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, 0, sess); err != nil {
 			return err
 			return err
 		} else if isNameTaken {
 		} else if isNameTaken {
-			return m.ErrTeamNameTaken
+			return models.ErrTeamNameTaken
 		}
 		}
 
 
-		team := m.Team{
+		team := models.Team{
 			Name:    cmd.Name,
 			Name:    cmd.Name,
 			Email:   cmd.Email,
 			Email:   cmd.Email,
 			OrgId:   cmd.OrgId,
 			OrgId:   cmd.OrgId,
@@ -70,16 +71,16 @@ func CreateTeam(cmd *m.CreateTeamCommand) error {
 	})
 	})
 }
 }
 
 
-func UpdateTeam(cmd *m.UpdateTeamCommand) error {
+func UpdateTeam(cmd *models.UpdateTeamCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
 
 
 		if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, cmd.Id, sess); err != nil {
 		if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, cmd.Id, sess); err != nil {
 			return err
 			return err
 		} else if isNameTaken {
 		} else if isNameTaken {
-			return m.ErrTeamNameTaken
+			return models.ErrTeamNameTaken
 		}
 		}
 
 
-		team := m.Team{
+		team := models.Team{
 			Name:    cmd.Name,
 			Name:    cmd.Name,
 			Email:   cmd.Email,
 			Email:   cmd.Email,
 			Updated: time.Now(),
 			Updated: time.Now(),
@@ -94,7 +95,7 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
 		}
 		}
 
 
 		if affectedRows == 0 {
 		if affectedRows == 0 {
-			return m.ErrTeamNotFound
+			return models.ErrTeamNotFound
 		}
 		}
 
 
 		return nil
 		return nil
@@ -102,7 +103,7 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
 }
 }
 
 
 // DeleteTeam will delete a team, its member and any permissions connected to the team
 // DeleteTeam will delete a team, its member and any permissions connected to the team
-func DeleteTeam(cmd *m.DeleteTeamCommand) error {
+func DeleteTeam(cmd *models.DeleteTeamCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
 		if _, err := teamExists(cmd.OrgId, cmd.Id, sess); err != nil {
 		if _, err := teamExists(cmd.OrgId, cmd.Id, sess); err != nil {
 			return err
 			return err
@@ -128,14 +129,14 @@ func teamExists(orgId int64, teamId int64, sess *DBSession) (bool, error) {
 	if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", orgId, teamId); err != nil {
 	if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", orgId, teamId); err != nil {
 		return false, err
 		return false, err
 	} else if len(res) != 1 {
 	} else if len(res) != 1 {
-		return false, m.ErrTeamNotFound
+		return false, models.ErrTeamNotFound
 	}
 	}
 
 
 	return true, nil
 	return true, nil
 }
 }
 
 
 func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession) (bool, error) {
 func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession) (bool, error) {
-	var team m.Team
+	var team models.Team
 	exists, err := sess.Where("org_id=? and name=?", orgId, name).Get(&team)
 	exists, err := sess.Where("org_id=? and name=?", orgId, name).Get(&team)
 
 
 	if err != nil {
 	if err != nil {
@@ -149,9 +150,9 @@ func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession
 	return false, nil
 	return false, nil
 }
 }
 
 
-func SearchTeams(query *m.SearchTeamsQuery) error {
-	query.Result = m.SearchTeamQueryResult{
-		Teams: make([]*m.TeamDTO, 0),
+func SearchTeams(query *models.SearchTeamsQuery) error {
+	query.Result = models.SearchTeamQueryResult{
+		Teams: make([]*models.TeamDTO, 0),
 	}
 	}
 	queryWithWildcards := "%" + query.Query + "%"
 	queryWithWildcards := "%" + query.Query + "%"
 
 
@@ -189,7 +190,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
 		return err
 		return err
 	}
 	}
 
 
-	team := m.Team{}
+	team := models.Team{}
 	countSess := x.Table("team")
 	countSess := x.Table("team")
 	if query.Query != "" {
 	if query.Query != "" {
 		countSess.Where(`name `+dialect.LikeStr()+` ?`, queryWithWildcards)
 		countSess.Where(`name `+dialect.LikeStr()+` ?`, queryWithWildcards)
@@ -205,13 +206,13 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
 	return err
 	return err
 }
 }
 
 
-func GetTeamById(query *m.GetTeamByIdQuery) error {
+func GetTeamById(query *models.GetTeamByIdQuery) error {
 	var sql bytes.Buffer
 	var sql bytes.Buffer
 
 
 	sql.WriteString(getTeamSelectSqlBase())
 	sql.WriteString(getTeamSelectSqlBase())
 	sql.WriteString(` WHERE team.org_id = ? and team.id = ?`)
 	sql.WriteString(` WHERE team.org_id = ? and team.id = ?`)
 
 
-	var team m.TeamDTO
+	var team models.TeamDTO
 	exists, err := x.SQL(sql.String(), query.OrgId, query.Id).Get(&team)
 	exists, err := x.SQL(sql.String(), query.OrgId, query.Id).Get(&team)
 
 
 	if err != nil {
 	if err != nil {
@@ -219,7 +220,7 @@ func GetTeamById(query *m.GetTeamByIdQuery) error {
 	}
 	}
 
 
 	if !exists {
 	if !exists {
-		return m.ErrTeamNotFound
+		return models.ErrTeamNotFound
 	}
 	}
 
 
 	query.Result = &team
 	query.Result = &team
@@ -227,8 +228,8 @@ func GetTeamById(query *m.GetTeamByIdQuery) error {
 }
 }
 
 
 // GetTeamsByUser is used by the Guardian when checking a users' permissions
 // GetTeamsByUser is used by the Guardian when checking a users' permissions
-func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
-	query.Result = make([]*m.TeamDTO, 0)
+func GetTeamsByUser(query *models.GetTeamsByUserQuery) error {
+	query.Result = make([]*models.TeamDTO, 0)
 
 
 	var sql bytes.Buffer
 	var sql bytes.Buffer
 
 
@@ -241,19 +242,19 @@ func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
 }
 }
 
 
 // AddTeamMember adds a user to a team
 // AddTeamMember adds a user to a team
-func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
+func AddTeamMember(cmd *models.AddTeamMemberCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
 		if res, err := sess.Query("SELECT 1 from team_member WHERE org_id=? and team_id=? and user_id=?", cmd.OrgId, cmd.TeamId, cmd.UserId); err != nil {
 		if res, err := sess.Query("SELECT 1 from team_member WHERE org_id=? and team_id=? and user_id=?", cmd.OrgId, cmd.TeamId, cmd.UserId); err != nil {
 			return err
 			return err
 		} else if len(res) == 1 {
 		} else if len(res) == 1 {
-			return m.ErrTeamMemberAlreadyAdded
+			return models.ErrTeamMemberAlreadyAdded
 		}
 		}
 
 
 		if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
 		if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
 			return err
 			return err
 		}
 		}
 
 
-		entity := m.TeamMember{
+		entity := models.TeamMember{
 			OrgId:      cmd.OrgId,
 			OrgId:      cmd.OrgId,
 			TeamId:     cmd.TeamId,
 			TeamId:     cmd.TeamId,
 			UserId:     cmd.UserId,
 			UserId:     cmd.UserId,
@@ -268,23 +269,23 @@ func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
 	})
 	})
 }
 }
 
 
-func getTeamMember(sess *DBSession, orgId int64, teamId int64, userId int64) (m.TeamMember, error) {
+func getTeamMember(sess *DBSession, orgId int64, teamId int64, userId int64) (models.TeamMember, error) {
 	rawSql := `SELECT * FROM team_member WHERE org_id=? and team_id=? and user_id=?`
 	rawSql := `SELECT * FROM team_member WHERE org_id=? and team_id=? and user_id=?`
-	var member m.TeamMember
+	var member models.TeamMember
 	exists, err := sess.SQL(rawSql, orgId, teamId, userId).Get(&member)
 	exists, err := sess.SQL(rawSql, orgId, teamId, userId).Get(&member)
 
 
 	if err != nil {
 	if err != nil {
 		return member, err
 		return member, err
 	}
 	}
 	if !exists {
 	if !exists {
-		return member, m.ErrTeamMemberNotFound
+		return member, models.ErrTeamMemberNotFound
 	}
 	}
 
 
 	return member, nil
 	return member, nil
 }
 }
 
 
 // UpdateTeamMember updates a team member
 // UpdateTeamMember updates a team member
-func UpdateTeamMember(cmd *m.UpdateTeamMemberCommand) error {
+func UpdateTeamMember(cmd *models.UpdateTeamMemberCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
 		member, err := getTeamMember(sess, cmd.OrgId, cmd.TeamId, cmd.UserId)
 		member, err := getTeamMember(sess, cmd.OrgId, cmd.TeamId, cmd.UserId)
 		if err != nil {
 		if err != nil {
@@ -298,7 +299,7 @@ func UpdateTeamMember(cmd *m.UpdateTeamMemberCommand) error {
 			}
 			}
 		}
 		}
 
 
-		if cmd.Permission != m.PERMISSION_ADMIN { // make sure we don't get invalid permission levels in store
+		if cmd.Permission != models.PERMISSION_ADMIN { // make sure we don't get invalid permission levels in store
 			cmd.Permission = 0
 			cmd.Permission = 0
 		}
 		}
 
 
@@ -310,7 +311,7 @@ func UpdateTeamMember(cmd *m.UpdateTeamMemberCommand) error {
 }
 }
 
 
 // RemoveTeamMember removes a member from a team
 // RemoveTeamMember removes a member from a team
-func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
+func RemoveTeamMember(cmd *models.RemoveTeamMemberCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 	return inTransaction(func(sess *DBSession) error {
 		if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
 		if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
 			return err
 			return err
@@ -330,7 +331,7 @@ func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
 		}
 		}
 		rows, err := res.RowsAffected()
 		rows, err := res.RowsAffected()
 		if rows == 0 {
 		if rows == 0 {
-			return m.ErrTeamMemberNotFound
+			return models.ErrTeamMemberNotFound
 		}
 		}
 
 
 		return err
 		return err
@@ -340,7 +341,7 @@ func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
 func isLastAdmin(sess *DBSession, orgId int64, teamId int64, userId int64) (bool, error) {
 func isLastAdmin(sess *DBSession, orgId int64, teamId int64, userId int64) (bool, error) {
 	rawSql := "SELECT user_id FROM team_member WHERE org_id=? and team_id=? and permission=?"
 	rawSql := "SELECT user_id FROM team_member WHERE org_id=? and team_id=? and permission=?"
 	userIds := []*int64{}
 	userIds := []*int64{}
-	err := sess.SQL(rawSql, orgId, teamId, m.PERMISSION_ADMIN).Find(&userIds)
+	err := sess.SQL(rawSql, orgId, teamId, models.PERMISSION_ADMIN).Find(&userIds)
 	if err != nil {
 	if err != nil {
 		return false, err
 		return false, err
 	}
 	}
@@ -354,15 +355,15 @@ func isLastAdmin(sess *DBSession, orgId int64, teamId int64, userId int64) (bool
 	}
 	}
 
 
 	if isAdmin && len(userIds) == 1 {
 	if isAdmin && len(userIds) == 1 {
-		return true, m.ErrLastTeamAdmin
+		return true, models.ErrLastTeamAdmin
 	}
 	}
 
 
 	return false, err
 	return false, err
 }
 }
 
 
 // GetTeamMembers return a list of members for the specified team
 // GetTeamMembers return a list of members for the specified team
-func GetTeamMembers(query *m.GetTeamMembersQuery) error {
-	query.Result = make([]*m.TeamMemberDTO, 0)
+func GetTeamMembers(query *models.GetTeamMembersQuery) error {
+	query.Result = make([]*models.TeamMemberDTO, 0)
 	sess := x.Table("team_member")
 	sess := x.Table("team_member")
 	sess.Join("INNER", x.Dialect().Quote("user"), fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user")))
 	sess.Join("INNER", x.Dialect().Quote("user"), fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user")))
 
 
@@ -392,3 +393,21 @@ func GetTeamMembers(query *m.GetTeamMembersQuery) error {
 	err := sess.Find(&query.Result)
 	err := sess.Find(&query.Result)
 	return err
 	return err
 }
 }
+
+func IsAdminOfTeams(query *models.IsAdminOfTeamsQuery) error {
+	builder := &SqlBuilder{}
+	builder.Write("SELECT COUNT(team.id) AS count FROM team INNER JOIN team_member ON team_member.team_id = team.id WHERE team.org_id = ? AND team_member.user_id = ? AND team_member.permission = ?", query.SignedInUser.OrgId, query.SignedInUser.UserId, models.PERMISSION_ADMIN)
+
+	type teamCount struct {
+		Count int64
+	}
+
+	resp := make([]*teamCount, 0)
+	if err := x.SQL(builder.GetSqlString(), builder.params...).Find(&resp); err != nil {
+		return err
+	}
+
+	query.Result = len(resp) > 0 && resp[0].Count > 0
+
+	return nil
+}

+ 68 - 50
pkg/services/sqlstore/team_test.go

@@ -7,7 +7,7 @@ import (
 
 
 	. "github.com/smartystreets/goconvey/convey"
 	. "github.com/smartystreets/goconvey/convey"
 
 
-	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/models"
 )
 )
 
 
 func TestTeamCommandsAndQueries(t *testing.T) {
 func TestTeamCommandsAndQueries(t *testing.T) {
@@ -18,7 +18,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 		Convey("Given saved users and two teams", func() {
 		Convey("Given saved users and two teams", func() {
 			var userIds []int64
 			var userIds []int64
 			for i := 0; i < 5; i++ {
 			for i := 0; i < 5; i++ {
-				userCmd := &m.CreateUserCommand{
+				userCmd := &models.CreateUserCommand{
 					Email: fmt.Sprint("user", i, "@test.com"),
 					Email: fmt.Sprint("user", i, "@test.com"),
 					Name:  fmt.Sprint("user", i),
 					Name:  fmt.Sprint("user", i),
 					Login: fmt.Sprint("loginuser", i),
 					Login: fmt.Sprint("loginuser", i),
@@ -29,8 +29,8 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 			}
 			}
 
 
 			var testOrgId int64 = 1
 			var testOrgId int64 = 1
-			group1 := m.CreateTeamCommand{OrgId: testOrgId, Name: "group1 name", Email: "test1@test.com"}
-			group2 := m.CreateTeamCommand{OrgId: testOrgId, Name: "group2 name", Email: "test2@test.com"}
+			group1 := models.CreateTeamCommand{OrgId: testOrgId, Name: "group1 name", Email: "test1@test.com"}
+			group2 := models.CreateTeamCommand{OrgId: testOrgId, Name: "group2 name", Email: "test2@test.com"}
 
 
 			err := CreateTeam(&group1)
 			err := CreateTeam(&group1)
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
@@ -38,7 +38,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 
 
 			Convey("Should be able to create teams and add users", func() {
 			Convey("Should be able to create teams and add users", func() {
-				query := &m.SearchTeamsQuery{OrgId: testOrgId, Name: "group1 name", Page: 1, Limit: 10}
+				query := &models.SearchTeamsQuery{OrgId: testOrgId, Name: "group1 name", Page: 1, Limit: 10}
 				err = SearchTeams(query)
 				err = SearchTeams(query)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(query.Page, ShouldEqual, 1)
 				So(query.Page, ShouldEqual, 1)
@@ -48,12 +48,12 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 				So(team1.Email, ShouldEqual, "test1@test.com")
 				So(team1.Email, ShouldEqual, "test1@test.com")
 				So(team1.OrgId, ShouldEqual, testOrgId)
 				So(team1.OrgId, ShouldEqual, testOrgId)
 
 
-				err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[0]})
+				err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[0]})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-				err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[1], External: true})
+				err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[1], External: true})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				q1 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id}
+				q1 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id}
 				err = GetTeamMembers(q1)
 				err = GetTeamMembers(q1)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(q1.Result, ShouldHaveLength, 2)
 				So(q1.Result, ShouldHaveLength, 2)
@@ -65,7 +65,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 				So(q1.Result[1].OrgId, ShouldEqual, testOrgId)
 				So(q1.Result[1].OrgId, ShouldEqual, testOrgId)
 				So(q1.Result[1].External, ShouldEqual, true)
 				So(q1.Result[1].External, ShouldEqual, true)
 
 
-				q2 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true}
+				q2 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true}
 				err = GetTeamMembers(q2)
 				err = GetTeamMembers(q2)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(q2.Result, ShouldHaveLength, 1)
 				So(q2.Result, ShouldHaveLength, 1)
@@ -77,20 +77,20 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 
 
 			Convey("Should return latest auth module for users when getting team members", func() {
 			Convey("Should return latest auth module for users when getting team members", func() {
 				userId := userIds[1]
 				userId := userIds[1]
-				err := SetAuthInfo(&m.SetAuthInfoCommand{UserId: userId, AuthModule: "oauth_github", AuthId: "1234567"})
+				err := SetAuthInfo(&models.SetAuthInfoCommand{UserId: userId, AuthModule: "oauth_github", AuthId: "1234567"})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				teamQuery := &m.SearchTeamsQuery{OrgId: testOrgId, Name: "group1 name", Page: 1, Limit: 10}
+				teamQuery := &models.SearchTeamsQuery{OrgId: testOrgId, Name: "group1 name", Page: 1, Limit: 10}
 				err = SearchTeams(teamQuery)
 				err = SearchTeams(teamQuery)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(teamQuery.Page, ShouldEqual, 1)
 				So(teamQuery.Page, ShouldEqual, 1)
 
 
 				team1 := teamQuery.Result.Teams[0]
 				team1 := teamQuery.Result.Teams[0]
 
 
-				err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userId, External: true})
+				err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userId, External: true})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				memberQuery := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true}
+				memberQuery := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true}
 				err = GetTeamMembers(memberQuery)
 				err = GetTeamMembers(memberQuery)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(memberQuery.Result, ShouldHaveLength, 1)
 				So(memberQuery.Result, ShouldHaveLength, 1)
@@ -104,44 +104,44 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 			Convey("Should be able to update users in a team", func() {
 			Convey("Should be able to update users in a team", func() {
 				userId := userIds[0]
 				userId := userIds[0]
 				team := group1.Result
 				team := group1.Result
-				addMemberCmd := m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team.Id, UserId: userId}
+				addMemberCmd := models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team.Id, UserId: userId}
 				err = AddTeamMember(&addMemberCmd)
 				err = AddTeamMember(&addMemberCmd)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				qBeforeUpdate := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
+				qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
 				err = GetTeamMembers(qBeforeUpdate)
 				err = GetTeamMembers(qBeforeUpdate)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0)
 				So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0)
 
 
-				err = UpdateTeamMember(&m.UpdateTeamMemberCommand{
+				err = UpdateTeamMember(&models.UpdateTeamMemberCommand{
 					UserId:     userId,
 					UserId:     userId,
 					OrgId:      testOrgId,
 					OrgId:      testOrgId,
 					TeamId:     team.Id,
 					TeamId:     team.Id,
-					Permission: m.PERMISSION_ADMIN,
+					Permission: models.PERMISSION_ADMIN,
 				})
 				})
 
 
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				qAfterUpdate := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
+				qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
 				err = GetTeamMembers(qAfterUpdate)
 				err = GetTeamMembers(qAfterUpdate)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-				So(qAfterUpdate.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
+				So(qAfterUpdate.Result[0].Permission, ShouldEqual, models.PERMISSION_ADMIN)
 			})
 			})
 
 
 			Convey("Should default to member permission level when updating a user with invalid permission level", func() {
 			Convey("Should default to member permission level when updating a user with invalid permission level", func() {
 				userID := userIds[0]
 				userID := userIds[0]
 				team := group1.Result
 				team := group1.Result
-				addMemberCmd := m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team.Id, UserId: userID}
+				addMemberCmd := models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team.Id, UserId: userID}
 				err = AddTeamMember(&addMemberCmd)
 				err = AddTeamMember(&addMemberCmd)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				qBeforeUpdate := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
+				qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
 				err = GetTeamMembers(qBeforeUpdate)
 				err = GetTeamMembers(qBeforeUpdate)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0)
 				So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0)
 
 
-				invalidPermissionLevel := m.PERMISSION_EDIT
-				err = UpdateTeamMember(&m.UpdateTeamMemberCommand{
+				invalidPermissionLevel := models.PERMISSION_EDIT
+				err = UpdateTeamMember(&models.UpdateTeamMemberCommand{
 					UserId:     userID,
 					UserId:     userID,
 					OrgId:      testOrgId,
 					OrgId:      testOrgId,
 					TeamId:     team.Id,
 					TeamId:     team.Id,
@@ -150,31 +150,31 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 
 
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				qAfterUpdate := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
+				qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id}
 				err = GetTeamMembers(qAfterUpdate)
 				err = GetTeamMembers(qAfterUpdate)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(qAfterUpdate.Result[0].Permission, ShouldEqual, 0)
 				So(qAfterUpdate.Result[0].Permission, ShouldEqual, 0)
 			})
 			})
 
 
 			Convey("Shouldn't be able to update a user not in the team.", func() {
 			Convey("Shouldn't be able to update a user not in the team.", func() {
-				err = UpdateTeamMember(&m.UpdateTeamMemberCommand{
+				err = UpdateTeamMember(&models.UpdateTeamMemberCommand{
 					UserId:     1,
 					UserId:     1,
 					OrgId:      testOrgId,
 					OrgId:      testOrgId,
 					TeamId:     group1.Result.Id,
 					TeamId:     group1.Result.Id,
-					Permission: m.PERMISSION_ADMIN,
+					Permission: models.PERMISSION_ADMIN,
 				})
 				})
 
 
-				So(err, ShouldEqual, m.ErrTeamMemberNotFound)
+				So(err, ShouldEqual, models.ErrTeamMemberNotFound)
 			})
 			})
 
 
 			Convey("Should be able to search for teams", func() {
 			Convey("Should be able to search for teams", func() {
-				query := &m.SearchTeamsQuery{OrgId: testOrgId, Query: "group", Page: 1}
+				query := &models.SearchTeamsQuery{OrgId: testOrgId, Query: "group", Page: 1}
 				err = SearchTeams(query)
 				err = SearchTeams(query)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(len(query.Result.Teams), ShouldEqual, 2)
 				So(len(query.Result.Teams), ShouldEqual, 2)
 				So(query.Result.TotalCount, ShouldEqual, 2)
 				So(query.Result.TotalCount, ShouldEqual, 2)
 
 
-				query2 := &m.SearchTeamsQuery{OrgId: testOrgId, Query: ""}
+				query2 := &models.SearchTeamsQuery{OrgId: testOrgId, Query: ""}
 				err = SearchTeams(query2)
 				err = SearchTeams(query2)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(len(query2.Result.Teams), ShouldEqual, 2)
 				So(len(query2.Result.Teams), ShouldEqual, 2)
@@ -182,10 +182,10 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 
 
 			Convey("Should be able to return all teams a user is member of", func() {
 			Convey("Should be able to return all teams a user is member of", func() {
 				groupId := group2.Result.Id
 				groupId := group2.Result.Id
-				err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
+				err := AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				query := &m.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]}
+				query := &models.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]}
 				err = GetTeamsByUser(query)
 				err = GetTeamsByUser(query)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(len(query.Result), ShouldEqual, 1)
 				So(len(query.Result), ShouldEqual, 1)
@@ -194,66 +194,84 @@ func TestTeamCommandsAndQueries(t *testing.T) {
 			})
 			})
 
 
 			Convey("Should be able to remove users from a group", func() {
 			Convey("Should be able to remove users from a group", func() {
-				err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]})
+				err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				err = RemoveTeamMember(&m.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]})
+				err = RemoveTeamMember(&models.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				q2 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: group1.Result.Id}
+				q2 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: group1.Result.Id}
 				err = GetTeamMembers(q2)
 				err = GetTeamMembers(q2)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(len(q2.Result), ShouldEqual, 0)
 				So(len(q2.Result), ShouldEqual, 0)
 			})
 			})
 
 
 			Convey("When ProtectLastAdmin is set to true", func() {
 			Convey("When ProtectLastAdmin is set to true", func() {
-				err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], Permission: m.PERMISSION_ADMIN})
+				err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], Permission: models.PERMISSION_ADMIN})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
 				Convey("A user should not be able to remove the last admin", func() {
 				Convey("A user should not be able to remove the last admin", func() {
-					err = RemoveTeamMember(&m.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], ProtectLastAdmin: true})
-					So(err, ShouldEqual, m.ErrLastTeamAdmin)
+					err = RemoveTeamMember(&models.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], ProtectLastAdmin: true})
+					So(err, ShouldEqual, models.ErrLastTeamAdmin)
 				})
 				})
 
 
 				Convey("A user should be able to remove an admin if there are other admins", func() {
 				Convey("A user should be able to remove an admin if there are other admins", func() {
-					AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[1], Permission: m.PERMISSION_ADMIN})
-					err = RemoveTeamMember(&m.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], ProtectLastAdmin: true})
+					AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[1], Permission: models.PERMISSION_ADMIN})
+					err = RemoveTeamMember(&models.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], ProtectLastAdmin: true})
 					So(err, ShouldEqual, nil)
 					So(err, ShouldEqual, nil)
 				})
 				})
 
 
 				Convey("A user should not be able to remove the admin permission for the last admin", func() {
 				Convey("A user should not be able to remove the admin permission for the last admin", func() {
-					err = UpdateTeamMember(&m.UpdateTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], Permission: 0, ProtectLastAdmin: true})
-					So(err, ShouldEqual, m.ErrLastTeamAdmin)
+					err = UpdateTeamMember(&models.UpdateTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], Permission: 0, ProtectLastAdmin: true})
+					So(err, ShouldEqual, models.ErrLastTeamAdmin)
 				})
 				})
 
 
 				Convey("A user should be able to remove the admin permission if there are other admins", func() {
 				Convey("A user should be able to remove the admin permission if there are other admins", func() {
-					AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[1], Permission: m.PERMISSION_ADMIN})
-					err = UpdateTeamMember(&m.UpdateTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], Permission: 0, ProtectLastAdmin: true})
+					AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[1], Permission: models.PERMISSION_ADMIN})
+					err = UpdateTeamMember(&models.UpdateTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0], Permission: 0, ProtectLastAdmin: true})
 					So(err, ShouldEqual, nil)
 					So(err, ShouldEqual, nil)
 				})
 				})
 			})
 			})
 
 
 			Convey("Should be able to remove a group with users and permissions", func() {
 			Convey("Should be able to remove a group with users and permissions", func() {
 				groupId := group2.Result.Id
 				groupId := group2.Result.Id
-				err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[1]})
+				err := AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[1]})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-				err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]})
+				err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-				err = testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId})
+				err = testHelperUpdateDashboardAcl(1, models.DashboardAcl{DashboardId: 1, OrgId: testOrgId, Permission: models.PERMISSION_EDIT, TeamId: groupId})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
-				err = DeleteTeam(&m.DeleteTeamCommand{OrgId: testOrgId, Id: groupId})
+				err = DeleteTeam(&models.DeleteTeamCommand{OrgId: testOrgId, Id: groupId})
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
-				query := &m.GetTeamByIdQuery{OrgId: testOrgId, Id: groupId}
+				query := &models.GetTeamByIdQuery{OrgId: testOrgId, Id: groupId}
 				err = GetTeamById(query)
 				err = GetTeamById(query)
-				So(err, ShouldEqual, m.ErrTeamNotFound)
+				So(err, ShouldEqual, models.ErrTeamNotFound)
 
 
-				permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: testOrgId}
+				permQuery := &models.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: testOrgId}
 				err = GetDashboardAclInfoList(permQuery)
 				err = GetDashboardAclInfoList(permQuery)
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 
 
 				So(len(permQuery.Result), ShouldEqual, 0)
 				So(len(permQuery.Result), ShouldEqual, 0)
 			})
 			})
+
+			Convey("Should be able to return if user is admin of teams or not", func() {
+				groupId := group2.Result.Id
+				err := AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
+				So(err, ShouldBeNil)
+				err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[1], Permission: models.PERMISSION_ADMIN})
+				So(err, ShouldBeNil)
+
+				query := &models.IsAdminOfTeamsQuery{SignedInUser: &models.SignedInUser{OrgId: testOrgId, UserId: userIds[0]}}
+				err = IsAdminOfTeams(query)
+				So(err, ShouldBeNil)
+				So(query.Result, ShouldBeFalse)
+
+				query = &models.IsAdminOfTeamsQuery{SignedInUser: &models.SignedInUser{OrgId: testOrgId, UserId: userIds[1]}}
+				err = IsAdminOfTeams(query)
+				So(err, ShouldBeNil)
+				So(query.Result, ShouldBeTrue)
+			})
 		})
 		})
 	})
 	})
 }
 }

+ 2 - 2
public/app/core/components/Select/UserPicker.tsx

@@ -44,12 +44,12 @@ export class UserPicker extends Component<Props, State> {
     }
     }
 
 
     return backendSrv
     return backendSrv
-      .get(`/api/org/users?query=${query}&limit=10`)
+      .get(`/api/org/users/lookup?query=${query}&limit=10`)
       .then((result: any) => {
       .then((result: any) => {
         return result.map((user: any) => ({
         return result.map((user: any) => ({
           id: user.userId,
           id: user.userId,
           value: user.userId,
           value: user.userId,
-          label: user.login === user.email ? user.login : `${user.login} - ${user.email}`,
+          label: user.login,
           imgUrl: user.avatarUrl,
           imgUrl: user.avatarUrl,
           login: user.login,
           login: user.login,
         }));
         }));

+ 1 - 1
public/app/features/alerting/AlertTabCtrl.ts

@@ -71,7 +71,7 @@ export class AlertTabCtrl {
     this.alertNotifications = [];
     this.alertNotifications = [];
     this.alertHistory = [];
     this.alertHistory = [];
 
 
-    return this.backendSrv.get('/api/alert-notifications').then((res: any) => {
+    return this.backendSrv.get('/api/alert-notifications/lookup').then((res: any) => {
       this.notifications = res;
       this.notifications = res;
 
 
       this.initModel();
       this.initModel();

+ 1 - 1
public/app/plugins/panel/gettingstarted/GettingStarted.tsx

@@ -79,7 +79,7 @@ export class GettingStarted extends PureComponent<PanelProps, State> {
         href: 'org/users?gettingstarted',
         href: 'org/users?gettingstarted',
         check: () => {
         check: () => {
           return getBackendSrv()
           return getBackendSrv()
-            .get('/api/org/users')
+            .get('/api/org/users/lookup')
             .then((res: any) => {
             .then((res: any) => {
               return res.length > 1;
               return res.length > 1;
             });
             });