Commit fa18b005 by Daniel Lee

WIP: delete permission in API

parent fff7b706
...@@ -250,6 +250,8 @@ func (hs *HttpServer) registerRoutes() { ...@@ -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)
}) })
......
...@@ -29,3 +29,45 @@ func GetDashboardAcl(c *middleware.Context) Response { ...@@ -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, "")
}
...@@ -15,6 +15,8 @@ func TestDashboardAclApiEndpoint(t *testing.T) { ...@@ -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) { ...@@ -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) { ...@@ -37,14 +39,68 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
}) })
}) })
Convey("When user is editor and not in the ACL", func() { Convey("When user is editor and in the ACL", func() {
loggedInUserScenarioWithRole("When calling GET on", "/api/dashboard/1/acl", models.ROLE_EDITOR, func(sc *scenarioContext) { 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) { ...@@ -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)
})
})
}) })
}) })
} }
...@@ -56,10 +56,10 @@ func TestDataSourcesProxy(t *testing.T) { ...@@ -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, ...@@ -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)
}) })
......
...@@ -32,13 +32,45 @@ func CanViewAcl(dashboardId int64, role m.RoleType, isGrafanaAdmin bool, orgId i ...@@ -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, ...@@ -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
}
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