Procházet zdrojové kódy

alertlist: disable pause button when user does not have permission

Daniel Lee před 8 roky
rodič
revize
eb765d288c

+ 21 - 0
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)
 }
 

+ 1 - 0
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 {

+ 14 - 0
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"`
+}

+ 74 - 1
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)

+ 55 - 0
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)
 

+ 1 - 0
public/app/containers/AlertRuleList/AlertRuleList.jest.tsx

@@ -24,6 +24,7 @@ describe('AlertRuleList', () => {
           evalData: {},
           executionError: '',
           dashboardUri: 'db/mygool',
+          canEdit: true,
         },
       ])
     );

+ 20 - 6
public/app/containers/AlertRuleList/AlertRuleList.tsx

@@ -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>
     );

+ 3 - 2
public/app/containers/AlertRuleList/__snapshots__/AlertRuleList.jest.tsx.snap

@@ -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"

+ 1 - 0
public/app/stores/AlertListStore/AlertListStore.jest.ts

@@ -20,6 +20,7 @@ function getRule(name, state, info) {
     stateClass: 'asd',
     stateAge: '10m',
     info: info,
+    canEdit: true,
   };
 }
 

+ 1 - 0
public/app/stores/AlertListStore/AlertRule.ts

@@ -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() {