Commit eb765d28 by Daniel Lee

alertlist: disable pause button when user does not have permission

parent 4bc5945c
...@@ -99,6 +99,27 @@ func GetAlerts(c *middleware.Context) Response { ...@@ -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) return Json(200, alertDTOs)
} }
......
...@@ -20,6 +20,7 @@ type AlertRule struct { ...@@ -20,6 +20,7 @@ type AlertRule struct {
EvalData *simplejson.Json `json:"evalData"` EvalData *simplejson.Json `json:"evalData"`
ExecutionError string `json:"executionError"` ExecutionError string `json:"executionError"`
DashbboardUri string `json:"dashboardUri"` DashbboardUri string `json:"dashboardUri"`
CanEdit bool `json:"canEdit"`
} }
type AlertNotification struct { type AlertNotification struct {
......
...@@ -199,6 +199,14 @@ type GetDashboardsQuery struct { ...@@ -199,6 +199,14 @@ type GetDashboardsQuery struct {
Result []*Dashboard Result []*Dashboard
} }
type GetDashboardPermissionsForUserQuery struct {
DashboardIds []int64
OrgId int64
UserId int64
OrgRole RoleType
Result []*DashboardPermissionForUser
}
type GetDashboardsByPluginIdQuery struct { type GetDashboardsByPluginIdQuery struct {
OrgId int64 OrgId int64
PluginId string PluginId string
...@@ -221,3 +229,9 @@ type DashboardFolder struct { ...@@ -221,3 +229,9 @@ type DashboardFolder struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Title string `json:"title"` Title string `json:"title"`
} }
type DashboardPermissionForUser struct {
DashboardId int64 `json:"dashboardId"`
Permission PermissionType `json:"permission"`
PermissionName string `json:"permissionName"`
}
package sqlstore package sqlstore
import ( import (
"strings"
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
...@@ -19,6 +20,7 @@ func init() { ...@@ -19,6 +20,7 @@ func init() {
bus.AddHandler("sql", GetDashboardSlugById) bus.AddHandler("sql", GetDashboardSlugById)
bus.AddHandler("sql", GetDashboardsByPluginId) bus.AddHandler("sql", GetDashboardsByPluginId)
bus.AddHandler("sql", GetFoldersForSignedInUser) bus.AddHandler("sql", GetFoldersForSignedInUser)
bus.AddHandler("sql", GetDashboardPermissionsForUser)
} }
func SaveDashboard(cmd *m.SaveDashboardCommand) error { func SaveDashboard(cmd *m.SaveDashboardCommand) error {
...@@ -309,9 +311,10 @@ func GetFoldersForSignedInUser(query *m.GetFoldersForSignedInUserQuery) 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 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 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 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.SignedInUser.UserId) params = append(params, query.SignedInUser.UserId)
params = append(params, query.OrgId)
sql += `WHERE sql += `WHERE
d.org_id = ? AND d.org_id = ? AND
...@@ -389,6 +392,76 @@ func GetDashboards(query *m.GetDashboardsQuery) error { ...@@ -389,6 +392,76 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
return nil 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 { func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error {
var dashboards = make([]*m.Dashboard, 0) var dashboards = make([]*m.Dashboard, 0)
whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false) whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false)
......
...@@ -482,6 +482,24 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -482,6 +482,24 @@ func TestDashboardDataAccess(t *testing.T) {
So(query.Result[0].Id, ShouldEqual, folder1.Id) So(query.Result[0].Id, ShouldEqual, folder1.Id)
So(query.Result[1].Id, ShouldEqual, folder2.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() { Convey("Editor users", func() {
...@@ -499,6 +517,24 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -499,6 +517,24 @@ func TestDashboardDataAccess(t *testing.T) {
So(query.Result[1].Id, ShouldEqual, folder2.Id) 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() { 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) updateTestDashboardWithAcl(folder1.Id, editorUser.Id, m.PERMISSION_VIEW)
...@@ -508,6 +544,7 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -508,6 +544,7 @@ func TestDashboardDataAccess(t *testing.T) {
So(len(query.Result), ShouldEqual, 1) So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, folder2.Id) So(query.Result[0].Id, ShouldEqual, folder2.Id)
}) })
}) })
Convey("Viewer users", func() { Convey("Viewer users", func() {
...@@ -523,6 +560,24 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -523,6 +560,24 @@ func TestDashboardDataAccess(t *testing.T) {
So(len(query.Result), ShouldEqual, 0) 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() { 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) updateTestDashboardWithAcl(folder1.Id, viewerUser.Id, m.PERMISSION_EDIT)
......
...@@ -24,6 +24,7 @@ describe('AlertRuleList', () => { ...@@ -24,6 +24,7 @@ describe('AlertRuleList', () => {
evalData: {}, evalData: {},
executionError: '', executionError: '',
dashboardUri: 'db/mygool', dashboardUri: 'db/mygool',
canEdit: true,
}, },
]) ])
); );
......
...@@ -147,7 +147,8 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> { ...@@ -147,7 +147,8 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
<div className="alert-rule-item__body"> <div className="alert-rule-item__body">
<div className="alert-rule-item__header"> <div className="alert-rule-item__header">
<div className="alert-rule-item__name"> <div className="alert-rule-item__name">
<a href={ruleUrl}>{this.renderText(rule.name)}</a> {rule.canEdit && <a href={ruleUrl}>{this.renderText(rule.name)}</a>}
{!rule.canEdit && <span>{this.renderText(rule.name)}</span>}
</div> </div>
<div className="alert-rule-item__text"> <div className="alert-rule-item__text">
<span className={`${rule.stateClass}`}>{this.renderText(rule.stateText)}</span> <span className={`${rule.stateClass}`}>{this.renderText(rule.stateText)}</span>
...@@ -156,17 +157,30 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> { ...@@ -156,17 +157,30 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
</div> </div>
{rule.info && <div className="small muted alert-rule-item__info">{this.renderText(rule.info)}</div>} {rule.info && <div className="small muted alert-rule-item__info">{this.renderText(rule.info)}</div>}
</div> </div>
<div className="alert-rule-item__actions"> <div className="alert-rule-item__actions">
<a <button
className="btn btn-small btn-inverse alert-list__btn width-2" className="btn btn-small btn-inverse alert-list__btn width-2"
title="Pausing an alert rule prevents it from executing" title="Pausing an alert rule prevents it from executing"
onClick={this.toggleState} onClick={this.toggleState}
disabled={!rule.canEdit}
> >
<i className={stateClass} /> <i className={stateClass} />
</a> </button>
{rule.canEdit && (
<a className="btn btn-small btn-inverse alert-list__btn width-2" href={ruleUrl} title="Edit alert rule"> <a className="btn btn-small btn-inverse alert-list__btn width-2" href={ruleUrl} title="Edit alert rule">
<i className="icon-gf icon-gf-settings" /> <i className="icon-gf icon-gf-settings" />
</a> </a>
)}
{!rule.canEdit && (
<button
className="btn btn-small btn-inverse alert-list__btn width-2"
title="Edit alert rule"
disabled={true}
>
<i className="icon-gf icon-gf-settings" />
</button>
)}
</div> </div>
</li> </li>
); );
......
...@@ -80,15 +80,16 @@ exports[`AlertRuleList should render 1 rule 1`] = ` ...@@ -80,15 +80,16 @@ exports[`AlertRuleList should render 1 rule 1`] = `
<div <div
className="alert-rule-item__actions" className="alert-rule-item__actions"
> >
<a <button
className="btn btn-small btn-inverse alert-list__btn width-2" className="btn btn-small btn-inverse alert-list__btn width-2"
disabled={false}
onClick={[Function]} onClick={[Function]}
title="Pausing an alert rule prevents it from executing" title="Pausing an alert rule prevents it from executing"
> >
<i <i
className="fa fa-pause" className="fa fa-pause"
/> />
</a> </button>
<a <a
className="btn btn-small btn-inverse alert-list__btn width-2" className="btn btn-small btn-inverse alert-list__btn width-2"
href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert" href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert"
......
...@@ -20,6 +20,7 @@ function getRule(name, state, info) { ...@@ -20,6 +20,7 @@ function getRule(name, state, info) {
stateClass: 'asd', stateClass: 'asd',
stateAge: '10m', stateAge: '10m',
info: info, info: info,
canEdit: true,
}; };
} }
......
...@@ -14,6 +14,7 @@ export const AlertRule = types ...@@ -14,6 +14,7 @@ export const AlertRule = types
stateAge: types.string, stateAge: types.string,
info: types.optional(types.string, ''), info: types.optional(types.string, ''),
dashboardUri: types.string, dashboardUri: types.string,
canEdit: types.boolean,
}) })
.views(self => ({ .views(self => ({
get isPaused() { get isPaused() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment