Procházet zdrojové kódy

WIP: delete permission in API

Daniel Lee před 8 roky
rodič
revize
fa18b0053d

+ 2 - 0
pkg/api/api.go

@@ -250,6 +250,8 @@ func (hs *HttpServer) registerRoutes() {
 
 
 			r.Group("/:id/acl", func() {
 			r.Group("/:id/acl", func() {
 				r.Get("/", wrap(GetDashboardAcl))
 				r.Get("/", wrap(GetDashboardAcl))
+				r.Delete("/user/:userId", wrap(DeleteDashboardAclByUser))
+				r.Delete("/user-group/:userGroupId", wrap(DeleteDashboardAclByUserGroup))
 			}, reqSignedIn)
 			}, reqSignedIn)
 		})
 		})
 
 

+ 42 - 0
pkg/api/dashboard_acl.go

@@ -29,3 +29,45 @@ func GetDashboardAcl(c *middleware.Context) Response {
 
 
 	return Json(200, &query.Result)
 	return Json(200, &query.Result)
 }
 }
+
+func DeleteDashboardAclByUser(c *middleware.Context) Response {
+	dashboardId := c.ParamsInt64(":id")
+	userId := c.ParamsInt64(":userId")
+	cmd := m.RemoveDashboardPermissionCommand{DashboardId: dashboardId, UserId: userId, OrgId: c.OrgId}
+
+	hasPermission, err := guardian.CanDeleteFromAcl(dashboardId, c.OrgRole, c.IsGrafanaAdmin, c.OrgId, c.UserId)
+	if err != nil {
+		return ApiError(500, "Failed to delete from Dashboard ACL", err)
+	}
+
+	if !hasPermission {
+		return Json(403, util.DynMap{"status": "Forbidden", "message": "Does not have access to this Dashboard ACL"})
+	}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		return ApiError(500, "Failed to delete permission for user", err)
+	}
+
+	return Json(200, "")
+}
+
+func DeleteDashboardAclByUserGroup(c *middleware.Context) Response {
+	dashboardId := c.ParamsInt64(":id")
+	userGroupId := c.ParamsInt64(":userGroupId")
+	cmd := m.RemoveDashboardPermissionCommand{DashboardId: dashboardId, UserGroupId: userGroupId, OrgId: c.OrgId}
+
+	hasPermission, err := guardian.CanDeleteFromAcl(dashboardId, c.OrgRole, c.IsGrafanaAdmin, c.OrgId, c.UserId)
+	if err != nil {
+		return ApiError(500, "Failed to delete from Dashboard ACL", err)
+	}
+
+	if !hasPermission {
+		return Json(403, util.DynMap{"status": "Forbidden", "message": "Does not have access to this Dashboard ACL"})
+	}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		return ApiError(500, "Failed to delete permission for user", err)
+	}
+
+	return Json(200, "")
+}

+ 73 - 3
pkg/api/dashboard_acl_test.go

@@ -15,6 +15,8 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
 		mockResult := []*models.DashboardAclInfoDTO{
 		mockResult := []*models.DashboardAclInfoDTO{
 			{Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permissions: models.PERMISSION_EDIT},
 			{Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permissions: models.PERMISSION_EDIT},
 			{Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permissions: models.PERMISSION_VIEW},
 			{Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permissions: models.PERMISSION_VIEW},
+			{Id: 3, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permissions: models.PERMISSION_EDIT},
+			{Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permissions: models.PERMISSION_READ_ONLY_EDIT},
 		}
 		}
 		bus.AddHandler("test", func(query *models.GetDashboardPermissionsQuery) error {
 		bus.AddHandler("test", func(query *models.GetDashboardPermissionsQuery) error {
 			query.Result = mockResult
 			query.Result = mockResult
@@ -22,7 +24,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
 		})
 		})
 
 
 		Convey("When user is org admin", func() {
 		Convey("When user is org admin", func() {
-			loggedInUserScenarioWithRole("When calling GET on", "/api/dashboard/1/acl", models.ROLE_ADMIN, func(sc *scenarioContext) {
+			loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/1/acl", "/api/dashboards/:id/acl", models.ROLE_ADMIN, func(sc *scenarioContext) {
 				Convey("Should be able to access ACL", func() {
 				Convey("Should be able to access ACL", func() {
 					sc.handlerFunc = GetDashboardAcl
 					sc.handlerFunc = GetDashboardAcl
 					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
 					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
@@ -37,14 +39,68 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
 			})
 			})
 		})
 		})
 
 
-		Convey("When user is editor and not in the ACL", func() {
-			loggedInUserScenarioWithRole("When calling GET on", "/api/dashboard/1/acl", models.ROLE_EDITOR, func(sc *scenarioContext) {
+		Convey("When user is editor and in the ACL", func() {
+			loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/1/acl", "/api/dashboards/:id/acl", models.ROLE_EDITOR, func(sc *scenarioContext) {
+				mockResult = append(mockResult, &models.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_EDIT})
 
 
 				bus.AddHandler("test2", func(query *models.GetAllowedDashboardsQuery) error {
 				bus.AddHandler("test2", func(query *models.GetAllowedDashboardsQuery) error {
 					query.Result = []int64{1}
 					query.Result = []int64{1}
 					return nil
 					return nil
 				})
 				})
 
 
+				Convey("Should be able to access ACL", func() {
+					sc.handlerFunc = GetDashboardAcl
+					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
+
+					So(sc.resp.Code, ShouldEqual, 200)
+				})
+			})
+
+			loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/1/acl/user/1", "/api/dashboards/:id/acl/user/:userId", models.ROLE_EDITOR, func(sc *scenarioContext) {
+				mockResult = append(mockResult, &models.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_EDIT})
+
+				bus.AddHandler("test3", func(cmd *models.RemoveDashboardPermissionCommand) error {
+					return nil
+				})
+
+				Convey("Should be able to delete permission", func() {
+					sc.handlerFunc = DeleteDashboardAclByUser
+					sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
+
+					So(sc.resp.Code, ShouldEqual, 200)
+				})
+			})
+
+			Convey("When user is a member of a user group in the ACL with edit permission", func() {
+				loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/1/acl/user/1", "/api/dashboards/:id/acl/user/:userId", models.ROLE_EDITOR, func(sc *scenarioContext) {
+
+					bus.AddHandler("test3", func(query *models.GetUserGroupsByUserQuery) error {
+						query.Result = []*models.UserGroup{{Id: 1, OrgId: 1, Name: "UG1"}}
+						return nil
+					})
+
+					bus.AddHandler("test3", func(cmd *models.RemoveDashboardPermissionCommand) error {
+						return nil
+					})
+
+					Convey("Should be able to delete permission", func() {
+						sc.handlerFunc = DeleteDashboardAclByUser
+						sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
+
+						So(sc.resp.Code, ShouldEqual, 200)
+					})
+				})
+			})
+		})
+
+		Convey("When user is editor and not in the ACL", func() {
+			loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/1/acl", "/api/dashboards/:id/acl", models.ROLE_EDITOR, func(sc *scenarioContext) {
+
+				bus.AddHandler("test2", func(query *models.GetAllowedDashboardsQuery) error {
+					query.Result = []int64{}
+					return nil
+				})
+
 				Convey("Should not be able to access ACL", func() {
 				Convey("Should not be able to access ACL", func() {
 					sc.handlerFunc = GetDashboardAcl
 					sc.handlerFunc = GetDashboardAcl
 					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
 					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
@@ -52,6 +108,20 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
 					So(sc.resp.Code, ShouldEqual, 403)
 					So(sc.resp.Code, ShouldEqual, 403)
 				})
 				})
 			})
 			})
+
+			loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/1/acl/user/1", "/api/dashboards/:id/acl/user/:userId", models.ROLE_EDITOR, func(sc *scenarioContext) {
+				mockResult = append(mockResult, &models.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_VIEW})
+				bus.AddHandler("test3", func(cmd *models.RemoveDashboardPermissionCommand) error {
+					return nil
+				})
+
+				Convey("Should be not be able to delete permission", func() {
+					sc.handlerFunc = DeleteDashboardAclByUser
+					sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
+
+					So(sc.resp.Code, ShouldEqual, 403)
+				})
+			})
 		})
 		})
 	})
 	})
 }
 }

