1package guardian 2 3import ( 4 "bytes" 5 "context" 6 "fmt" 7 "strings" 8 "testing" 9 10 "github.com/grafana/grafana/pkg/bus" 11 "github.com/grafana/grafana/pkg/models" 12 "github.com/stretchr/testify/assert" 13) 14 15type scenarioContext struct { 16 t *testing.T 17 orgRoleScenario string 18 permissionScenario string 19 g DashboardGuardian 20 givenUser *models.SignedInUser 21 givenDashboardID int64 22 givenPermissions []*models.DashboardAclInfoDTO 23 givenTeams []*models.TeamDTO 24 updatePermissions []*models.DashboardAcl 25 expectedFlags permissionFlags 26 callerFile string 27 callerLine int 28} 29 30type scenarioFunc func(c *scenarioContext) 31 32func orgRoleScenario(desc string, t *testing.T, role models.RoleType, fn scenarioFunc) { 33 t.Run(desc, func(t *testing.T) { 34 user := &models.SignedInUser{ 35 UserId: userID, 36 OrgId: orgID, 37 OrgRole: role, 38 } 39 guard := New(context.Background(), dashboardID, orgID, user) 40 41 sc := &scenarioContext{ 42 t: t, 43 orgRoleScenario: desc, 44 givenUser: user, 45 givenDashboardID: dashboardID, 46 g: guard, 47 } 48 fn(sc) 49 }) 50} 51 52func apiKeyScenario(desc string, t *testing.T, role models.RoleType, fn scenarioFunc) { 53 t.Run(desc, func(t *testing.T) { 54 user := &models.SignedInUser{ 55 UserId: 0, 56 OrgId: orgID, 57 OrgRole: role, 58 ApiKeyId: 10, 59 } 60 guard := New(context.Background(), dashboardID, orgID, user) 61 sc := &scenarioContext{ 62 t: t, 63 orgRoleScenario: desc, 64 givenUser: user, 65 givenDashboardID: dashboardID, 66 g: guard, 67 } 68 69 fn(sc) 70 }) 71} 72 73func permissionScenario(desc string, dashboardID int64, sc *scenarioContext, 74 permissions []*models.DashboardAclInfoDTO, fn scenarioFunc) { 75 sc.t.Run(desc, func(t *testing.T) { 76 bus.ClearBusHandlers() 77 78 bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error { 79 if query.OrgID != sc.givenUser.OrgId { 80 sc.reportFailure("Invalid organization id for GetDashboardAclInfoListQuery", sc.givenUser.OrgId, query.OrgID) 81 } 82 if query.DashboardID != sc.givenDashboardID { 83 sc.reportFailure("Invalid dashboard id for GetDashboardAclInfoListQuery", sc.givenDashboardID, query.DashboardID) 84 } 85 86 query.Result = permissions 87 return nil 88 }) 89 90 teams := []*models.TeamDTO{} 91 92 for _, p := range permissions { 93 if p.TeamId > 0 { 94 teams = append(teams, &models.TeamDTO{Id: p.TeamId}) 95 } 96 } 97 98 bus.AddHandler("test", func(query *models.GetTeamsByUserQuery) error { 99 if query.OrgId != sc.givenUser.OrgId { 100 sc.reportFailure("Invalid organization id for GetTeamsByUserQuery", sc.givenUser.OrgId, query.OrgId) 101 } 102 if query.UserId != sc.givenUser.UserId { 103 sc.reportFailure("Invalid user id for GetTeamsByUserQuery", sc.givenUser.UserId, query.UserId) 104 } 105 106 query.Result = teams 107 return nil 108 }) 109 110 sc.permissionScenario = desc 111 sc.g = New(context.Background(), dashboardID, sc.givenUser.OrgId, sc.givenUser) 112 sc.givenDashboardID = dashboardID 113 sc.givenPermissions = permissions 114 sc.givenTeams = teams 115 116 fn(sc) 117 }) 118} 119 120type permissionType uint8 121 122const ( 123 USER permissionType = 1 << iota 124 TEAM 125 EDITOR 126 VIEWER 127) 128 129func (p permissionType) String() string { 130 names := map[uint8]string{ 131 uint8(USER): "user", 132 uint8(TEAM): "team", 133 uint8(EDITOR): "editor role", 134 uint8(VIEWER): "viewer role", 135 } 136 return names[uint8(p)] 137} 138 139type permissionFlags uint8 140 141const ( 142 NO_ACCESS permissionFlags = 1 << iota 143 CAN_ADMIN 144 CAN_EDIT 145 CAN_SAVE 146 CAN_VIEW 147 FULL_ACCESS = CAN_ADMIN | CAN_EDIT | CAN_SAVE | CAN_VIEW 148 EDITOR_ACCESS = CAN_EDIT | CAN_SAVE | CAN_VIEW 149 VIEWER_ACCESS = CAN_VIEW 150) 151 152func (f permissionFlags) canAdmin() bool { 153 return f&CAN_ADMIN != 0 154} 155 156func (f permissionFlags) canEdit() bool { 157 return f&CAN_EDIT != 0 158} 159 160func (f permissionFlags) canSave() bool { 161 return f&CAN_SAVE != 0 162} 163 164func (f permissionFlags) canView() bool { 165 return f&CAN_VIEW != 0 166} 167 168func (f permissionFlags) noAccess() bool { 169 return f&(CAN_ADMIN|CAN_EDIT|CAN_SAVE|CAN_VIEW) == 0 170} 171 172func (f permissionFlags) String() string { 173 r := []string{} 174 175 if f.canAdmin() { 176 r = append(r, "admin") 177 } 178 179 if f.canEdit() { 180 r = append(r, "edit") 181 } 182 183 if f.canSave() { 184 r = append(r, "save") 185 } 186 187 if f.canView() { 188 r = append(r, "view") 189 } 190 191 if f.noAccess() { 192 r = append(r, "<no access>") 193 } 194 195 return strings.Join(r, ", ") 196} 197 198func (sc *scenarioContext) reportSuccess() { 199 assert.True(sc.t, true) 200} 201 202func (sc *scenarioContext) reportFailure(desc string, expected interface{}, actual interface{}) { 203 var buf bytes.Buffer 204 buf.WriteString("\n") 205 buf.WriteString(sc.orgRoleScenario) 206 buf.WriteString(" ") 207 buf.WriteString(sc.permissionScenario) 208 buf.WriteString("\n ") 209 buf.WriteString(desc) 210 buf.WriteString("\n") 211 buf.WriteString(fmt.Sprintf("Source test: %s:%d\n", sc.callerFile, sc.callerLine)) 212 buf.WriteString(fmt.Sprintf("Expected: %v\n", expected)) 213 buf.WriteString(fmt.Sprintf("Actual: %v\n", actual)) 214 buf.WriteString("Context:") 215 buf.WriteString(fmt.Sprintf("\n Given user: orgRole=%s, id=%d, orgId=%d", sc.givenUser.OrgRole, sc.givenUser.UserId, sc.givenUser.OrgId)) 216 buf.WriteString(fmt.Sprintf("\n Given dashboard id: %d", sc.givenDashboardID)) 217 218 for i, p := range sc.givenPermissions { 219 r := "<nil>" 220 if p.Role != nil { 221 r = string(*p.Role) 222 } 223 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())) 224 } 225 226 for i, t := range sc.givenTeams { 227 buf.WriteString(fmt.Sprintf("\n Given team (%d): id=%d", i, t.Id)) 228 } 229 230 for i, p := range sc.updatePermissions { 231 r := "<nil>" 232 if p.Role != nil { 233 r = string(*p.Role) 234 } 235 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())) 236 } 237 238 sc.t.Fatalf(buf.String()) 239} 240 241func newCustomUserPermission(dashboardID int64, userID int64, permission models.PermissionType) *models.DashboardAcl { 242 return &models.DashboardAcl{OrgID: orgID, DashboardID: dashboardID, UserID: userID, Permission: permission} 243} 244 245func newDefaultUserPermission(dashboardID int64, permission models.PermissionType) *models.DashboardAcl { 246 return newCustomUserPermission(dashboardID, userID, permission) 247} 248 249func newCustomTeamPermission(dashboardID int64, teamID int64, permission models.PermissionType) *models.DashboardAcl { 250 return &models.DashboardAcl{OrgID: orgID, DashboardID: dashboardID, TeamID: teamID, Permission: permission} 251} 252 253func newDefaultTeamPermission(dashboardID int64, permission models.PermissionType) *models.DashboardAcl { 254 return newCustomTeamPermission(dashboardID, teamID, permission) 255} 256 257func newAdminRolePermission(dashboardID int64, permission models.PermissionType) *models.DashboardAcl { 258 return &models.DashboardAcl{OrgID: orgID, DashboardID: dashboardID, Role: &adminRole, Permission: permission} 259} 260 261func newEditorRolePermission(dashboardID int64, permission models.PermissionType) *models.DashboardAcl { 262 return &models.DashboardAcl{OrgID: orgID, DashboardID: dashboardID, Role: &editorRole, Permission: permission} 263} 264 265func newViewerRolePermission(dashboardID int64, permission models.PermissionType) *models.DashboardAcl { 266 return &models.DashboardAcl{OrgID: orgID, DashboardID: dashboardID, Role: &viewerRole, Permission: permission} 267} 268 269func toDto(acl *models.DashboardAcl) *models.DashboardAclInfoDTO { 270 return &models.DashboardAclInfoDTO{ 271 OrgId: acl.OrgID, 272 DashboardId: acl.DashboardID, 273 UserId: acl.UserID, 274 TeamId: acl.TeamID, 275 Role: acl.Role, 276 Permission: acl.Permission, 277 PermissionName: acl.Permission.String(), 278 } 279} 280