diff --git a/docs/sources/http_api/alerting_notification_channels.md b/docs/sources/http_api/alerting_notification_channels.md index aa6e7297ac2..925dfdfe568 100644 --- a/docs/sources/http_api/alerting_notification_channels.md +++ b/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 /api/alert-notifications/uid/:uid` diff --git a/docs/sources/http_api/org.md b/docs/sources/http_api/org.md index d1eb4ea0972..00bbe4cd363 100644 --- a/docs/sources/http_api/org.md +++ b/docs/sources/http_api/org.md @@ -47,6 +47,9 @@ Content-Type: application/json `GET /api/org/users` +Returns all org users within the current organization. +Accessible to users with org admin role. + **Example Request**: ```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" } ] ``` diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index e5e94326027..ad87fb2abd2 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -6,15 +6,15 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "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/guardian" "github.com/grafana/grafana/pkg/services/search" ) -func ValidateOrgAlert(c *m.ReqContext) { +func ValidateOrgAlert(c *models.ReqContext) { id := c.ParamsInt64(":alertId") - query := m.GetAlertByIdQuery{Id: id} + query := models.GetAlertByIdQuery{Id: id} if err := bus.Dispatch(&query); err != 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") if dashboardID == 0 { return Error(400, "Missing query parameter dashboardId", nil) } - query := m.GetAlertStatesForDashboardQuery{ + query := models.GetAlertStatesForDashboardQuery{ OrgId: c.OrgId, DashboardId: c.QueryInt64("dashboardId"), } @@ -47,7 +47,7 @@ func GetAlertStatesForDashboard(c *m.ReqContext) Response { } // GET /api/alerts -func GetAlerts(c *m.ReqContext) Response { +func GetAlerts(c *models.ReqContext) Response { dashboardQuery := c.Query("dashboardQuery") dashboardTags := c.QueryStrings("dashboardTag") stringDashboardIDs := c.QueryStrings("dashboardId") @@ -79,7 +79,7 @@ func GetAlerts(c *m.ReqContext) Response { DashboardIds: dashboardIDs, Type: string(search.DashHitDB), FolderIds: folderIDs, - Permission: m.PERMISSION_VIEW, + Permission: models.PERMISSION_VIEW, } 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 len(dashboardIDs) == 0 { - return JSON(200, []*m.AlertListItemDTO{}) + return JSON(200, []*models.AlertListItemDTO{}) } } - query := m.GetAlertsQuery{ + query := models.GetAlertsQuery{ OrgId: c.OrgId, DashboardIDs: dashboardIDs, PanelId: c.QueryInt64("panelId"), @@ -118,14 +118,14 @@ func GetAlerts(c *m.ReqContext) Response { } 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) } // 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 { 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 { return Error(422, validationErr.Error(), nil) } - if err == m.ErrDataSourceAccessDenied { + if err == models.ErrDataSourceAccessDenied { return Error(403, "Access denied to datasource", 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 -func GetAlert(c *m.ReqContext) Response { +func GetAlert(c *models.ReqContext) Response { id := c.ParamsInt64(":alertId") - query := m.GetAlertByIdQuery{Id: id} + query := models.GetAlertByIdQuery{Id: id} if err := bus.Dispatch(&query); err != nil { return Error(500, "List alerts failed", err) @@ -182,28 +182,52 @@ func GetAlert(c *m.ReqContext) Response { return JSON(200, &query.Result) } -func GetAlertNotifiers(c *m.ReqContext) Response { +func GetAlertNotifiers(c *models.ReqContext) Response { 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) } result := make([]*dtos.AlertNotification, 0) - for _, notification := range query.Result { + for _, notification := range alertNotifications { result = append(result, dtos.NewAlertNotification(notification)) } 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, Id: c.ParamsInt64("notificationId"), } @@ -223,8 +247,8 @@ func GetAlertNotificationByID(c *m.ReqContext) Response { 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, Uid: c.Params("uid"), } @@ -244,7 +268,7 @@ func GetAlertNotificationByUID(c *m.ReqContext) Response { 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 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)) } -func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationCommand) Response { +func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) Response { cmd.OrgId = c.OrgId 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)) } -func UpdateAlertNotificationByUID(c *m.ReqContext, cmd m.UpdateAlertNotificationWithUidCommand) Response { +func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) Response { cmd.OrgId = c.OrgId cmd.Uid = c.Params("uid") @@ -283,8 +307,8 @@ func UpdateAlertNotificationByUID(c *m.ReqContext, cmd m.UpdateAlertNotification 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, Id: c.ParamsInt64("notificationId"), } @@ -296,8 +320,8 @@ func DeleteAlertNotification(c *m.ReqContext) Response { 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, Uid: c.Params("uid"), } @@ -310,7 +334,7 @@ func DeleteAlertNotificationByUID(c *m.ReqContext) Response { } //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{ Name: dto.Name, 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 == m.ErrSmtpNotEnabled { + if err == models.ErrSmtpNotEnabled { return Error(412, err.Error(), 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 -func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response { +func PauseAlert(c *models.ReqContext, dto dtos.PauseAlertCommand) Response { alertID := c.ParamsInt64("alertId") - query := m.GetAlertByIdQuery{Id: alertID} + query := models.GetAlertByIdQuery{Id: alertID} if err := bus.Dispatch(&query); err != nil { 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) } - cmd := m.PauseAlertCommand{ + cmd := models.PauseAlertCommand{ OrgId: c.OrgId, AlertIds: []int64{alertID}, Paused: dto.Paused, @@ -356,10 +380,10 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response { return Error(500, "", err) } - var response m.AlertStateType = m.AlertStateUnknown + var response models.AlertStateType = models.AlertStateUnknown pausedState := "un-paused" if cmd.Paused { - response = m.AlertStatePaused + response = models.AlertStatePaused pausedState = "paused" } @@ -373,8 +397,8 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response { } //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, } @@ -382,10 +406,10 @@ func PauseAllAlerts(c *m.ReqContext, dto dtos.PauseAllAlertsCommand) Response { return Error(500, "Failed to pause alerts", err) } - var response m.AlertStateType = m.AlertStatePending + var response models.AlertStateType = models.AlertStatePending pausedState := "un paused" if updateCmd.Paused { - response = m.AlertStatePaused + response = models.AlertStatePaused pausedState = "paused" } diff --git a/pkg/api/alerting_test.go b/pkg/api/alerting_test.go index 168193e377f..11d309b299a 100644 --- a/pkg/api/alerting_test.go +++ b/pkg/api/alerting_test.go @@ -5,7 +5,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "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/smartystreets/goconvey/convey" @@ -14,24 +14,24 @@ import ( func TestAlertingApiEndpoint(t *testing.T) { 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 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 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 }) @@ -41,7 +41,7 @@ func TestAlertingApiEndpoint(t *testing.T) { AlertId: 1, 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) 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() { - 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() { @@ -59,22 +59,22 @@ func TestAlertingApiEndpoint(t *testing.T) { AlertId: 1, 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) 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 bus.AddHandler("test", func(query *search.Query) error { searchQuery = query 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 return nil }) @@ -86,7 +86,7 @@ func TestAlertingApiEndpoint(t *testing.T) { 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 bus.AddHandler("test", func(query *search.Query) error { searchQuery = query @@ -97,8 +97,8 @@ func TestAlertingApiEndpoint(t *testing.T) { 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 return nil }) @@ -120,7 +120,7 @@ func TestAlertingApiEndpoint(t *testing.T) { 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.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() So(sc.resp.Code, ShouldEqual, 404) @@ -129,19 +129,19 @@ func TestAlertingApiEndpoint(t *testing.T) { } func CallPauseAlert(sc *scenarioContext) { - bus.AddHandler("test", func(cmd *m.PauseAlertCommand) error { + bus.AddHandler("test", func(cmd *models.PauseAlertCommand) error { return nil }) 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() { defer bus.ClearBusHandlers() sc := setupScenarioContext(url) - sc.defaultHandler = Wrap(func(c *m.ReqContext) Response { + sc.defaultHandler = Wrap(func(c *models.ReqContext) Response { sc.context = c sc.context.UserId = TestUserID sc.context.OrgId = TestOrgID diff --git a/pkg/api/api.go b/pkg/api/api.go index 27f68f90f01..dbb72f9ba3c 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -183,6 +183,7 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Group("/org", func(orgRoute routing.RouteRegister) { orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrgCurrent)) orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddressCurrent)) + orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg)) orgRoute.Post("/users", quota("user"), bind(models.AddOrgUserCommand{}), Wrap(AddOrgUserToCurrentOrg)) orgRoute.Patch("/users/:userId", bind(models.UpdateOrgUserCommand{}), Wrap(UpdateOrgUserForCurrentOrg)) orgRoute.Delete("/users/:userId", Wrap(RemoveOrgUserForCurrentOrg)) @@ -199,7 +200,7 @@ func (hs *HTTPServer) registerRoutes() { // current org without requirement of user to be org admin apiRoute.Group("/org", func(orgRoute routing.RouteRegister) { - orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg)) + orgRoute.Get("/users/lookup", Wrap(GetOrgUsersForCurrentOrgLookup)) }) // create new org @@ -343,10 +344,10 @@ func (hs *HTTPServer) registerRoutes() { 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) { + alertNotifications.Get("/", Wrap(GetAlertNotifications)) alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), Wrap(NotificationTest)) alertNotifications.Post("/", bind(models.CreateAlertNotificationCommand{}), Wrap(CreateAlertNotification)) alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), Wrap(UpdateAlertNotification)) @@ -357,6 +358,11 @@ func (hs *HTTPServer) registerRoutes() { alertNotifications.Delete("/uid/:uid", Wrap(DeleteAlertNotificationByUID)) }, 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.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), Wrap(DeleteAnnotations)) diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index dcdc3976ec5..2ad95dc59b6 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -77,6 +77,24 @@ type AlertNotification struct { 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 { Dashboard *simplejson.Json `json:"dashboard" binding:"Required"` PanelId int64 `json:"panelId" binding:"Required"` diff --git a/pkg/api/dtos/user.go b/pkg/api/dtos/user.go index 93d3074f66f..d6a58a98d71 100644 --- a/pkg/api/dtos/user.go +++ b/pkg/api/dtos/user.go @@ -50,3 +50,9 @@ type ResetUserPasswordForm struct { NewPassword string `json:"newPassword"` ConfirmPassword string `json:"confirmPassword"` } + +type UserLookupDTO struct { + UserID int64 `json:"userId"` + Login string `json:"login"` + AvatarURL string `json:"avatarUrl"` +} diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go index e750662c764..9cf6328bb62 100644 --- a/pkg/api/org_users.go +++ b/pkg/api/org_users.go @@ -3,27 +3,27 @@ package api import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" - m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/models" ) // 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 return addOrgUserHelper(cmd) } // 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") return addOrgUserHelper(cmd) } -func addOrgUserHelper(cmd m.AddOrgUserCommand) Response { +func addOrgUserHelper(cmd models.AddOrgUserCommand) Response { if !cmd.Role.IsValid() { return Error(400, "Invalid role specified", nil) } - userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail} + userQuery := models.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail} err := bus.Dispatch(&userQuery) if err != nil { return Error(404, "User not found", nil) @@ -34,7 +34,7 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response { cmd.UserId = userToAdd.Id 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(500, "Could not add user to organization", err) @@ -44,54 +44,115 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response { } // 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 -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, Query: query, Limit: limit, } if err := bus.Dispatch(&q); err != nil { - return Error(500, "Failed to get account user", err) + return nil, err } for _, user := range q.Result { user.AvatarUrl = dtos.GetGravatarUrl(user.Email) } - return JSON(200, q.Result) + return q.Result, nil } // 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.UserId = c.ParamsInt64(":userId") return updateOrgUserHelper(cmd) } // 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.UserId = c.ParamsInt64(":userId") return updateOrgUserHelper(cmd) } -func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response { +func updateOrgUserHelper(cmd models.UpdateOrgUserCommand) Response { if !cmd.Role.IsValid() { return Error(400, "Invalid role specified", 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(500, "Failed update org user", err) @@ -101,8 +162,8 @@ func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response { } // 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"), OrgId: c.OrgId, ShouldDeleteOrphanedUser: true, @@ -110,16 +171,16 @@ func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response { } // 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"), 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 == m.ErrLastOrgAdmin { + if err == models.ErrLastOrgAdmin { return Error(400, "Cannot remove last organization admin", nil) } return Error(500, "Failed to remove user from organization", err) diff --git a/pkg/models/folders.go b/pkg/models/folders.go index f4dd7e5b776..b48b7f9e83f 100644 --- a/pkg/models/folders.go +++ b/pkg/models/folders.go @@ -98,3 +98,8 @@ type HasEditPermissionInFoldersQuery struct { SignedInUser *SignedInUser Result bool } + +type HasAdminPermissionInFoldersQuery struct { + SignedInUser *SignedInUser + Result bool +} diff --git a/pkg/models/team.go b/pkg/models/team.go index bc8cbba8100..5c5a19f8db4 100644 --- a/pkg/models/team.go +++ b/pkg/models/team.go @@ -88,3 +88,8 @@ type SearchTeamQueryResult struct { Page int `json:"page"` PerPage int `json:"perPage"` } + +type IsAdminOfTeamsQuery struct { + SignedInUser *SignedInUser + Result bool +} diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index 9c5f0148b5e..6d30003a1aa 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -6,7 +6,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "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/util" ) @@ -25,17 +25,18 @@ func init() { bus.AddHandler("sql", GetDashboardsBySlug) bus.AddHandler("sql", ValidateDashboardBeforeSave) bus.AddHandler("sql", HasEditPermissionInFolders) + bus.AddHandler("sql", HasAdminPermissionInFolders) } 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 saveDashboard(sess, cmd) }) } -func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error { +func saveDashboard(sess *DBSession, cmd *models.SaveDashboardCommand) error { dash := cmd.GetDashboardModel() userId := cmd.UserId @@ -45,13 +46,13 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error { } 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) if err != nil { return err } if !dashWithIdExists { - return m.ErrDashboardNotFound + return models.ErrDashboardNotFound } // check for is someone else has written in between @@ -59,13 +60,13 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error { if cmd.Overwrite { dash.SetVersion(existing.Version) } else { - return m.ErrDashboardVersionMismatch + return models.ErrDashboardVersionMismatch } } // do not allow plugin dashboard updates without overwrite flag 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 { - return m.ErrDashboardNotFound + return models.ErrDashboardNotFound } - dashVersion := &m.DashboardVersion{ + dashVersion := &models.DashboardVersion{ DashboardId: dash.Id, ParentVersion: parentVersion, RestoredFrom: cmd.RestoredFrom, @@ -126,7 +127,7 @@ func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error { if affectedRows, err = sess.Insert(dashVersion); err != nil { return err } else if affectedRows == 0 { - return m.ErrDashboardNotFound + return models.ErrDashboardNotFound } // delete existing tags @@ -154,7 +155,7 @@ func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) { for i := 0; i < 3; i++ { 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 { 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) if err != nil { return err } else if !has { - return m.ErrDashboardNotFound + return models.ErrDashboardNotFound } dashboard.SetId(dashboard.Id) @@ -262,7 +263,7 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard Uid: item.Uid, Title: item.Title, 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), FolderId: item.FolderId, FolderUid: item.FolderUid, @@ -271,7 +272,7 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard } 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) @@ -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 COUNT(*) as count, term @@ -293,20 +294,20 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error { GROUP BY term ORDER BY term` - query.Result = make([]*m.DashboardTagCloudItem, 0) + query.Result = make([]*models.DashboardTagCloudItem, 0) sess := x.SQL(sql, query.OrgId) err := sess.Find(&query.Result) return err } -func DeleteDashboard(cmd *m.DeleteDashboardCommand) error { +func DeleteDashboard(cmd *models.DeleteDashboardCommand) 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) if err != nil { return err } else if !has { - return m.ErrDashboardNotFound + return models.ErrDashboardNotFound } 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 { - 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) 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) // 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 { - 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 { - permissions = append(permissions, &m.DashboardPermissionForUser{ + permissions = append(permissions, &models.DashboardPermissionForUser{ DashboardId: d, - Permission: m.PERMISSION_ADMIN, - PermissionName: m.PERMISSION_ADMIN.String(), + Permission: models.PERMISSION_ADMIN, + PermissionName: models.PERMISSION_ADMIN.String(), }) } query.Result = permissions @@ -436,8 +437,8 @@ func GetDashboardPermissionsForUser(query *m.GetDashboardPermissionsForUserQuery 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) err := x.Where(whereExpr, query.OrgId, query.PluginId).Find(&dashboards) @@ -449,7 +450,7 @@ type DashboardSlugDTO struct { Slug string } -func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error { +func GetDashboardSlugById(query *models.GetDashboardSlugByIdQuery) error { var rawSql = `SELECT slug from dashboard WHERE Id=?` var slug = DashboardSlugDTO{} @@ -458,15 +459,15 @@ func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error { if err != nil { return err } else if !exists { - return m.ErrDashboardNotFound + return models.ErrDashboardNotFound } query.Result = slug.Slug 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 { return err @@ -476,28 +477,28 @@ func GetDashboardsBySlug(query *m.GetDashboardsBySlugQuery) error { return nil } -func GetDashboardUIDById(query *m.GetDashboardRefByIdQuery) error { +func GetDashboardUIDById(query *models.GetDashboardRefByIdQuery) error { var rawSql = `SELECT uid, slug from dashboard WHERE Id=?` - us := &m.DashboardRef{} + us := &models.DashboardRef{} exists, err := x.SQL(rawSql, query.Id).Get(us) if err != nil { return err } else if !exists { - return m.ErrDashboardNotFound + return models.ErrDashboardNotFound } query.Result = us return nil } -func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) (err error) { +func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *models.ValidateDashboardBeforeSaveCommand) (err error) { dash := cmd.Dashboard dashWithIdExists := false - var existingById m.Dashboard + var existingById models.Dashboard if dash.Id > 0 { 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 { - return m.ErrDashboardNotFound + return models.ErrDashboardNotFound } if dash.Uid == "" { @@ -515,7 +516,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash } dashWithUidExists := false - var existingByUid m.Dashboard + var existingByUid models.Dashboard if dash.Uid != "" { 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 { - 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) if folderErr != nil { return folderErr } 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 { - return m.ErrDashboardWithSameUIDExists + return models.ErrDashboardWithSameUIDExists } existing := existingById @@ -558,7 +559,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash if (existing.IsFolder && !dash.IsFolder) || (!existing.IsFolder && dash.IsFolder) { - return m.ErrDashboardTypeMismatch + return models.ErrDashboardTypeMismatch } if !dash.IsFolder && dash.FolderId != existing.FolderId { @@ -570,21 +571,21 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash if cmd.Overwrite { dash.SetVersion(existing.Version) } else { - return m.ErrDashboardVersionMismatch + return models.ErrDashboardVersionMismatch } } // do not allow plugin dashboard updates without overwrite flag if existing.PluginId != "" && !cmd.Overwrite { - return m.UpdatePluginDashboardError{PluginId: existing.PluginId} + return models.UpdatePluginDashboardError{PluginId: existing.PluginId} } return nil } -func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) error { +func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *models.ValidateDashboardBeforeSaveCommand) error { 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) if err != nil { @@ -593,11 +594,11 @@ func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashbo if exists && dash.Id != existing.Id { if existing.IsFolder && !dash.IsFolder { - return m.ErrDashboardWithSameNameAsFolder + return models.ErrDashboardWithSameNameAsFolder } if !existing.IsFolder && dash.IsFolder { - return m.ErrDashboardFolderWithSameNameAsDashboard + return models.ErrDashboardFolderWithSameNameAsDashboard } 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.SetVersion(existing.Version) } else { - return m.ErrDashboardWithSameNameInFolderExists + return models.ErrDashboardWithSameNameInFolderExists } } 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 { if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil { 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 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, 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 { Count int64 diff --git a/pkg/services/sqlstore/dashboard_folder_test.go b/pkg/services/sqlstore/dashboard_folder_test.go index cdd107c3e90..33b6c566f54 100644 --- a/pkg/services/sqlstore/dashboard_folder_test.go +++ b/pkg/services/sqlstore/dashboard_folder_test.go @@ -5,7 +5,7 @@ import ( . "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" ) @@ -24,7 +24,7 @@ func TestDashboardFolderDataAccess(t *testing.T) { Convey("and no acls are set", func() { Convey("should return all dashboards", func() { 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}, } @@ -38,11 +38,11 @@ func TestDashboardFolderDataAccess(t *testing.T) { Convey("and acl is set for dashboard folder", func() { 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() { 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}, } err := SearchDashboards(query) @@ -53,11 +53,11 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) 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() { 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}, } @@ -72,10 +72,10 @@ func TestDashboardFolderDataAccess(t *testing.T) { Convey("when the user is an admin", func() { Convey("should be able to access folder", func() { query := &search.FindPersistedDashboardsQuery{ - SignedInUser: &m.SignedInUser{ + SignedInUser: &models.SignedInUser{ UserId: currentUser.Id, OrgId: 1, - OrgRole: m.ROLE_ADMIN, + OrgRole: models.ROLE_ADMIN, }, OrgId: 1, 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() { var otherUser int64 = 999 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() { - 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) So(err, ShouldBeNil) 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() { - 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() { - 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) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 2) @@ -118,10 +118,10 @@ func TestDashboardFolderDataAccess(t *testing.T) { Convey("when the user is an admin", func() { Convey("should be able to search for child dash and folder", func() { query := &search.FindPersistedDashboardsQuery{ - SignedInUser: &m.SignedInUser{ + SignedInUser: &models.SignedInUser{ UserId: currentUser.Id, OrgId: 1, - OrgRole: m.ROLE_ADMIN, + OrgRole: models.ROLE_ADMIN, }, OrgId: 1, 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("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) So(err, ShouldBeNil) 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() { 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() { moveDashboard(1, childDash2.Data, folder1.Id) Convey("should not return folder with acl or its children", func() { 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{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() { 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{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() { - 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) Convey("should return folder without acl but not the dashboard with acl", func() { 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{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() { query := search.FindPersistedDashboardsQuery{ 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", } @@ -247,11 +247,11 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) Convey("should have write access to all folders and dashboards", func() { - query := m.GetDashboardPermissionsForUserQuery{ + query := models.GetDashboardPermissionsForUserQuery{ DashboardIds: []int64{folder1.Id, folder2.Id}, OrgId: 1, UserId: adminUser.Id, - OrgRole: m.ROLE_ADMIN, + OrgRole: models.ROLE_ADMIN, } err := GetDashboardPermissionsForUser(&query) @@ -259,26 +259,35 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(len(query.Result), ShouldEqual, 2) 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].Permission, ShouldEqual, m.PERMISSION_ADMIN) + So(query.Result[1].Permission, ShouldEqual, models.PERMISSION_ADMIN) }) 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) So(err, ShouldBeNil) 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() { query := search.FindPersistedDashboardsQuery{ 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() { @@ -291,11 +300,11 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) Convey("should have edit access to folders with default ACL", func() { - query := m.GetDashboardPermissionsForUserQuery{ + query := models.GetDashboardPermissionsForUserQuery{ DashboardIds: []int64{folder1.Id, folder2.Id}, OrgId: 1, UserId: editorUser.Id, - OrgRole: m.ROLE_EDITOR, + OrgRole: models.ROLE_EDITOR, } err := GetDashboardPermissionsForUser(&query) @@ -303,13 +312,13 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(len(query.Result), ShouldEqual, 2) 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].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() { - 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) So(err, ShouldBeNil) @@ -319,20 +328,29 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) 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) So(err, ShouldBeNil) 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() { query := search.FindPersistedDashboardsQuery{ 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() { @@ -343,11 +361,11 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) Convey("should have view access to folders with default ACL", func() { - query := m.GetDashboardPermissionsForUserQuery{ + query := models.GetDashboardPermissionsForUserQuery{ DashboardIds: []int64{folder1.Id, folder2.Id}, OrgId: 1, UserId: viewerUser.Id, - OrgRole: m.ROLE_VIEWER, + OrgRole: models.ROLE_VIEWER, } err := GetDashboardPermissionsForUser(&query) @@ -355,13 +373,13 @@ func TestDashboardFolderDataAccess(t *testing.T) { So(len(query.Result), ShouldEqual, 2) 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].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() { - 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) So(err, ShouldBeNil) @@ -371,20 +389,29 @@ func TestDashboardFolderDataAccess(t *testing.T) { }) 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) So(err, ShouldBeNil) 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() { - 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() { - 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) 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() { - 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() { - 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) So(err, ShouldBeNil) diff --git a/pkg/services/sqlstore/datasource_test.go b/pkg/services/sqlstore/datasource_test.go index 90300e20029..32555c958d7 100644 --- a/pkg/services/sqlstore/datasource_test.go +++ b/pkg/services/sqlstore/datasource_test.go @@ -5,7 +5,7 @@ import ( . "github.com/smartystreets/goconvey/convey" - m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/models" ) type Test struct { @@ -17,11 +17,11 @@ func TestDataAccess(t *testing.T) { Convey("Testing DB", t, func() { InitTestDB(t) Convey("Can add datasource", func() { - err := AddDataSource(&m.AddDataSourceCommand{ + err := AddDataSource(&models.AddDataSourceCommand{ OrgId: 10, Name: "laban", - Type: m.DS_INFLUXDB, - Access: m.DS_ACCESS_DIRECT, + Type: models.DS_INFLUXDB, + Access: models.DS_ACCESS_DIRECT, Url: "http://test", Database: "site", ReadOnly: true, @@ -29,7 +29,7 @@ func TestDataAccess(t *testing.T) { So(err, ShouldBeNil) - query := m.GetDataSourcesQuery{OrgId: 10} + query := models.GetDataSourcesQuery{OrgId: 10} err = GetDataSources(&query) So(err, ShouldBeNil) @@ -43,28 +43,28 @@ func TestDataAccess(t *testing.T) { }) Convey("Given a datasource", func() { - err := AddDataSource(&m.AddDataSourceCommand{ + err := AddDataSource(&models.AddDataSourceCommand{ OrgId: 10, Name: "nisse", - Type: m.DS_GRAPHITE, - Access: m.DS_ACCESS_DIRECT, + Type: models.DS_GRAPHITE, + Access: models.DS_ACCESS_DIRECT, Url: "http://test", }) So(err, ShouldBeNil) - query := m.GetDataSourcesQuery{OrgId: 10} + query := models.GetDataSourcesQuery{OrgId: 10} err = GetDataSources(&query) So(err, ShouldBeNil) ds := query.Result[0] Convey(" updated ", func() { - cmd := &m.UpdateDataSourceCommand{ + cmd := &models.UpdateDataSourceCommand{ Id: ds.Id, OrgId: 10, Name: "nisse", - Type: m.DS_GRAPHITE, - Access: m.DS_ACCESS_PROXY, + Type: models.DS_GRAPHITE, + Access: models.DS_ACCESS_PROXY, Url: "http://test", Version: ds.Version, } @@ -75,27 +75,27 @@ func TestDataAccess(t *testing.T) { }) Convey("when someone else updated between read and update", func() { - query := m.GetDataSourcesQuery{OrgId: 10} + query := models.GetDataSourcesQuery{OrgId: 10} err = GetDataSources(&query) So(err, ShouldBeNil) ds := query.Result[0] - intendedUpdate := &m.UpdateDataSourceCommand{ + intendedUpdate := &models.UpdateDataSourceCommand{ Id: ds.Id, OrgId: 10, Name: "nisse", - Type: m.DS_GRAPHITE, - Access: m.DS_ACCESS_PROXY, + Type: models.DS_GRAPHITE, + Access: models.DS_ACCESS_PROXY, Url: "http://test", Version: ds.Version, } - updateFromOtherUser := &m.UpdateDataSourceCommand{ + updateFromOtherUser := &models.UpdateDataSourceCommand{ Id: ds.Id, OrgId: 10, Name: "nisse", - Type: m.DS_GRAPHITE, - Access: m.DS_ACCESS_PROXY, + Type: models.DS_GRAPHITE, + Access: models.DS_ACCESS_PROXY, Url: "http://test", Version: ds.Version, } @@ -108,12 +108,12 @@ func TestDataAccess(t *testing.T) { }) Convey("updating datasource without version", func() { - cmd := &m.UpdateDataSourceCommand{ + cmd := &models.UpdateDataSourceCommand{ Id: ds.Id, OrgId: 10, Name: "nisse", - Type: m.DS_GRAPHITE, - Access: m.DS_ACCESS_PROXY, + Type: models.DS_GRAPHITE, + Access: models.DS_ACCESS_PROXY, Url: "http://test", } @@ -124,12 +124,12 @@ func TestDataAccess(t *testing.T) { }) Convey("updating datasource without higher version", func() { - cmd := &m.UpdateDataSourceCommand{ + cmd := &models.UpdateDataSourceCommand{ Id: ds.Id, OrgId: 10, Name: "nisse", - Type: m.DS_GRAPHITE, - Access: m.DS_ACCESS_PROXY, + Type: models.DS_GRAPHITE, + Access: models.DS_ACCESS_PROXY, Url: "http://test", Version: 90000, } @@ -142,7 +142,7 @@ func TestDataAccess(t *testing.T) { }) 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) GetDataSources(&query) @@ -150,7 +150,7 @@ func TestDataAccess(t *testing.T) { }) 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) GetDataSources(&query) @@ -158,7 +158,7 @@ func TestDataAccess(t *testing.T) { }) 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) GetDataSources(&query) diff --git a/pkg/services/sqlstore/team.go b/pkg/services/sqlstore/team.go index 460eb592598..7697a59ba90 100644 --- a/pkg/services/sqlstore/team.go +++ b/pkg/services/sqlstore/team.go @@ -6,7 +6,7 @@ import ( "time" "github.com/grafana/grafana/pkg/bus" - m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/models" ) func init() { @@ -21,6 +21,7 @@ func init() { bus.AddHandler("sql", UpdateTeamMember) bus.AddHandler("sql", RemoveTeamMember) bus.AddHandler("sql", GetTeamMembers) + bus.AddHandler("sql", IsAdminOfTeams) } func getTeamSearchSqlBase() string { @@ -45,16 +46,16 @@ func getTeamSelectSqlBase() string { FROM team as team ` } -func CreateTeam(cmd *m.CreateTeamCommand) error { +func CreateTeam(cmd *models.CreateTeamCommand) error { return inTransaction(func(sess *DBSession) error { if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, 0, sess); err != nil { return err } else if isNameTaken { - return m.ErrTeamNameTaken + return models.ErrTeamNameTaken } - team := m.Team{ + team := models.Team{ Name: cmd.Name, Email: cmd.Email, 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 { if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, cmd.Id, sess); err != nil { return err } else if isNameTaken { - return m.ErrTeamNameTaken + return models.ErrTeamNameTaken } - team := m.Team{ + team := models.Team{ Name: cmd.Name, Email: cmd.Email, Updated: time.Now(), @@ -94,7 +95,7 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error { } if affectedRows == 0 { - return m.ErrTeamNotFound + return models.ErrTeamNotFound } 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 -func DeleteTeam(cmd *m.DeleteTeamCommand) error { +func DeleteTeam(cmd *models.DeleteTeamCommand) error { return inTransaction(func(sess *DBSession) error { if _, err := teamExists(cmd.OrgId, cmd.Id, sess); err != nil { 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 { return false, err } else if len(res) != 1 { - return false, m.ErrTeamNotFound + return false, models.ErrTeamNotFound } return true, nil } 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) if err != nil { @@ -149,9 +150,9 @@ func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession 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 + "%" @@ -189,7 +190,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error { return err } - team := m.Team{} + team := models.Team{} countSess := x.Table("team") if query.Query != "" { countSess.Where(`name `+dialect.LikeStr()+` ?`, queryWithWildcards) @@ -205,13 +206,13 @@ func SearchTeams(query *m.SearchTeamsQuery) error { return err } -func GetTeamById(query *m.GetTeamByIdQuery) error { +func GetTeamById(query *models.GetTeamByIdQuery) error { var sql bytes.Buffer sql.WriteString(getTeamSelectSqlBase()) 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) if err != nil { @@ -219,7 +220,7 @@ func GetTeamById(query *m.GetTeamByIdQuery) error { } if !exists { - return m.ErrTeamNotFound + return models.ErrTeamNotFound } query.Result = &team @@ -227,8 +228,8 @@ func GetTeamById(query *m.GetTeamByIdQuery) error { } // 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 @@ -241,19 +242,19 @@ func GetTeamsByUser(query *m.GetTeamsByUserQuery) error { } // 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 { 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 } else if len(res) == 1 { - return m.ErrTeamMemberAlreadyAdded + return models.ErrTeamMemberAlreadyAdded } if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil { return err } - entity := m.TeamMember{ + entity := models.TeamMember{ OrgId: cmd.OrgId, TeamId: cmd.TeamId, 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=?` - var member m.TeamMember + var member models.TeamMember exists, err := sess.SQL(rawSql, orgId, teamId, userId).Get(&member) if err != nil { return member, err } if !exists { - return member, m.ErrTeamMemberNotFound + return member, models.ErrTeamMemberNotFound } return member, nil } // UpdateTeamMember updates a team member -func UpdateTeamMember(cmd *m.UpdateTeamMemberCommand) error { +func UpdateTeamMember(cmd *models.UpdateTeamMemberCommand) error { return inTransaction(func(sess *DBSession) error { member, err := getTeamMember(sess, cmd.OrgId, cmd.TeamId, cmd.UserId) 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 } @@ -310,7 +311,7 @@ func UpdateTeamMember(cmd *m.UpdateTeamMemberCommand) error { } // 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 { if _, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil { return err @@ -330,7 +331,7 @@ func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error { } rows, err := res.RowsAffected() if rows == 0 { - return m.ErrTeamMemberNotFound + return models.ErrTeamMemberNotFound } return err @@ -340,7 +341,7 @@ func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) 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=?" 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 { return false, err } @@ -354,15 +355,15 @@ func isLastAdmin(sess *DBSession, orgId int64, teamId int64, userId int64) (bool } if isAdmin && len(userIds) == 1 { - return true, m.ErrLastTeamAdmin + return true, models.ErrLastTeamAdmin } return false, err } // 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.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) 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 +} diff --git a/pkg/services/sqlstore/team_test.go b/pkg/services/sqlstore/team_test.go index c407fd9a8d4..e6bf281af43 100644 --- a/pkg/services/sqlstore/team_test.go +++ b/pkg/services/sqlstore/team_test.go @@ -7,7 +7,7 @@ import ( . "github.com/smartystreets/goconvey/convey" - m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/models" ) func TestTeamCommandsAndQueries(t *testing.T) { @@ -18,7 +18,7 @@ func TestTeamCommandsAndQueries(t *testing.T) { Convey("Given saved users and two teams", func() { var userIds []int64 for i := 0; i < 5; i++ { - userCmd := &m.CreateUserCommand{ + userCmd := &models.CreateUserCommand{ Email: fmt.Sprint("user", i, "@test.com"), Name: fmt.Sprint("user", i), Login: fmt.Sprint("loginuser", i), @@ -29,8 +29,8 @@ func TestTeamCommandsAndQueries(t *testing.T) { } 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) So(err, ShouldBeNil) @@ -38,7 +38,7 @@ func TestTeamCommandsAndQueries(t *testing.T) { So(err, ShouldBeNil) 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) So(err, ShouldBeNil) So(query.Page, ShouldEqual, 1) @@ -48,12 +48,12 @@ func TestTeamCommandsAndQueries(t *testing.T) { So(team1.Email, ShouldEqual, "test1@test.com") 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) - 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) - q1 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id} + q1 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id} err = GetTeamMembers(q1) So(err, ShouldBeNil) 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].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) So(err, ShouldBeNil) 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() { 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) - 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) So(err, ShouldBeNil) So(teamQuery.Page, ShouldEqual, 1) 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) - memberQuery := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true} + memberQuery := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id, External: true} err = GetTeamMembers(memberQuery) So(err, ShouldBeNil) 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() { userId := userIds[0] 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) So(err, ShouldBeNil) - qBeforeUpdate := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} + qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} err = GetTeamMembers(qBeforeUpdate) So(err, ShouldBeNil) So(qBeforeUpdate.Result[0].Permission, ShouldEqual, 0) - err = UpdateTeamMember(&m.UpdateTeamMemberCommand{ + err = UpdateTeamMember(&models.UpdateTeamMemberCommand{ UserId: userId, OrgId: testOrgId, TeamId: team.Id, - Permission: m.PERMISSION_ADMIN, + Permission: models.PERMISSION_ADMIN, }) So(err, ShouldBeNil) - qAfterUpdate := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} + qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} err = GetTeamMembers(qAfterUpdate) 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() { userID := userIds[0] 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) So(err, ShouldBeNil) - qBeforeUpdate := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} + qBeforeUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} err = GetTeamMembers(qBeforeUpdate) So(err, ShouldBeNil) 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, OrgId: testOrgId, TeamId: team.Id, @@ -150,31 +150,31 @@ func TestTeamCommandsAndQueries(t *testing.T) { So(err, ShouldBeNil) - qAfterUpdate := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} + qAfterUpdate := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team.Id} err = GetTeamMembers(qAfterUpdate) So(err, ShouldBeNil) So(qAfterUpdate.Result[0].Permission, ShouldEqual, 0) }) Convey("Shouldn't be able to update a user not in the team.", func() { - err = UpdateTeamMember(&m.UpdateTeamMemberCommand{ + err = UpdateTeamMember(&models.UpdateTeamMemberCommand{ UserId: 1, OrgId: testOrgId, 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() { - query := &m.SearchTeamsQuery{OrgId: testOrgId, Query: "group", Page: 1} + query := &models.SearchTeamsQuery{OrgId: testOrgId, Query: "group", Page: 1} err = SearchTeams(query) So(err, ShouldBeNil) So(len(query.Result.Teams), ShouldEqual, 2) So(query.Result.TotalCount, ShouldEqual, 2) - query2 := &m.SearchTeamsQuery{OrgId: testOrgId, Query: ""} + query2 := &models.SearchTeamsQuery{OrgId: testOrgId, Query: ""} err = SearchTeams(query2) So(err, ShouldBeNil) 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() { 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) - query := &m.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]} + query := &models.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]} err = GetTeamsByUser(query) So(err, ShouldBeNil) 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() { - 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) - 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) - q2 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: group1.Result.Id} + q2 := &models.GetTeamMembersQuery{OrgId: testOrgId, TeamId: group1.Result.Id} err = GetTeamMembers(q2) So(err, ShouldBeNil) So(len(q2.Result), ShouldEqual, 0) }) 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) 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() { - 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) }) 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() { - 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) }) }) Convey("Should be able to remove a group with users and permissions", func() { 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) - 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) - 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) - err = DeleteTeam(&m.DeleteTeamCommand{OrgId: testOrgId, Id: groupId}) + err = DeleteTeam(&models.DeleteTeamCommand{OrgId: testOrgId, Id: groupId}) So(err, ShouldBeNil) - query := &m.GetTeamByIdQuery{OrgId: testOrgId, Id: groupId} + query := &models.GetTeamByIdQuery{OrgId: testOrgId, Id: groupId} 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) So(err, ShouldBeNil) 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) + }) }) }) } diff --git a/public/app/core/components/Select/UserPicker.tsx b/public/app/core/components/Select/UserPicker.tsx index ba054a20620..5f8a96f65cd 100644 --- a/public/app/core/components/Select/UserPicker.tsx +++ b/public/app/core/components/Select/UserPicker.tsx @@ -44,12 +44,12 @@ export class UserPicker extends Component { } return backendSrv - .get(`/api/org/users?query=${query}&limit=10`) + .get(`/api/org/users/lookup?query=${query}&limit=10`) .then((result: any) => { return result.map((user: any) => ({ id: user.userId, value: user.userId, - label: user.login === user.email ? user.login : `${user.login} - ${user.email}`, + label: user.login, imgUrl: user.avatarUrl, login: user.login, })); diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index c6efaac610a..dfec502323f 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -71,7 +71,7 @@ export class AlertTabCtrl { this.alertNotifications = []; 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.initModel(); diff --git a/public/app/plugins/panel/gettingstarted/GettingStarted.tsx b/public/app/plugins/panel/gettingstarted/GettingStarted.tsx index 43ad7c3ab85..676b3c22ec3 100644 --- a/public/app/plugins/panel/gettingstarted/GettingStarted.tsx +++ b/public/app/plugins/panel/gettingstarted/GettingStarted.tsx @@ -79,7 +79,7 @@ export class GettingStarted extends PureComponent { href: 'org/users?gettingstarted', check: () => { return getBackendSrv() - .get('/api/org/users') + .get('/api/org/users/lookup') .then((res: any) => { return res.length > 1; });