+ 8 - 3
pkg/api/datasources_test.go

@@ -56,10 +56,10 @@ func TestDataSourcesProxy(t *testing.T) {
 }
 }
 
 
 func loggedInUserScenario(desc string, url string, fn scenarioFunc) {
 func loggedInUserScenario(desc string, url string, fn scenarioFunc) {
-	loggedInUserScenarioWithRole(desc, url, models.ROLE_EDITOR, fn)
+	loggedInUserScenarioWithRole(desc, "GET", url, url, models.ROLE_EDITOR, fn)
 }
 }
 
 
-func loggedInUserScenarioWithRole(desc string, url string, role models.RoleType, fn scenarioFunc) {
+func loggedInUserScenarioWithRole(desc string, method string, url string, routePattern string, role models.RoleType, fn scenarioFunc) {
 	Convey(desc+" "+url, func() {
 	Convey(desc+" "+url, func() {
 		defer bus.ClearBusHandlers()
 		defer bus.ClearBusHandlers()
 
 
@@ -89,7 +89,12 @@ func loggedInUserScenarioWithRole(desc string, url string, role models.RoleType,
 			return nil
 			return nil
 		})
 		})
 
 
-		sc.m.Get(url, sc.defaultHandler)
+		switch method {
+		case "GET":
+			sc.m.Get(routePattern, sc.defaultHandler)
+		case "DELETE":
+			sc.m.Delete(routePattern, sc.defaultHandler)
+		}
 
 
 		fn(sc)
 		fn(sc)
 	})
 	})

+ 47 - 1
pkg/services/guardian/guardian.go

@@ -32,13 +32,45 @@ func CanViewAcl(dashboardId int64, role m.RoleType, isGrafanaAdmin bool, orgId i
 		return false, err
 		return false, err
 	}
 	}
 
 
-	if len(filteredList) > 1 && filteredList[0] == dashboardId {
+	if len(filteredList) > 0 && filteredList[0] == dashboardId {
 		return true, nil
 		return true, nil
 	}
 	}
 
 
 	return false, nil
 	return false, nil
 }
 }
 
 
+// CanDeleteFromAcl determines if a user has permission to delete from a dashboard's ACL
+func CanDeleteFromAcl(dashboardId int64, role m.RoleType, isGrafanaAdmin bool, orgId int64, userId int64) (bool, error) {
+	if role == m.ROLE_ADMIN || isGrafanaAdmin {
+		return true, nil
+	}
+
+	permissions, err := getDashboardPermissions(dashboardId)
+	if err != nil {
+		return false, err
+	}
+
+	if len(permissions) == 0 {
+		return true, nil
+	}
+
+	userGroups, err := getUserGroupsByUser(userId)
+
+	for _, p := range permissions {
+		if p.UserId == userId && p.Permissions == m.PERMISSION_EDIT {
+			return true, nil
+		}
+
+		for _, ug := range userGroups {
+			if ug.Id == p.UserGroupId && p.Permissions == m.PERMISSION_EDIT {
+				return true, nil
+			}
+		}
+	}
+
+	return false, nil
+}
+
 func getUser(userId int64) (*m.SignedInUser, error) {
 func getUser(userId int64) (*m.SignedInUser, error) {
 	query := m.GetSignedInUserQuery{UserId: userId}
 	query := m.GetSignedInUserQuery{UserId: userId}
 	err := bus.Dispatch(&query)
 	err := bus.Dispatch(&query)
@@ -52,3 +84,17 @@ func getAllowedDashboards(dashList []int64, orgId int64, userId int64) ([]int64,
 
 
 	return query.Result, err
 	return query.Result, err
 }
 }
+
+func getDashboardPermissions(dashboardId int64) ([]*m.DashboardAclInfoDTO, error) {
+	query := m.GetDashboardPermissionsQuery{DashboardId: dashboardId}
+	err := bus.Dispatch(&query)
+
+	return query.Result, err
+}
+
+func getUserGroupsByUser(userId int64) ([]*m.UserGroup, error) {
+	query := m.GetUserGroupsByUserQuery{UserId: userId}
+	err := bus.Dispatch(&query)
+
+	return query.Result, err
+}