mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
alertlist: disable pause button when user does not have permission
This commit is contained in:
parent
4bc5945c17
commit
eb765d288c
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -24,6 +24,7 @@ describe('AlertRuleList', () => {
|
||||
evalData: {},
|
||||
executionError: '',
|
||||
dashboardUri: 'db/mygool',
|
||||
canEdit: true,
|
||||
},
|
||||
])
|
||||
);
|
||||
|
@ -147,7 +147,8 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
|
||||
<div className="alert-rule-item__body">
|
||||
<div className="alert-rule-item__header">
|
||||
<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 className="alert-rule-item__text">
|
||||
<span className={`${rule.stateClass}`}>{this.renderText(rule.stateText)}</span>
|
||||
@ -156,17 +157,30 @@ export class AlertRuleItem extends React.Component<AlertRuleItemProps, any> {
|
||||
</div>
|
||||
{rule.info && <div className="small muted alert-rule-item__info">{this.renderText(rule.info)}</div>}
|
||||
</div>
|
||||
|
||||
<div className="alert-rule-item__actions">
|
||||
<a
|
||||
<button
|
||||
className="btn btn-small btn-inverse alert-list__btn width-2"
|
||||
title="Pausing an alert rule prevents it from executing"
|
||||
onClick={this.toggleState}
|
||||
disabled={!rule.canEdit}
|
||||
>
|
||||
<i className={stateClass} />
|
||||
</a>
|
||||
<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" />
|
||||
</a>
|
||||
</button>
|
||||
{rule.canEdit && (
|
||||
<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" />
|
||||
</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>
|
||||
</li>
|
||||
);
|
||||
|
@ -80,15 +80,16 @@ exports[`AlertRuleList should render 1 rule 1`] = `
|
||||
<div
|
||||
className="alert-rule-item__actions"
|
||||
>
|
||||
<a
|
||||
<button
|
||||
className="btn btn-small btn-inverse alert-list__btn width-2"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Pausing an alert rule prevents it from executing"
|
||||
>
|
||||
<i
|
||||
className="fa fa-pause"
|
||||
/>
|
||||
</a>
|
||||
</button>
|
||||
<a
|
||||
className="btn btn-small btn-inverse alert-list__btn width-2"
|
||||
href="dashboard/db/mygool?panelId=3&fullscreen&edit&tab=alert"
|
||||
|
@ -20,6 +20,7 @@ function getRule(name, state, info) {
|
||||
stateClass: 'asd',
|
||||
stateAge: '10m',
|
||||
info: info,
|
||||
canEdit: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ export const AlertRule = types
|
||||
stateAge: types.string,
|
||||
info: types.optional(types.string, ''),
|
||||
dashboardUri: types.string,
|
||||
canEdit: types.boolean,
|
||||
})
|
||||
.views(self => ({
|
||||
get isPaused() {
|
||||
|
Loading…
Reference in New Issue
Block a user