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.TeamDTO 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 apiKeyScenario(desc string, t *testing.T, role m.RoleType, fn scenarioFunc) { user := &m.SignedInUser{ UserId: 0, OrgId: orgID, OrgRole: role, ApiKeyId: 10, } 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.TeamDTO{} for _, p := range permissions { if p.TeamId > 0 { teams = append(teams, &m.TeamDTO{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, "") } 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 := "" 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 := "" 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(), } }