diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 61232f3752f..62575deb8d7 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -99,6 +99,27 @@ func GetAlerts(c *middleware.Context) Response { } } + permissionsQuery := models.GetDashboardPermissionsForUserQuery{ + DashboardIds: dashboardIds, + OrgId: c.OrgId, + UserId: c.SignedInUser.UserId, + OrgRole: c.SignedInUser.OrgRole, + } + + if len(alertDTOs) > 0 { + if err := bus.Dispatch(&permissionsQuery); err != nil { + return ApiError(500, "List alerts failed", err) + } + } + + for _, alert := range alertDTOs { + for _, perm := range permissionsQuery.Result { + if alert.DashboardId == perm.DashboardId { + alert.CanEdit = perm.Permission > 1 + } + } + } + return Json(200, alertDTOs) } diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index 4285ebc89cc..c32edb6e51c 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -20,6 +20,7 @@ type AlertRule struct { EvalData *simplejson.Json `json:"evalData"` ExecutionError string `json:"executionError"` DashbboardUri string `json:"dashboardUri"` + CanEdit bool `json:"canEdit"` } type AlertNotification struct { diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index dccd93707a5..69e490ece11 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -199,6 +199,14 @@ type GetDashboardsQuery struct { Result []*Dashboard } +type GetDashboardPermissionsForUserQuery struct { + DashboardIds []int64 + OrgId int64 + UserId int64 + OrgRole RoleType + Result []*DashboardPermissionForUser +} + type GetDashboardsByPluginIdQuery struct { OrgId int64 PluginId string @@ -221,3 +229,9 @@ type DashboardFolder struct { Id int64 `json:"id"` Title string `json:"title"` } + +type DashboardPermissionForUser struct { + DashboardId int64 `json:"dashboardId"` + Permission PermissionType `json:"permission"` + PermissionName string `json:"permissionName"` +} diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index f4cdab22e89..81dab375188 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -1,6 +1,7 @@ package sqlstore import ( + "strings" "time" "github.com/grafana/grafana/pkg/bus" @@ -19,6 +20,7 @@ func init() { bus.AddHandler("sql", GetDashboardSlugById) bus.AddHandler("sql", GetDashboardsByPluginId) bus.AddHandler("sql", GetFoldersForSignedInUser) + bus.AddHandler("sql", GetDashboardPermissionsForUser) } func SaveDashboard(cmd *m.SaveDashboardCommand) error { @@ -309,9 +311,10 @@ func GetFoldersForSignedInUser(query *m.GetFoldersForSignedInUserQuery) error { LEFT JOIN dashboard_acl AS da ON d.id = da.dashboard_id LEFT JOIN team_member AS ugm ON ugm.team_id = da.team_id LEFT JOIN org_user ou ON ou.role = da.role AND ou.user_id = ? - LEFT JOIN org_user ouRole ON ouRole.role = 'Editor' AND ouRole.user_id = ?` + LEFT JOIN org_user ouRole ON ouRole.role = 'Editor' AND ouRole.user_id = ? AND ouRole.org_id = ?` params = append(params, query.SignedInUser.UserId) params = append(params, query.SignedInUser.UserId) + params = append(params, query.OrgId) sql += `WHERE d.org_id = ? AND @@ -389,6 +392,76 @@ func GetDashboards(query *m.GetDashboardsQuery) error { return nil } +// 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 { + if len(query.DashboardIds) == 0 { + return m.ErrCommandValidationFailed + } + + if query.OrgRole == m.ROLE_ADMIN { + var permissions = make([]*m.DashboardPermissionForUser, 0) + for _, d := range query.DashboardIds { + permissions = append(permissions, &m.DashboardPermissionForUser{ + DashboardId: d, + Permission: m.PERMISSION_ADMIN, + PermissionName: m.PERMISSION_ADMIN.String(), + }) + } + query.Result = permissions + + return nil + } + + params := make([]interface{}, 0) + + // check dashboards that have ACLs via user id, team id or role + sql := `SELECT d.id AS dashboard_id, MAX(COALESCE(da.permission, pt.permission)) AS permission + FROM dashboard AS d + LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id + LEFT JOIN team_member as ugm on ugm.team_id = da.team_id + LEFT JOIN org_user ou ON ou.role = da.role AND ou.user_id = ? + ` + params = append(params, query.UserId) + + //check the user's role for dashboards that do not have hasAcl set + sql += `LEFT JOIN org_user ouRole ON ouRole.user_id = ? AND ouRole.org_id = ?` + params = append(params, query.UserId) + params = append(params, query.OrgId) + + sql += ` + LEFT JOIN (SELECT 1 AS permission, 'Viewer' AS 'role' + UNION SELECT 2 AS permission, 'Editor' AS 'role' + UNION SELECT 4 AS permission, 'Admin' AS 'role') pt ON ouRole.role = pt.role + WHERE + d.Id IN (?` + strings.Repeat(",?", len(query.DashboardIds)-1) + `) ` + for _, id := range query.DashboardIds { + params = append(params, id) + } + + sql += ` AND + d.org_id = ? AND + ( + (d.has_acl = ? AND (da.user_id = ? OR ugm.user_id = ? OR ou.id IS NOT NULL)) + OR (d.has_acl = ? AND ouRole.id IS NOT NULL) + ) + group by d.id + order by d.id asc` + params = append(params, dialect.BooleanStr(true)) + params = append(params, query.OrgId) + params = append(params, query.UserId) + params = append(params, query.UserId) + params = append(params, dialect.BooleanStr(false)) + + err := x.Sql(sql, params...).Find(&query.Result) + + for _, p := range query.Result { + p.PermissionName = p.Permission.String() + } + + return err +} + func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error { var dashboards = make([]*m.Dashboard, 0) whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false) diff --git a/pkg/services/sqlstore/dashboard_test.go b/pkg/services/sqlstore/dashboard_test.go index 3b1e05d3772..b5d21b37567 100644 --- a/pkg/services/sqlstore/dashboard_test.go +++ b/pkg/services/sqlstore/dashboard_test.go @@ -482,6 +482,24 @@ func TestDashboardDataAccess(t *testing.T) { So(query.Result[0].Id, ShouldEqual, folder1.Id) So(query.Result[1].Id, ShouldEqual, folder2.Id) }) + + Convey("should have write access to all folders and dashboards", func() { + query := m.GetDashboardPermissionsForUserQuery{ + DashboardIds: []int64{folder1.Id, folder2.Id}, + OrgId: 1, + UserId: adminUser.Id, + OrgRole: m.ROLE_ADMIN, + } + + err := GetDashboardPermissionsForUser(&query) + So(err, ShouldBeNil) + + 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[1].DashboardId, ShouldEqual, folder2.Id) + So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_ADMIN) + }) }) Convey("Editor users", func() { @@ -499,6 +517,24 @@ func TestDashboardDataAccess(t *testing.T) { So(query.Result[1].Id, ShouldEqual, folder2.Id) }) + Convey("should have edit access to folders with default ACL", func() { + query := m.GetDashboardPermissionsForUserQuery{ + DashboardIds: []int64{folder1.Id, folder2.Id}, + OrgId: 1, + UserId: editorUser.Id, + OrgRole: m.ROLE_EDITOR, + } + + err := GetDashboardPermissionsForUser(&query) + So(err, ShouldBeNil) + + 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[1].DashboardId, ShouldEqual, folder2.Id) + So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_EDIT) + }) + Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() { updateTestDashboardWithAcl(folder1.Id, editorUser.Id, m.PERMISSION_VIEW) @@ -508,6 +544,7 @@ func TestDashboardDataAccess(t *testing.T) { So(len(query.Result), ShouldEqual, 1) So(query.Result[0].Id, ShouldEqual, folder2.Id) }) + }) Convey("Viewer users", func() { @@ -523,6 +560,24 @@ func TestDashboardDataAccess(t *testing.T) { So(len(query.Result), ShouldEqual, 0) }) + Convey("should have view access to folders with default ACL", func() { + query := m.GetDashboardPermissionsForUserQuery{ + DashboardIds: []int64{folder1.Id, folder2.Id}, + OrgId: 1, + UserId: viewerUser.Id, + OrgRole: m.ROLE_VIEWER, + } + + err := GetDashboardPermissionsForUser(&query) + So(err, ShouldBeNil) + + 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[1].DashboardId, ShouldEqual, folder2.Id) + So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_VIEW) + }) + Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() { updateTestDashboardWithAcl(folder1.Id, viewerUser.Id, m.PERMISSION_EDIT) diff --git a/public/app/containers/AlertRuleList/AlertRuleList.jest.tsx b/public/app/containers/AlertRuleList/AlertRuleList.jest.tsx index a12340555e9..f5aa07b454a 100644 --- a/public/app/containers/AlertRuleList/AlertRuleList.jest.tsx +++ b/public/app/containers/AlertRuleList/AlertRuleList.jest.tsx @@ -24,6 +24,7 @@ describe('AlertRuleList', () => { evalData: {}, executionError: '', dashboardUri: 'db/mygool', + canEdit: true, }, ]) ); diff --git a/public/app/containers/AlertRuleList/AlertRuleList.tsx b/public/app/containers/AlertRuleList/AlertRuleList.tsx index 429504e46d9..d2712706154 100644 --- a/public/app/containers/AlertRuleList/AlertRuleList.tsx +++ b/public/app/containers/AlertRuleList/AlertRuleList.tsx @@ -147,7 +147,8 @@ export class AlertRuleItem extends React.Component {
- {this.renderText(rule.name)} + {rule.canEdit && {this.renderText(rule.name)}} + {!rule.canEdit && {this.renderText(rule.name)}}
{this.renderText(rule.stateText)} @@ -156,17 +157,30 @@ export class AlertRuleItem extends React.Component {
{rule.info &&
{this.renderText(rule.info)}
}
+
- - - - - + + {rule.canEdit && ( + + + + )} + {!rule.canEdit && ( + + )}
); diff --git a/public/app/containers/AlertRuleList/__snapshots__/AlertRuleList.jest.tsx.snap b/public/app/containers/AlertRuleList/__snapshots__/AlertRuleList.jest.tsx.snap index 571566503f8..da5fa5f12c4 100644 --- a/public/app/containers/AlertRuleList/__snapshots__/AlertRuleList.jest.tsx.snap +++ b/public/app/containers/AlertRuleList/__snapshots__/AlertRuleList.jest.tsx.snap @@ -80,15 +80,16 @@ exports[`AlertRuleList should render 1 rule 1`] = `