mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #11526 from grafana/11173_folder_admin
A folder admin should be able to add permissions for folder/its dashboards
This commit is contained in:
commit
b3acbb9995
@ -149,8 +149,6 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
|
|
||||||
// team (admin permission required)
|
// team (admin permission required)
|
||||||
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
|
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
|
||||||
teamsRoute.Get("/:teamId", wrap(GetTeamByID))
|
|
||||||
teamsRoute.Get("/search", wrap(SearchTeams))
|
|
||||||
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam))
|
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam))
|
||||||
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
|
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
|
||||||
teamsRoute.Delete("/:teamId", wrap(DeleteTeamByID))
|
teamsRoute.Delete("/:teamId", wrap(DeleteTeamByID))
|
||||||
@ -159,6 +157,12 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
|
teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
|
||||||
}, reqOrgAdmin)
|
}, reqOrgAdmin)
|
||||||
|
|
||||||
|
// team without requirement of user to be org admin
|
||||||
|
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
|
||||||
|
teamsRoute.Get("/:teamId", wrap(GetTeamByID))
|
||||||
|
teamsRoute.Get("/search", wrap(SearchTeams))
|
||||||
|
})
|
||||||
|
|
||||||
// org information available to all users.
|
// org information available to all users.
|
||||||
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
||||||
orgRoute.Get("/", wrap(GetOrgCurrent))
|
orgRoute.Get("/", wrap(GetOrgCurrent))
|
||||||
@ -170,7 +174,6 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
|
orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
|
||||||
orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
|
orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
|
||||||
orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
|
orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
|
||||||
orgRoute.Get("/users", wrap(GetOrgUsersForCurrentOrg))
|
|
||||||
orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
|
orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
|
||||||
orgRoute.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))
|
orgRoute.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))
|
||||||
|
|
||||||
@ -184,6 +187,11 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
|
orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
|
||||||
}, reqOrgAdmin)
|
}, reqOrgAdmin)
|
||||||
|
|
||||||
|
// current org without requirement of user to be org admin
|
||||||
|
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
||||||
|
orgRoute.Get("/users", wrap(GetOrgUsersForCurrentOrg))
|
||||||
|
})
|
||||||
|
|
||||||
// create new org
|
// create new org
|
||||||
apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))
|
apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))
|
||||||
|
|
||||||
|
@ -173,7 +173,7 @@ func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission m.Permiss
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.checkAcl(permission, acl)
|
return g.checkAcl(permission, existingPermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAcl returns dashboard acl
|
// GetAcl returns dashboard acl
|
||||||
|
File diff suppressed because it is too large
Load Diff
256
pkg/services/guardian/guardian_util_test.go
Normal file
256
pkg/services/guardian/guardian_util_test.go
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
package guardian
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
type scenarioContext struct {
|
||||||
|
t *testing.T
|
||||||
|
orgRoleScenario string
|
||||||
|
permissionScenario string
|
||||||
|
g DashboardGuardian
|
||||||
|
givenUser *m.SignedInUser
|
||||||
|
givenDashboardID int64
|
||||||
|
givenPermissions []*m.DashboardAclInfoDTO
|
||||||
|
givenTeams []*m.Team
|
||||||
|
updatePermissions []*m.DashboardAcl
|
||||||
|
expectedFlags permissionFlags
|
||||||
|
callerFile string
|
||||||
|
callerLine int
|
||||||
|
}
|
||||||
|
|
||||||
|
type scenarioFunc func(c *scenarioContext)
|
||||||
|
|
||||||
|
func orgRoleScenario(desc string, t *testing.T, role m.RoleType, fn scenarioFunc) {
|
||||||
|
user := &m.SignedInUser{
|
||||||
|
UserId: userID,
|
||||||
|
OrgId: orgID,
|
||||||
|
OrgRole: role,
|
||||||
|
}
|
||||||
|
guard := New(dashboardID, orgID, user)
|
||||||
|
sc := &scenarioContext{
|
||||||
|
t: t,
|
||||||
|
orgRoleScenario: desc,
|
||||||
|
givenUser: user,
|
||||||
|
givenDashboardID: dashboardID,
|
||||||
|
g: guard,
|
||||||
|
}
|
||||||
|
|
||||||
|
Convey(desc, func() {
|
||||||
|
fn(sc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func permissionScenario(desc string, dashboardID int64, sc *scenarioContext, permissions []*m.DashboardAclInfoDTO, fn scenarioFunc) {
|
||||||
|
bus.ClearBusHandlers()
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
|
||||||
|
if query.OrgId != sc.givenUser.OrgId {
|
||||||
|
sc.reportFailure("Invalid organization id for GetDashboardAclInfoListQuery", sc.givenUser.OrgId, query.OrgId)
|
||||||
|
}
|
||||||
|
if query.DashboardId != sc.givenDashboardID {
|
||||||
|
sc.reportFailure("Invalid dashboard id for GetDashboardAclInfoListQuery", sc.givenDashboardID, query.DashboardId)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Result = permissions
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
teams := []*m.Team{}
|
||||||
|
|
||||||
|
for _, p := range permissions {
|
||||||
|
if p.TeamId > 0 {
|
||||||
|
teams = append(teams, &m.Team{Id: p.TeamId})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||||
|
if query.OrgId != sc.givenUser.OrgId {
|
||||||
|
sc.reportFailure("Invalid organization id for GetTeamsByUserQuery", sc.givenUser.OrgId, query.OrgId)
|
||||||
|
}
|
||||||
|
if query.UserId != sc.givenUser.UserId {
|
||||||
|
sc.reportFailure("Invalid user id for GetTeamsByUserQuery", sc.givenUser.UserId, query.UserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.Result = teams
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
sc.permissionScenario = desc
|
||||||
|
sc.g = New(dashboardID, sc.givenUser.OrgId, sc.givenUser)
|
||||||
|
sc.givenDashboardID = dashboardID
|
||||||
|
sc.givenPermissions = permissions
|
||||||
|
sc.givenTeams = teams
|
||||||
|
|
||||||
|
Convey(desc, func() {
|
||||||
|
fn(sc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type permissionType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
USER permissionType = 1 << iota
|
||||||
|
TEAM
|
||||||
|
EDITOR
|
||||||
|
VIEWER
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p permissionType) String() string {
|
||||||
|
names := map[uint8]string{
|
||||||
|
uint8(USER): "user",
|
||||||
|
uint8(TEAM): "team",
|
||||||
|
uint8(EDITOR): "editor role",
|
||||||
|
uint8(VIEWER): "viewer role",
|
||||||
|
}
|
||||||
|
return names[uint8(p)]
|
||||||
|
}
|
||||||
|
|
||||||
|
type permissionFlags uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
NO_ACCESS permissionFlags = 1 << iota
|
||||||
|
CAN_ADMIN
|
||||||
|
CAN_EDIT
|
||||||
|
CAN_SAVE
|
||||||
|
CAN_VIEW
|
||||||
|
FULL_ACCESS = CAN_ADMIN | CAN_EDIT | CAN_SAVE | CAN_VIEW
|
||||||
|
EDITOR_ACCESS = CAN_EDIT | CAN_SAVE | CAN_VIEW
|
||||||
|
VIEWER_ACCESS = CAN_VIEW
|
||||||
|
)
|
||||||
|
|
||||||
|
func (flag permissionFlags) canAdmin() bool {
|
||||||
|
return flag&CAN_ADMIN != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flag permissionFlags) canEdit() bool {
|
||||||
|
return flag&CAN_EDIT != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flag permissionFlags) canSave() bool {
|
||||||
|
return flag&CAN_SAVE != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flag permissionFlags) canView() bool {
|
||||||
|
return flag&CAN_VIEW != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (flag permissionFlags) noAccess() bool {
|
||||||
|
return flag&(CAN_ADMIN|CAN_EDIT|CAN_SAVE|CAN_VIEW) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f permissionFlags) String() string {
|
||||||
|
r := []string{}
|
||||||
|
|
||||||
|
if f.canAdmin() {
|
||||||
|
r = append(r, "admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.canEdit() {
|
||||||
|
r = append(r, "edit")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.canSave() {
|
||||||
|
r = append(r, "save")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.canView() {
|
||||||
|
r = append(r, "view")
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.noAccess() {
|
||||||
|
r = append(r, "<no access>")
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(r[:], ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *scenarioContext) reportSuccess() {
|
||||||
|
So(true, ShouldBeTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *scenarioContext) reportFailure(desc string, expected interface{}, actual interface{}) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("\n")
|
||||||
|
buf.WriteString(sc.orgRoleScenario)
|
||||||
|
buf.WriteString(" ")
|
||||||
|
buf.WriteString(sc.permissionScenario)
|
||||||
|
buf.WriteString("\n ")
|
||||||
|
buf.WriteString(desc)
|
||||||
|
buf.WriteString("\n")
|
||||||
|
buf.WriteString(fmt.Sprintf("Source test: %s:%d\n", sc.callerFile, sc.callerLine))
|
||||||
|
buf.WriteString(fmt.Sprintf("Expected: %v\n", expected))
|
||||||
|
buf.WriteString(fmt.Sprintf("Actual: %v\n", actual))
|
||||||
|
buf.WriteString("Context:")
|
||||||
|
buf.WriteString(fmt.Sprintf("\n Given user: orgRole=%s, id=%d, orgId=%d", sc.givenUser.OrgRole, sc.givenUser.UserId, sc.givenUser.OrgId))
|
||||||
|
buf.WriteString(fmt.Sprintf("\n Given dashboard id: %d", sc.givenDashboardID))
|
||||||
|
|
||||||
|
for i, p := range sc.givenPermissions {
|
||||||
|
r := "<nil>"
|
||||||
|
if p.Role != nil {
|
||||||
|
r = string(*p.Role)
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf("\n Given permission (%d): dashboardId=%d, userId=%d, teamId=%d, role=%v, permission=%s", i, p.DashboardId, p.UserId, p.TeamId, r, p.Permission.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range sc.givenTeams {
|
||||||
|
buf.WriteString(fmt.Sprintf("\n Given team (%d): id=%d", i, t.Id))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range sc.updatePermissions {
|
||||||
|
r := "<nil>"
|
||||||
|
if p.Role != nil {
|
||||||
|
r = string(*p.Role)
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf("\n Update permission (%d): dashboardId=%d, userId=%d, teamId=%d, role=%v, permission=%s", i, p.DashboardId, p.UserId, p.TeamId, r, p.Permission.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.t.Fatalf(buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCustomUserPermission(dashboardID int64, userID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||||
|
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, UserId: userID, Permission: permission}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultUserPermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||||
|
return newCustomUserPermission(dashboardID, userID, permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCustomTeamPermission(dashboardID int64, teamID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||||
|
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, TeamId: teamID, Permission: permission}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultTeamPermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||||
|
return newCustomTeamPermission(dashboardID, teamID, permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAdminRolePermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||||
|
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, Role: &adminRole, Permission: permission}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEditorRolePermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||||
|
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, Role: &editorRole, Permission: permission}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newViewerRolePermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||||
|
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, Role: &viewerRole, Permission: permission}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toDto(acl *m.DashboardAcl) *m.DashboardAclInfoDTO {
|
||||||
|
return &m.DashboardAclInfoDTO{
|
||||||
|
OrgId: acl.OrgId,
|
||||||
|
DashboardId: acl.DashboardId,
|
||||||
|
UserId: acl.UserId,
|
||||||
|
TeamId: acl.TeamId,
|
||||||
|
Role: acl.Role,
|
||||||
|
Permission: acl.Permission,
|
||||||
|
PermissionName: acl.Permission.String(),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user