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