| 
									
										
										
										
											2017-05-08 21:35:34 +08:00
										 |  |  | package api | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-06-15 22:08:27 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2017-05-08 21:35:34 +08:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-23 23:43:32 +08:00
										 |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/api/dtos" | 
					
						
							| 
									
										
										
										
											2021-01-15 21:43:20 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/api/response" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/api/routing" | 
					
						
							| 
									
										
										
										
											2017-05-08 21:35:34 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/bus" | 
					
						
							| 
									
										
										
										
											2021-09-23 23:43:32 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/dashboards" | 
					
						
							| 
									
										
										
										
											2020-03-04 19:57:20 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/models" | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/guardian" | 
					
						
							| 
									
										
										
										
											2022-02-03 16:20:20 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2017-05-08 21:35:34 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | func TestDashboardPermissionAPIEndpoint(t *testing.T) { | 
					
						
							|  |  |  | 	t.Run("Dashboard permissions test", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 		settings := setting.NewCfg() | 
					
						
							|  |  |  | 		hs := &HTTPServer{Cfg: settings} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 		t.Run("Given dashboard not exists", func(t *testing.T) { | 
					
						
							|  |  |  | 			setUp := func() { | 
					
						
							| 
									
										
										
										
											2021-12-28 23:08:07 +08:00
										 |  |  | 				bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					return models.ErrDashboardNotFound | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-02-03 16:20:20 +08:00
										 |  |  | 			mock := mockstore.NewSQLStoreMock() | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				"/api/dashboards/id/:dashboardId/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					setUp() | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 					callGetDashboardPermissions(sc, hs) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					assert.Equal(t, 404, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2022-02-03 16:20:20 +08:00
										 |  |  | 				}, mock) | 
					
						
							| 
									
										
										
										
											2017-06-20 03:22:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			cmd := dtos.UpdateDashboardAclCommand{ | 
					
						
							|  |  |  | 				Items: []dtos.DashboardAclUpdateItem{ | 
					
						
							| 
									
										
										
										
											2020-11-18 00:09:14 +08:00
										 |  |  | 					{UserID: 1000, Permission: models.PERMISSION_ADMIN}, | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-05-08 21:35:34 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 			updateDashboardPermissionScenario(t, updatePermissionContext{ | 
					
						
							|  |  |  | 				desc:         "When calling POST on", | 
					
						
							|  |  |  | 				url:          "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				routePattern: "/api/dashboards/id/:dashboardId/permissions", | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				cmd:          cmd, | 
					
						
							|  |  |  | 				fn: func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					setUp() | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 					callUpdateDashboardPermissions(t, sc) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					assert.Equal(t, 404, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, hs) | 
					
						
							| 
									
										
										
										
											2017-06-12 21:48:55 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 		t.Run("Given user has no admin permissions", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			origNewGuardian := guardian.New | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			t.Cleanup(func() { | 
					
						
							|  |  |  | 				guardian.New = origNewGuardian | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false}) | 
					
						
							| 
									
										
										
										
											2018-01-30 20:28:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-04 19:57:20 +08:00
										 |  |  | 			getDashboardQueryResult := models.NewDashboard("Dash") | 
					
						
							| 
									
										
										
										
											2017-05-08 21:35:34 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			setUp := func() { | 
					
						
							| 
									
										
										
										
											2021-12-28 23:08:07 +08:00
										 |  |  | 				bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					query.Result = getDashboardQueryResult | 
					
						
							|  |  |  | 					return nil | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-02-03 16:20:20 +08:00
										 |  |  | 			mock := mockstore.NewSQLStoreMock() | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				"/api/dashboards/id/:dashboardId/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					setUp() | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 					callGetDashboardPermissions(sc, hs) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					assert.Equal(t, 403, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2022-02-03 16:20:20 +08:00
										 |  |  | 				}, mock) | 
					
						
							| 
									
										
										
										
											2018-02-12 16:26:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			cmd := dtos.UpdateDashboardAclCommand{ | 
					
						
							|  |  |  | 				Items: []dtos.DashboardAclUpdateItem{ | 
					
						
							| 
									
										
										
										
											2020-11-18 00:09:14 +08:00
										 |  |  | 					{UserID: 1000, Permission: models.PERMISSION_ADMIN}, | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-02-12 16:26:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 			updateDashboardPermissionScenario(t, updatePermissionContext{ | 
					
						
							|  |  |  | 				desc:         "When calling POST on", | 
					
						
							|  |  |  | 				url:          "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				routePattern: "/api/dashboards/id/:dashboardId/permissions", | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				cmd:          cmd, | 
					
						
							|  |  |  | 				fn: func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					setUp() | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 					callUpdateDashboardPermissions(t, sc) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					assert.Equal(t, 403, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, hs) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2018-02-12 16:26:09 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 		t.Run("Given user has admin permissions and permissions to update", func(t *testing.T) { | 
					
						
							|  |  |  | 			origNewGuardian := guardian.New | 
					
						
							|  |  |  | 			t.Cleanup(func() { | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 				guardian.New = origNewGuardian | 
					
						
							| 
									
										
										
										
											2018-02-12 16:26:09 +08:00
										 |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2017-05-08 21:35:34 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ | 
					
						
							|  |  |  | 				CanAdminValue:                    true, | 
					
						
							|  |  |  | 				CheckPermissionBeforeUpdateValue: true, | 
					
						
							| 
									
										
										
										
											2020-03-04 19:57:20 +08:00
										 |  |  | 				GetAclValue: []*models.DashboardAclInfoDTO{ | 
					
						
							|  |  |  | 					{OrgId: 1, DashboardId: 1, UserId: 2, Permission: models.PERMISSION_VIEW}, | 
					
						
							|  |  |  | 					{OrgId: 1, DashboardId: 1, UserId: 3, Permission: models.PERMISSION_EDIT}, | 
					
						
							|  |  |  | 					{OrgId: 1, DashboardId: 1, UserId: 4, Permission: models.PERMISSION_ADMIN}, | 
					
						
							|  |  |  | 					{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: models.PERMISSION_VIEW}, | 
					
						
							|  |  |  | 					{OrgId: 1, DashboardId: 1, TeamId: 2, Permission: models.PERMISSION_ADMIN}, | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2017-05-22 16:36:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			setUp := func() { | 
					
						
							|  |  |  | 				getDashboardQueryResult := models.NewDashboard("Dash") | 
					
						
							| 
									
										
										
										
											2021-12-28 23:08:07 +08:00
										 |  |  | 				bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					query.Result = getDashboardQueryResult | 
					
						
							|  |  |  | 					return nil | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-02-03 16:20:20 +08:00
										 |  |  | 			mock := mockstore.NewSQLStoreMock() | 
					
						
							| 
									
										
										
										
											2017-05-22 16:36:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				"/api/dashboards/id/:dashboardId/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					setUp() | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 					callGetDashboardPermissions(sc, hs) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					assert.Equal(t, 200, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					var resp []*models.DashboardAclInfoDTO | 
					
						
							|  |  |  | 					err := json.Unmarshal(sc.resp.Body.Bytes(), &resp) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					assert.Len(t, resp, 5) | 
					
						
							|  |  |  | 					assert.Equal(t, int64(2), resp[0].UserId) | 
					
						
							|  |  |  | 					assert.Equal(t, models.PERMISSION_VIEW, resp[0].Permission) | 
					
						
							| 
									
										
										
										
											2022-02-03 16:20:20 +08:00
										 |  |  | 				}, mock) | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			cmd := dtos.UpdateDashboardAclCommand{ | 
					
						
							|  |  |  | 				Items: []dtos.DashboardAclUpdateItem{ | 
					
						
							| 
									
										
										
										
											2020-11-18 00:09:14 +08:00
										 |  |  | 					{UserID: 1000, Permission: models.PERMISSION_ADMIN}, | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 			updateDashboardPermissionScenario(t, updatePermissionContext{ | 
					
						
							|  |  |  | 				desc:         "When calling POST on", | 
					
						
							|  |  |  | 				url:          "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				routePattern: "/api/dashboards/id/:dashboardId/permissions", | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				cmd:          cmd, | 
					
						
							|  |  |  | 				fn: func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					setUp() | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 					callUpdateDashboardPermissions(t, sc) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					assert.Equal(t, 200, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, hs) | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-14 02:16:58 +08:00
										 |  |  | 		t.Run("When trying to add permissions with both a team and user", func(t *testing.T) { | 
					
						
							|  |  |  | 			origNewGuardian := guardian.New | 
					
						
							|  |  |  | 			t.Cleanup(func() { | 
					
						
							|  |  |  | 				guardian.New = origNewGuardian | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ | 
					
						
							|  |  |  | 				CanAdminValue:                    true, | 
					
						
							|  |  |  | 				CheckPermissionBeforeUpdateValue: true, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			setUp := func() { | 
					
						
							|  |  |  | 				getDashboardQueryResult := models.NewDashboard("Dash") | 
					
						
							| 
									
										
										
										
											2021-12-28 23:08:07 +08:00
										 |  |  | 				bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { | 
					
						
							| 
									
										
										
										
											2021-10-14 02:16:58 +08:00
										 |  |  | 					query.Result = getDashboardQueryResult | 
					
						
							|  |  |  | 					return nil | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			cmd := dtos.UpdateDashboardAclCommand{ | 
					
						
							|  |  |  | 				Items: []dtos.DashboardAclUpdateItem{ | 
					
						
							|  |  |  | 					{UserID: 1000, TeamID: 1, Permission: models.PERMISSION_ADMIN}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			updateDashboardPermissionScenario(t, updatePermissionContext{ | 
					
						
							|  |  |  | 				desc:         "When calling POST on", | 
					
						
							|  |  |  | 				url:          "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				routePattern: "/api/dashboards/id/:dashboardId/permissions", | 
					
						
							| 
									
										
										
										
											2021-10-14 02:16:58 +08:00
										 |  |  | 				cmd:          cmd, | 
					
						
							|  |  |  | 				fn: func(sc *scenarioContext) { | 
					
						
							|  |  |  | 					setUp() | 
					
						
							|  |  |  | 					callUpdateDashboardPermissions(t, sc) | 
					
						
							|  |  |  | 					assert.Equal(t, 400, sc.resp.Code) | 
					
						
							|  |  |  | 					respJSON, err := jsonMap(sc.resp.Body.Bytes()) | 
					
						
							|  |  |  | 					require.NoError(t, err) | 
					
						
							|  |  |  | 					assert.Equal(t, models.ErrPermissionsWithUserAndTeamNotAllowed.Error(), respJSON["error"]) | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, hs) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 		t.Run("When trying to update permissions with duplicate permissions", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			origNewGuardian := guardian.New | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			t.Cleanup(func() { | 
					
						
							|  |  |  | 				guardian.New = origNewGuardian | 
					
						
							|  |  |  | 			}) | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ | 
					
						
							|  |  |  | 				CanAdminValue:                    true, | 
					
						
							|  |  |  | 				CheckPermissionBeforeUpdateValue: false, | 
					
						
							| 
									
										
										
										
											2018-02-27 23:03:11 +08:00
										 |  |  | 				CheckPermissionBeforeUpdateError: guardian.ErrGuardianPermissionExists, | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			setUp := func() { | 
					
						
							|  |  |  | 				getDashboardQueryResult := models.NewDashboard("Dash") | 
					
						
							| 
									
										
										
										
											2021-12-28 23:08:07 +08:00
										 |  |  | 				bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					query.Result = getDashboardQueryResult | 
					
						
							|  |  |  | 					return nil | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-05-22 16:36:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			cmd := dtos.UpdateDashboardAclCommand{ | 
					
						
							|  |  |  | 				Items: []dtos.DashboardAclUpdateItem{ | 
					
						
							| 
									
										
										
										
											2020-11-18 00:09:14 +08:00
										 |  |  | 					{UserID: 1000, Permission: models.PERMISSION_ADMIN}, | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-06-23 05:01:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 			updateDashboardPermissionScenario(t, updatePermissionContext{ | 
					
						
							|  |  |  | 				desc:         "When calling POST on", | 
					
						
							|  |  |  | 				url:          "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				routePattern: "/api/dashboards/id/:dashboardId/permissions", | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				cmd:          cmd, | 
					
						
							|  |  |  | 				fn: func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					setUp() | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 					callUpdateDashboardPermissions(t, sc) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					assert.Equal(t, 400, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, hs) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2017-06-23 05:01:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-18 22:36:41 +08:00
										 |  |  | 		t.Run("When trying to update team or user permissions with a role", func(t *testing.T) { | 
					
						
							|  |  |  | 			role := models.ROLE_EDITOR | 
					
						
							|  |  |  | 			cmds := []dtos.UpdateDashboardAclCommand{ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					Items: []dtos.DashboardAclUpdateItem{ | 
					
						
							| 
									
										
										
										
											2020-11-18 23:16:58 +08:00
										 |  |  | 						{UserID: 1000, Permission: models.PERMISSION_ADMIN, Role: &role}, | 
					
						
							| 
									
										
										
										
											2020-11-18 22:36:41 +08:00
										 |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					Items: []dtos.DashboardAclUpdateItem{ | 
					
						
							| 
									
										
										
										
											2020-11-18 23:16:58 +08:00
										 |  |  | 						{TeamID: 1000, Permission: models.PERMISSION_ADMIN, Role: &role}, | 
					
						
							| 
									
										
										
										
											2020-11-18 22:36:41 +08:00
										 |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for _, cmd := range cmds { | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				updateDashboardPermissionScenario(t, updatePermissionContext{ | 
					
						
							|  |  |  | 					desc:         "When calling POST on", | 
					
						
							|  |  |  | 					url:          "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 					routePattern: "/api/dashboards/id/:dashboardId/permissions", | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 					cmd:          cmd, | 
					
						
							|  |  |  | 					fn: func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 						callUpdateDashboardPermissions(t, sc) | 
					
						
							| 
									
										
										
										
											2020-11-18 22:36:41 +08:00
										 |  |  | 						assert.Equal(t, 400, sc.resp.Code) | 
					
						
							|  |  |  | 						respJSON, err := jsonMap(sc.resp.Body.Bytes()) | 
					
						
							|  |  |  | 						require.NoError(t, err) | 
					
						
							|  |  |  | 						assert.Equal(t, models.ErrPermissionsWithRoleNotAllowed.Error(), respJSON["error"]) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 					}, | 
					
						
							|  |  |  | 				}, hs) | 
					
						
							| 
									
										
										
										
											2020-11-18 22:36:41 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 		t.Run("When trying to override inherited permissions with lower precedence", func(t *testing.T) { | 
					
						
							|  |  |  | 			origNewGuardian := guardian.New | 
					
						
							|  |  |  | 			t.Cleanup(func() { | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 				guardian.New = origNewGuardian | 
					
						
							| 
									
										
										
										
											2017-06-23 05:01:04 +08:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ | 
					
						
							|  |  |  | 				CanAdminValue:                    true, | 
					
						
							|  |  |  | 				CheckPermissionBeforeUpdateValue: false, | 
					
						
							| 
									
										
										
										
											2018-02-27 23:03:11 +08:00
										 |  |  | 				CheckPermissionBeforeUpdateError: guardian.ErrGuardianOverride}, | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 			) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			setUp := func() { | 
					
						
							|  |  |  | 				getDashboardQueryResult := models.NewDashboard("Dash") | 
					
						
							| 
									
										
										
										
											2021-12-28 23:08:07 +08:00
										 |  |  | 				bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					query.Result = getDashboardQueryResult | 
					
						
							|  |  |  | 					return nil | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			cmd := dtos.UpdateDashboardAclCommand{ | 
					
						
							|  |  |  | 				Items: []dtos.DashboardAclUpdateItem{ | 
					
						
							| 
									
										
										
										
											2020-11-18 00:09:14 +08:00
										 |  |  | 					{UserID: 1000, Permission: models.PERMISSION_ADMIN}, | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-05-22 16:36:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 			updateDashboardPermissionScenario(t, updatePermissionContext{ | 
					
						
							|  |  |  | 				desc:         "When calling POST on", | 
					
						
							|  |  |  | 				url:          "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				routePattern: "/api/dashboards/id/:dashboardId/permissions", | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				cmd:          cmd, | 
					
						
							|  |  |  | 				fn: func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					setUp() | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 					callUpdateDashboardPermissions(t, sc) | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 					assert.Equal(t, 400, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, hs) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run("Getting and updating dashboard permissions with hidden users", func(t *testing.T) { | 
					
						
							|  |  |  | 			origNewGuardian := guardian.New | 
					
						
							|  |  |  | 			settings.HiddenUsers = map[string]struct{}{ | 
					
						
							|  |  |  | 				"hiddenUser":  {}, | 
					
						
							|  |  |  | 				testUserLogin: {}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			t.Cleanup(func() { | 
					
						
							|  |  |  | 				guardian.New = origNewGuardian | 
					
						
							|  |  |  | 				settings.HiddenUsers = make(map[string]struct{}) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{ | 
					
						
							|  |  |  | 				CanAdminValue:                    true, | 
					
						
							|  |  |  | 				CheckPermissionBeforeUpdateValue: true, | 
					
						
							|  |  |  | 				GetAclValue: []*models.DashboardAclInfoDTO{ | 
					
						
							|  |  |  | 					{OrgId: 1, DashboardId: 1, UserId: 2, UserLogin: "hiddenUser", Permission: models.PERMISSION_VIEW}, | 
					
						
							|  |  |  | 					{OrgId: 1, DashboardId: 1, UserId: 3, UserLogin: testUserLogin, Permission: models.PERMISSION_EDIT}, | 
					
						
							|  |  |  | 					{OrgId: 1, DashboardId: 1, UserId: 4, UserLogin: "user_1", Permission: models.PERMISSION_ADMIN}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				GetHiddenAclValue: []*models.DashboardAcl{ | 
					
						
							|  |  |  | 					{OrgID: 1, DashboardID: 1, UserID: 2, Permission: models.PERMISSION_VIEW}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			setUp := func() { | 
					
						
							|  |  |  | 				getDashboardQueryResult := models.NewDashboard("Dash") | 
					
						
							| 
									
										
										
										
											2021-12-28 23:08:07 +08:00
										 |  |  | 				bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 					query.Result = getDashboardQueryResult | 
					
						
							|  |  |  | 					return nil | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-02-03 16:20:20 +08:00
										 |  |  | 			mock := mockstore.NewSQLStoreMock() | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 			var resp []*models.DashboardAclInfoDTO | 
					
						
							|  |  |  | 			loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				"/api/dashboards/id/:dashboardId/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) { | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 					setUp() | 
					
						
							|  |  |  | 					callGetDashboardPermissions(sc, hs) | 
					
						
							|  |  |  | 					assert.Equal(t, 200, sc.resp.Code) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					err := json.Unmarshal(sc.resp.Body.Bytes(), &resp) | 
					
						
							|  |  |  | 					require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					assert.Len(t, resp, 2) | 
					
						
							|  |  |  | 					assert.Equal(t, int64(3), resp[0].UserId) | 
					
						
							|  |  |  | 					assert.Equal(t, models.PERMISSION_EDIT, resp[0].Permission) | 
					
						
							|  |  |  | 					assert.Equal(t, int64(4), resp[1].UserId) | 
					
						
							|  |  |  | 					assert.Equal(t, models.PERMISSION_ADMIN, resp[1].Permission) | 
					
						
							| 
									
										
										
										
											2022-02-03 16:20:20 +08:00
										 |  |  | 				}, mock) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			cmd := dtos.UpdateDashboardAclCommand{ | 
					
						
							|  |  |  | 				Items: []dtos.DashboardAclUpdateItem{ | 
					
						
							|  |  |  | 					{UserID: 1000, Permission: models.PERMISSION_ADMIN}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			for _, acl := range resp { | 
					
						
							|  |  |  | 				cmd.Items = append(cmd.Items, dtos.DashboardAclUpdateItem{ | 
					
						
							|  |  |  | 					UserID:     acl.UserId, | 
					
						
							|  |  |  | 					Permission: acl.Permission, | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			assert.Len(t, cmd.Items, 3) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			updateDashboardPermissionScenario(t, updatePermissionContext{ | 
					
						
							|  |  |  | 				desc:         "When calling POST on", | 
					
						
							|  |  |  | 				url:          "/api/dashboards/id/1/permissions", | 
					
						
							| 
									
										
										
										
											2022-01-15 00:55:57 +08:00
										 |  |  | 				routePattern: "/api/dashboards/id/:dashboardId/permissions", | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				cmd:          cmd, | 
					
						
							|  |  |  | 				fn: func(sc *scenarioContext) { | 
					
						
							|  |  |  | 					setUp() | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 					// TODO: Replace this fake with a fake SQLStore instead (once we can use an interface in its stead)
 | 
					
						
							|  |  |  | 					origUpdateDashboardACL := updateDashboardACL | 
					
						
							|  |  |  | 					t.Cleanup(func() { | 
					
						
							|  |  |  | 						updateDashboardACL = origUpdateDashboardACL | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 					}) | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 					var gotItems []*models.DashboardAcl | 
					
						
							| 
									
										
										
										
											2021-09-23 23:43:32 +08:00
										 |  |  | 					updateDashboardACL = func(_ context.Context, _ dashboards.Store, folderID int64, items []*models.DashboardAcl) error { | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 						gotItems = items | 
					
						
							|  |  |  | 						return nil | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 					sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() | 
					
						
							|  |  |  | 					assert.Equal(t, 200, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 					assert.Len(t, gotItems, 4) | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 				}, | 
					
						
							|  |  |  | 			}, hs) | 
					
						
							| 
									
										
										
										
											2017-05-08 21:35:34 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-06-23 05:01:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | func callGetDashboardPermissions(sc *scenarioContext, hs *HTTPServer) { | 
					
						
							|  |  |  | 	sc.handlerFunc = hs.GetDashboardPermissionList | 
					
						
							| 
									
										
										
										
											2018-02-27 03:15:57 +08:00
										 |  |  | 	sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() | 
					
						
							| 
									
										
										
										
											2017-06-23 05:01:04 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | func callUpdateDashboardPermissions(t *testing.T, sc *scenarioContext) { | 
					
						
							|  |  |  | 	t.Helper() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	origUpdateDashboardACL := updateDashboardACL | 
					
						
							|  |  |  | 	t.Cleanup(func() { | 
					
						
							|  |  |  | 		updateDashboardACL = origUpdateDashboardACL | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-09-23 23:43:32 +08:00
										 |  |  | 	updateDashboardACL = func(_ context.Context, _ dashboards.Store, dashID int64, items []*models.DashboardAcl) error { | 
					
						
							| 
									
										
										
										
											2021-03-17 23:06:10 +08:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | type updatePermissionContext struct { | 
					
						
							|  |  |  | 	desc         string | 
					
						
							|  |  |  | 	url          string | 
					
						
							|  |  |  | 	routePattern string | 
					
						
							|  |  |  | 	cmd          dtos.UpdateDashboardAclCommand | 
					
						
							|  |  |  | 	fn           scenarioFunc | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func updateDashboardPermissionScenario(t *testing.T, ctx updatePermissionContext, hs *HTTPServer) { | 
					
						
							|  |  |  | 	t.Run(fmt.Sprintf("%s %s", ctx.desc, ctx.url), func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 		t.Cleanup(bus.ClearBusHandlers) | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 		sc := setupScenarioContext(t, ctx.url) | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-15 21:43:20 +08:00
										 |  |  | 		sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { | 
					
						
							| 
									
										
										
										
											2021-11-29 17:18:01 +08:00
										 |  |  | 			c.Req.Body = mockRequestBody(ctx.cmd) | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 			sc.context = c | 
					
						
							| 
									
										
										
										
											2020-11-13 16:52:38 +08:00
										 |  |  | 			sc.context.OrgId = testOrgID | 
					
						
							|  |  |  | 			sc.context.UserId = testUserID | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-29 17:18:01 +08:00
										 |  |  | 			return hs.UpdateDashboardPermissions(c) | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 		sc.m.Post(ctx.routePattern, sc.defaultHandler) | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-24 19:10:32 +08:00
										 |  |  | 		ctx.fn(sc) | 
					
						
							| 
									
										
										
										
											2018-01-18 21:30:04 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | } |