1package api 2 3import ( 4 "context" 5 "net/http" 6 "net/http/httptest" 7 "testing" 8 9 "github.com/grafana/grafana/pkg/services/accesscontrol" 10 "github.com/grafana/grafana/pkg/services/provisioning" 11 "github.com/grafana/grafana/pkg/setting" 12 "github.com/stretchr/testify/assert" 13) 14 15type reloadProvisioningTestCase struct { 16 desc string 17 url string 18 expectedCode int 19 expectedBody string 20 permissions []*accesscontrol.Permission 21 exit bool 22 checkCall func(mock provisioning.ProvisioningServiceMock) 23} 24 25func TestAPI_AdminProvisioningReload_AccessControl(t *testing.T) { 26 tests := []reloadProvisioningTestCase{ 27 { 28 desc: "should work for dashboards with specific scope", 29 expectedCode: http.StatusOK, 30 expectedBody: `{"message":"Dashboards config reloaded"}`, 31 permissions: []*accesscontrol.Permission{ 32 { 33 Action: ActionProvisioningReload, 34 Scope: ScopeProvisionersDashboards, 35 }, 36 }, 37 url: "/api/admin/provisioning/dashboards/reload", 38 checkCall: func(mock provisioning.ProvisioningServiceMock) { 39 assert.Len(t, mock.Calls.ProvisionDashboards, 1) 40 }, 41 }, 42 { 43 desc: "should work for dashboards with broader scope", 44 expectedCode: http.StatusOK, 45 expectedBody: `{"message":"Dashboards config reloaded"}`, 46 permissions: []*accesscontrol.Permission{ 47 { 48 Action: ActionProvisioningReload, 49 Scope: ScopeProvisionersAll, 50 }, 51 }, 52 url: "/api/admin/provisioning/dashboards/reload", 53 checkCall: func(mock provisioning.ProvisioningServiceMock) { 54 assert.Len(t, mock.Calls.ProvisionDashboards, 1) 55 }, 56 }, 57 { 58 desc: "should fail for dashboard with wrong scope", 59 expectedCode: http.StatusForbidden, 60 permissions: []*accesscontrol.Permission{ 61 { 62 Action: ActionProvisioningReload, 63 Scope: "services:noservice", 64 }, 65 }, 66 url: "/api/admin/provisioning/dashboards/reload", 67 exit: true, 68 }, 69 { 70 desc: "should fail for dashboard with no permission", 71 expectedCode: http.StatusForbidden, 72 url: "/api/admin/provisioning/dashboards/reload", 73 exit: true, 74 }, 75 { 76 desc: "should work for notifications with specific scope", 77 expectedCode: http.StatusOK, 78 expectedBody: `{"message":"Notifications config reloaded"}`, 79 permissions: []*accesscontrol.Permission{ 80 { 81 Action: ActionProvisioningReload, 82 Scope: ScopeProvisionersNotifications, 83 }, 84 }, 85 url: "/api/admin/provisioning/notifications/reload", 86 checkCall: func(mock provisioning.ProvisioningServiceMock) { 87 assert.Len(t, mock.Calls.ProvisionNotifications, 1) 88 }, 89 }, 90 { 91 desc: "should fail for notifications with no permission", 92 expectedCode: http.StatusForbidden, 93 url: "/api/admin/provisioning/notifications/reload", 94 exit: true, 95 }, 96 { 97 desc: "should work for datasources with specific scope", 98 expectedCode: http.StatusOK, 99 expectedBody: `{"message":"Datasources config reloaded"}`, 100 permissions: []*accesscontrol.Permission{ 101 { 102 Action: ActionProvisioningReload, 103 Scope: ScopeProvisionersDatasources, 104 }, 105 }, 106 url: "/api/admin/provisioning/datasources/reload", 107 checkCall: func(mock provisioning.ProvisioningServiceMock) { 108 assert.Len(t, mock.Calls.ProvisionDatasources, 1) 109 }, 110 }, 111 { 112 desc: "should fail for datasources with no permission", 113 expectedCode: http.StatusForbidden, 114 url: "/api/admin/provisioning/datasources/reload", 115 exit: true, 116 }, 117 { 118 desc: "should work for plugins with specific scope", 119 expectedCode: http.StatusOK, 120 expectedBody: `{"message":"Plugins config reloaded"}`, 121 permissions: []*accesscontrol.Permission{ 122 { 123 Action: ActionProvisioningReload, 124 Scope: ScopeProvisionersPlugins, 125 }, 126 }, 127 url: "/api/admin/provisioning/plugins/reload", 128 checkCall: func(mock provisioning.ProvisioningServiceMock) { 129 assert.Len(t, mock.Calls.ProvisionPlugins, 1) 130 }, 131 }, 132 { 133 desc: "should fail for plugins with no permission", 134 expectedCode: http.StatusForbidden, 135 url: "/api/admin/provisioning/plugins/reload", 136 exit: true, 137 }, 138 } 139 140 cfg := setting.NewCfg() 141 142 for _, test := range tests { 143 t.Run(test.desc, func(t *testing.T) { 144 sc, hs := setupAccessControlScenarioContext(t, cfg, test.url, test.permissions) 145 146 // Setup the mock 147 provisioningMock := provisioning.NewProvisioningServiceMock(context.Background()) 148 hs.ProvisioningService = provisioningMock 149 150 sc.resp = httptest.NewRecorder() 151 var err error 152 sc.req, err = http.NewRequest(http.MethodPost, test.url, nil) 153 assert.NoError(t, err) 154 155 sc.exec() 156 157 // Check return code 158 assert.Equal(t, test.expectedCode, sc.resp.Code) 159 if test.exit { 160 return 161 } 162 163 // Check body 164 assert.Equal(t, test.expectedBody, sc.resp.Body.String()) 165 166 // Check we actually called the provisioning service 167 test.checkCall(*provisioningMock) 168 }) 169 } 170} 171