mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Security: Sync security changes on main (#45083)
* * Teams: Appropriately apply user id filter in /api/teams/:id and /api/teams/search * Teams: Ensure that users searching for teams are only able see teams they have access to * Teams: Require teamGuardian admin privileges to list team members * Teams: Prevent org viewers from administering teams * Teams: Add org_id condition to team count query * Teams: clarify permission requirements in teams api docs * Teams: expand scenarios for team search tests * Teams: mock teamGuardian in tests Co-authored-by: Dan Cech <dcech@grafana.com> * remove duplicate WHERE statement * Fix for CVE-2022-21702 (cherry picked from commit 202d7c190082c094bc1dc13f7fe9464746c37f9e) * Lint and test fixes (cherry picked from commit 3e6b67d5504abf4a1d7b8d621f04d062c048e981) * check content type properly (cherry picked from commit 70b4458892bf2f776302720c10d24c9ff34edd98) * basic csrf origin check (cherry picked from commit 3adaa5ff39832364f6390881fb5b42ad47df92e1) * compare origin to host (cherry picked from commit 5443892699e8ed42836bb2b9a44744ff3e970f42) * simplify url parsing (cherry picked from commit b2ffbc9513fed75468628370a48b929d30af2b1d) * check csrf for GET requests, only compare origin (cherry picked from commit 8b81dc12d8f8a1f07852809c5b4d44f0f0b1d709) * parse content type properly (cherry picked from commit 16f76f4902e6f2188bea9606c68b551af186bdc0) * mentioned get in the comment (cherry picked from commit a7e61811ef8ae558ce721e2e3fed04ce7a5a5345) * add content-type: application/json to test HTTP requests * fix pluginproxy test * Fix linter when comparing errors Co-authored-by: Kevin Minehart <kmineh0151@gmail.com> Co-authored-by: Dan Cech <dcech@grafana.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> Co-authored-by: Serge Zaitsev <serge.zaitsev@grafana.com> Co-authored-by: Vardan Torosyan <vardants@gmail.com>
This commit is contained in:
committed by
GitHub
parent
d3d7411e36
commit
605d056136
@@ -7,7 +7,13 @@ aliases = ["/docs/grafana/latest/http_api/team/"]
|
|||||||
|
|
||||||
# Team API
|
# Team API
|
||||||
|
|
||||||
This API can be used to create/update/delete Teams and to add/remove users to Teams. All actions require that the user has the Admin role for the organization.
|
This API can be used to manage Teams and Team Memberships.
|
||||||
|
|
||||||
|
Access to these API endpoints is restricted as follows:
|
||||||
|
|
||||||
|
- All authenticated users are able to view details of teams they are a member of.
|
||||||
|
- Organization Admins are able to manage all teams and team members.
|
||||||
|
- If the `editors_can_admin` configuration flag is enabled, Organization Editors are able to view details of all teams and to manage teams that they are Admin members of.
|
||||||
|
|
||||||
## Team Search With Paging
|
## Team Search With Paging
|
||||||
|
|
||||||
|
@@ -288,6 +288,7 @@ func putAdminScenario(t *testing.T, desc string, url string, routePattern string
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
sc.context.OrgId = testOrgID
|
sc.context.OrgId = testOrgID
|
||||||
@@ -346,6 +347,7 @@ func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, rou
|
|||||||
sc.userAuthTokenService = fakeAuthTokenService
|
sc.userAuthTokenService = fakeAuthTokenService
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
sc.context.OrgId = testOrgID
|
sc.context.OrgId = testOrgID
|
||||||
@@ -464,6 +466,7 @@ func adminCreateUserScenario(t *testing.T, desc string, url string, routePattern
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
|
|
||||||
|
@@ -144,6 +144,7 @@ func postAlertScenario(t *testing.T, hs *HTTPServer, desc string, url string, ro
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
sc.context.OrgId = testOrgID
|
sc.context.OrgId = testOrgID
|
||||||
|
@@ -279,6 +279,7 @@ func postAnnotationScenario(t *testing.T, desc string, url string, routePattern
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
sc.context.OrgId = testOrgID
|
sc.context.OrgId = testOrgID
|
||||||
@@ -304,6 +305,7 @@ func putAnnotationScenario(t *testing.T, desc string, url string, routePattern s
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
sc.context.OrgId = testOrgID
|
sc.context.OrgId = testOrgID
|
||||||
@@ -328,6 +330,7 @@ func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
sc.context.OrgId = testOrgID
|
sc.context.OrgId = testOrgID
|
||||||
@@ -353,6 +356,7 @@ func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePatte
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
sc.context.OrgId = testOrgID
|
sc.context.OrgId = testOrgID
|
||||||
|
@@ -27,7 +27,7 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
reqEditorRole := middleware.ReqEditorRole
|
reqEditorRole := middleware.ReqEditorRole
|
||||||
reqOrgAdmin := middleware.ReqOrgAdmin
|
reqOrgAdmin := middleware.ReqOrgAdmin
|
||||||
reqOrgAdminFolderAdminOrTeamAdmin := middleware.OrgAdminFolderAdminOrTeamAdmin
|
reqOrgAdminFolderAdminOrTeamAdmin := middleware.OrgAdminFolderAdminOrTeamAdmin
|
||||||
reqCanAccessTeams := middleware.AdminOrFeatureEnabled(hs.Cfg.EditorsCanAdmin)
|
reqCanAccessTeams := middleware.AdminOrEditorAndFeatureEnabled(hs.Cfg.EditorsCanAdmin)
|
||||||
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
|
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
|
||||||
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
|
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
|
||||||
authorize := acmiddleware.Middleware(hs.AccessControl)
|
authorize := acmiddleware.Middleware(hs.AccessControl)
|
||||||
|
@@ -102,6 +102,7 @@ func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
|
|||||||
sc.resp = httptest.NewRecorder()
|
sc.resp = httptest.NewRecorder()
|
||||||
req, err := http.NewRequest(method, url, nil)
|
req, err := http.NewRequest(method, url, nil)
|
||||||
require.NoError(sc.t, err)
|
require.NoError(sc.t, err)
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
sc.req = req
|
sc.req = req
|
||||||
|
|
||||||
return sc
|
return sc
|
||||||
@@ -117,6 +118,8 @@ func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map
|
|||||||
panic(fmt.Sprintf("Making request failed: %s", err))
|
panic(fmt.Sprintf("Making request failed: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
q := req.URL.Query()
|
q := req.URL.Query()
|
||||||
for k, v := range queryParams {
|
for k, v := range queryParams {
|
||||||
q.Add(k, v)
|
q.Add(k, v)
|
||||||
@@ -129,6 +132,7 @@ func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map
|
|||||||
func (sc *scenarioContext) fakeReqNoAssertions(method, url string) *scenarioContext {
|
func (sc *scenarioContext) fakeReqNoAssertions(method, url string) *scenarioContext {
|
||||||
sc.resp = httptest.NewRecorder()
|
sc.resp = httptest.NewRecorder()
|
||||||
req, _ := http.NewRequest(method, url, nil)
|
req, _ := http.NewRequest(method, url, nil)
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
sc.req = req
|
sc.req = req
|
||||||
|
|
||||||
return sc
|
return sc
|
||||||
@@ -140,7 +144,7 @@ func (sc *scenarioContext) fakeReqNoAssertionsWithCookie(method, url string, coo
|
|||||||
|
|
||||||
req, _ := http.NewRequest(method, url, nil)
|
req, _ := http.NewRequest(method, url, nil)
|
||||||
req.Header = http.Header{"Cookie": sc.resp.Header()["Set-Cookie"]}
|
req.Header = http.Header{"Cookie": sc.resp.Header()["Set-Cookie"]}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
sc.req = req
|
sc.req = req
|
||||||
|
|
||||||
return sc
|
return sc
|
||||||
|
@@ -383,6 +383,7 @@ func updateDashboardPermissionScenario(t *testing.T, ctx updatePermissionContext
|
|||||||
|
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(ctx.cmd)
|
c.Req.Body = mockRequestBody(ctx.cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.OrgId = testOrgID
|
sc.context.OrgId = testOrgID
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
|
@@ -36,6 +36,7 @@ import (
|
|||||||
func TestGetHomeDashboard(t *testing.T) {
|
func TestGetHomeDashboard(t *testing.T) {
|
||||||
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
|
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
httpReq.Header.Add("Content-Type", "application/json")
|
||||||
req := &models.ReqContext{SignedInUser: &models.SignedInUser{}, Context: &web.Context{Req: httpReq}}
|
req := &models.ReqContext{SignedInUser: &models.SignedInUser{}, Context: &web.Context{Req: httpReq}}
|
||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
cfg.StaticRootPath = "../../public/"
|
cfg.StaticRootPath = "../../public/"
|
||||||
@@ -1023,6 +1024,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.SignedInUser = &models.SignedInUser{OrgId: cmd.OrgId, UserId: cmd.UserId}
|
sc.context.SignedInUser = &models.SignedInUser{OrgId: cmd.OrgId, UserId: cmd.UserId}
|
||||||
|
|
||||||
@@ -1056,6 +1058,7 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.SignedInUser = &models.SignedInUser{
|
sc.context.SignedInUser = &models.SignedInUser{
|
||||||
OrgId: testOrgID,
|
OrgId: testOrgID,
|
||||||
@@ -1095,6 +1098,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
|
|||||||
sc.sqlStore = mockSQLStore
|
sc.sqlStore = mockSQLStore
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.SignedInUser = &models.SignedInUser{
|
sc.context.SignedInUser = &models.SignedInUser{
|
||||||
OrgId: testOrgID,
|
OrgId: testOrgID,
|
||||||
|
@@ -405,6 +405,7 @@ func updateFolderPermissionScenario(t *testing.T, ctx updatePermissionContext, h
|
|||||||
|
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(ctx.cmd)
|
c.Req.Body = mockRequestBody(ctx.cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.OrgId = testOrgID
|
sc.context.OrgId = testOrgID
|
||||||
sc.context.UserId = testUserID
|
sc.context.UserId = testUserID
|
||||||
|
@@ -149,6 +149,7 @@ func createFolderScenario(t *testing.T, desc string, url string, routePattern st
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
|
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
|
||||||
|
|
||||||
@@ -184,6 +185,7 @@ func updateFolderScenario(t *testing.T, desc string, url string, routePattern st
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
|
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
|
||||||
|
|
||||||
|
@@ -92,6 +92,7 @@ func logSentryEventScenario(t *testing.T, desc string, event frontendlogging.Fro
|
|||||||
handler := routing.Wrap(func(c *models.ReqContext) response.Response {
|
handler := routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
sc.context = c
|
sc.context = c
|
||||||
c.Req.Body = mockRequestBody(event)
|
c.Req.Body = mockRequestBody(event)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
return loggingHandler(c)
|
return loggingHandler(c)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -444,6 +444,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.Use(middleware.Recovery(hs.Cfg))
|
m.Use(middleware.Recovery(hs.Cfg))
|
||||||
|
m.UseMiddleware(middleware.CSRF(hs.Cfg.LoginCookieName))
|
||||||
|
|
||||||
hs.mapStatic(m, hs.Cfg.StaticRootPath, "build", "public/build")
|
hs.mapStatic(m, hs.Cfg.StaticRootPath, "build", "public/build")
|
||||||
hs.mapStatic(m, hs.Cfg.StaticRootPath, "", "public", "/public/views/swagger.html")
|
hs.mapStatic(m, hs.Cfg.StaticRootPath, "", "public", "/public/views/swagger.html")
|
||||||
|
@@ -56,6 +56,7 @@ func (t *handleResponseTransport) RoundTrip(req *http.Request) (*http.Response,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res.Header.Del("Set-Cookie")
|
res.Header.Del("Set-Cookie")
|
||||||
|
proxyutil.SetProxyResponseHeaders(res.Header)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -670,6 +670,20 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
|||||||
assert.Equal(t, "important_cookie=important_value", proxy.ctx.Resp.Header().Get("Set-Cookie"))
|
assert.Equal(t, "important_cookie=important_value", proxy.ctx.Resp.Header().Get("Set-Cookie"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("When response should set Content-Security-Policy header", func(t *testing.T) {
|
||||||
|
ctx, ds := setUp(t)
|
||||||
|
var routes []*plugins.Route
|
||||||
|
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||||
|
dsService := datasources.ProvideService(bus.New(), nil, secretsService, &acmock.Mock{})
|
||||||
|
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
proxy.HandleRequest()
|
||||||
|
|
||||||
|
require.NoError(t, writeErr)
|
||||||
|
assert.Equal(t, "sandbox", proxy.ctx.Resp.Header().Get("Content-Security-Policy"))
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Data source returns status code 401", func(t *testing.T) {
|
t.Run("Data source returns status code 401", func(t *testing.T) {
|
||||||
ctx, ds := setUp(t, setUpCfg{
|
ctx, ds := setUp(t, setUpCfg{
|
||||||
writeCb: func(w http.ResponseWriter, r *http.Request) {
|
writeCb: func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@@ -83,5 +83,11 @@ func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &httputil.ReverseProxy{Director: director}
|
return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyResponse}
|
||||||
|
}
|
||||||
|
|
||||||
|
func modifyResponse(resp *http.Response) error {
|
||||||
|
proxyutil.SetProxyResponseHeaders(resp.Header)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@@ -238,6 +239,46 @@ func TestPluginProxy(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, `{ "url": "https://dynamic.grafana.com", "secret": "123" }`, string(content))
|
require.Equal(t, `{ "url": "https://dynamic.grafana.com", "secret": "123" }`, string(content))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("When proxying a request should set expected response headers", func(t *testing.T) {
|
||||||
|
requestHandled := false
|
||||||
|
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
_, _ = w.Write([]byte("I am the backend"))
|
||||||
|
requestHandled = true
|
||||||
|
}))
|
||||||
|
t.Cleanup(backendServer.Close)
|
||||||
|
|
||||||
|
responseWriter := web.NewResponseWriter("GET", httptest.NewRecorder())
|
||||||
|
|
||||||
|
route := &plugins.Route{
|
||||||
|
Path: "/",
|
||||||
|
URL: backendServer.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := &models.ReqContext{
|
||||||
|
SignedInUser: &models.SignedInUser{},
|
||||||
|
Context: &web.Context{
|
||||||
|
Req: httptest.NewRequest("GET", "/", nil),
|
||||||
|
Resp: responseWriter,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
store := mockstore.NewSQLStoreMock()
|
||||||
|
|
||||||
|
store.ExpectedPluginSetting = &models.PluginSetting{
|
||||||
|
SecureJsonData: map[string][]byte{},
|
||||||
|
}
|
||||||
|
proxy := NewApiPluginProxy(ctx, "", route, "", &setting.Cfg{}, store, secretsService)
|
||||||
|
proxy.ServeHTTP(ctx.Resp, ctx.Req)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if requestHandled {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, "sandbox", ctx.Resp.Header().Get("Content-Security-Policy"))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPluginProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
|
// getPluginProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
|
||||||
|
@@ -613,6 +613,9 @@ func (hs *HTTPServer) flushStream(stream callResourceClientResponseStream, w htt
|
|||||||
w.Header().Add(k, v)
|
w.Header().Add(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxyutil.SetProxyResponseHeaders(w.Header())
|
||||||
|
|
||||||
w.WriteHeader(resp.Status)
|
w.WriteHeader(resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -11,6 +14,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@@ -185,6 +189,28 @@ func Test_GetPluginAssets(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMakePluginResourceRequest(t *testing.T) {
|
||||||
|
pluginClient := &fakePluginClient{}
|
||||||
|
hs := HTTPServer{
|
||||||
|
Cfg: setting.NewCfg(),
|
||||||
|
log: log.New(),
|
||||||
|
pluginClient: pluginClient,
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
pCtx := backend.PluginContext{}
|
||||||
|
err := hs.makePluginResourceRequest(resp, req, pCtx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if resp.Flushed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, "sandbox", resp.Header().Get("Content-Security-Policy"))
|
||||||
|
}
|
||||||
|
|
||||||
func callGetPluginAsset(sc *scenarioContext) {
|
func callGetPluginAsset(sc *scenarioContext) {
|
||||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||||
}
|
}
|
||||||
@@ -221,3 +247,25 @@ type logger struct {
|
|||||||
func (l *logger) Warn(msg string, ctx ...interface{}) {
|
func (l *logger) Warn(msg string, ctx ...interface{}) {
|
||||||
l.warnings = append(l.warnings, msg)
|
l.warnings = append(l.warnings, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakePluginClient struct {
|
||||||
|
plugins.Client
|
||||||
|
|
||||||
|
req *backend.CallResourceRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakePluginClient) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||||
|
c.req = req
|
||||||
|
bytes, err := json.Marshal(map[string]interface{}{
|
||||||
|
"message": "hello",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sender.Send(&backend.CallResourceResponse{
|
||||||
|
Status: http.StatusOK,
|
||||||
|
Headers: make(map[string][]string),
|
||||||
|
Body: bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -65,6 +65,7 @@ func createShortURLScenario(t *testing.T, desc string, url string, routePattern
|
|||||||
sc := setupScenarioContext(t, url)
|
sc := setupScenarioContext(t, url)
|
||||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||||
c.Req.Body = mockRequestBody(cmd)
|
c.Req.Body = mockRequestBody(cmd)
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
sc.context = c
|
sc.context = c
|
||||||
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
|
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
|
||||||
|
|
||||||
|
@@ -130,16 +130,11 @@ func (hs *HTTPServer) SearchTeams(c *models.ReqContext) response.Response {
|
|||||||
page = 1
|
page = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
var userIdFilter int64
|
|
||||||
if hs.Cfg.EditorsCanAdmin && c.OrgRole != models.ROLE_ADMIN {
|
|
||||||
userIdFilter = c.SignedInUser.UserId
|
|
||||||
}
|
|
||||||
|
|
||||||
query := models.SearchTeamsQuery{
|
query := models.SearchTeamsQuery{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
Query: c.Query("query"),
|
Query: c.Query("query"),
|
||||||
Name: c.Query("name"),
|
Name: c.Query("name"),
|
||||||
UserIdFilter: userIdFilter,
|
UserIdFilter: userFilter(hs.Cfg.EditorsCanAdmin, c),
|
||||||
Page: page,
|
Page: page,
|
||||||
Limit: perPage,
|
Limit: perPage,
|
||||||
SignedInUser: c.SignedInUser,
|
SignedInUser: c.SignedInUser,
|
||||||
@@ -186,17 +181,32 @@ func (hs *HTTPServer) getTeamAccessControlMetadata(c *models.ReqContext, teamID
|
|||||||
return accesscontrol.GetResourcesMetadata(c.Req.Context(), userPermissions, "teams", teamIDs)[key], nil
|
return accesscontrol.GetResourcesMetadata(c.Req.Context(), userPermissions, "teams", teamIDs)[key], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserFilter returns the user ID used in a filter when querying a team
|
||||||
|
// 1. If the user is a viewer or editor, this will return the user's ID.
|
||||||
|
// 2. If EditorsCanAdmin is enabled and the user is an editor, this will return models.FilterIgnoreUser (0)
|
||||||
|
// 3. If the user is an admin, this will return models.FilterIgnoreUser (0)
|
||||||
|
func userFilter(editorsCanAdmin bool, c *models.ReqContext) int64 {
|
||||||
|
userIdFilter := c.SignedInUser.UserId
|
||||||
|
if (editorsCanAdmin && c.OrgRole == models.ROLE_EDITOR) || c.OrgRole == models.ROLE_ADMIN {
|
||||||
|
userIdFilter = models.FilterIgnoreUser
|
||||||
|
}
|
||||||
|
|
||||||
|
return userIdFilter
|
||||||
|
}
|
||||||
|
|
||||||
// GET /api/teams/:teamId
|
// GET /api/teams/:teamId
|
||||||
func (hs *HTTPServer) GetTeamByID(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) GetTeamByID(c *models.ReqContext) response.Response {
|
||||||
teamId, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
|
teamId, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
|
return response.Error(http.StatusBadRequest, "teamId is invalid", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
query := models.GetTeamByIdQuery{
|
query := models.GetTeamByIdQuery{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
Id: teamId,
|
Id: teamId,
|
||||||
SignedInUser: c.SignedInUser,
|
SignedInUser: c.SignedInUser,
|
||||||
HiddenUsers: hs.Cfg.HiddenUsers,
|
HiddenUsers: hs.Cfg.HiddenUsers,
|
||||||
|
UserIdFilter: userFilter(hs.Cfg.EditorsCanAdmin, c),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := hs.SQLStore.GetTeamById(c.Req.Context(), &query); err != nil {
|
if err := hs.SQLStore.GetTeamById(c.Req.Context(), &query); err != nil {
|
||||||
|
@@ -25,6 +25,9 @@ func (hs *HTTPServer) GetTeamMembers(c *models.ReqContext) response.Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := models.GetTeamMembersQuery{OrgId: c.OrgId, TeamId: teamId}
|
query := models.GetTeamMembersQuery{OrgId: c.OrgId, TeamId: teamId}
|
||||||
|
if err := hs.teamGuardian.CanAdmin(c.Req.Context(), query.OrgId, query.TeamId, c.SignedInUser); err != nil {
|
||||||
|
return response.Error(403, "Not allowed to list team members", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := hs.SQLStore.GetTeamMembers(c.Req.Context(), &query); err != nil {
|
if err := hs.SQLStore.GetTeamMembers(c.Req.Context(), &query); err != nil {
|
||||||
return response.Error(500, "Failed to get Team Members", err)
|
return response.Error(500, "Failed to get Team Members", err)
|
||||||
|
@@ -21,6 +21,14 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TeamGuardianMock struct {
|
||||||
|
result error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TeamGuardianMock) CanAdmin(ctx context.Context, orgId int64, teamId int64, user *models.SignedInUser) error {
|
||||||
|
return t.result
|
||||||
|
}
|
||||||
|
|
||||||
func setUpGetTeamMembersHandler(t *testing.T, sqlStore *sqlstore.SQLStore) {
|
func setUpGetTeamMembersHandler(t *testing.T, sqlStore *sqlstore.SQLStore) {
|
||||||
const testOrgID int64 = 1
|
const testOrgID int64 = 1
|
||||||
var userCmd models.CreateUserCommand
|
var userCmd models.CreateUserCommand
|
||||||
@@ -44,9 +52,10 @@ func TestTeamMembersAPIEndpoint_userLoggedIn(t *testing.T) {
|
|||||||
settings := setting.NewCfg()
|
settings := setting.NewCfg()
|
||||||
sqlStore := sqlstore.InitTestDB(t)
|
sqlStore := sqlstore.InitTestDB(t)
|
||||||
hs := &HTTPServer{
|
hs := &HTTPServer{
|
||||||
Cfg: settings,
|
Cfg: settings,
|
||||||
License: &licensing.OSSLicensingService{},
|
License: &licensing.OSSLicensingService{},
|
||||||
SQLStore: sqlStore,
|
SQLStore: sqlStore,
|
||||||
|
teamGuardian: &TeamGuardianMock{},
|
||||||
}
|
}
|
||||||
mock := mockstore.NewSQLStoreMock()
|
mock := mockstore.NewSQLStoreMock()
|
||||||
|
|
||||||
|
@@ -36,6 +36,7 @@ func TestTeamAPIEndpoint(t *testing.T) {
|
|||||||
hs := setupSimpleHTTPServer(nil)
|
hs := setupSimpleHTTPServer(nil)
|
||||||
hs.SQLStore = sqlstore.InitTestDB(t)
|
hs.SQLStore = sqlstore.InitTestDB(t)
|
||||||
mock := &mockstore.SQLStoreMock{}
|
mock := &mockstore.SQLStoreMock{}
|
||||||
|
hs.Cfg.EditorsCanAdmin = true
|
||||||
loggedInUserScenario(t, "When calling GET on", "/api/teams/search", "/api/teams/search", func(sc *scenarioContext) {
|
loggedInUserScenario(t, "When calling GET on", "/api/teams/search", "/api/teams/search", func(sc *scenarioContext) {
|
||||||
_, err := hs.SQLStore.CreateTeam("team1", "", 1)
|
_, err := hs.SQLStore.CreateTeam("team1", "", 1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -96,6 +97,7 @@ func TestTeamAPIEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
c.OrgRole = models.ROLE_EDITOR
|
c.OrgRole = models.ROLE_EDITOR
|
||||||
c.Req.Body = mockRequestBody(models.CreateTeamCommand{Name: teamName})
|
c.Req.Body = mockRequestBody(models.CreateTeamCommand{Name: teamName})
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
r := hs.CreateTeam(c)
|
r := hs.CreateTeam(c)
|
||||||
|
|
||||||
assert.Equal(t, 200, r.Status())
|
assert.Equal(t, 200, r.Status())
|
||||||
@@ -112,6 +114,7 @@ func TestTeamAPIEndpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
c.OrgRole = models.ROLE_EDITOR
|
c.OrgRole = models.ROLE_EDITOR
|
||||||
c.Req.Body = mockRequestBody(models.CreateTeamCommand{Name: teamName})
|
c.Req.Body = mockRequestBody(models.CreateTeamCommand{Name: teamName})
|
||||||
|
c.Req.Header.Add("Content-Type", "application/json")
|
||||||
r := hs.CreateTeam(c)
|
r := hs.CreateTeam(c)
|
||||||
assert.Equal(t, 200, r.Status())
|
assert.Equal(t, 200, r.Status())
|
||||||
assert.False(t, stub.warnCalled)
|
assert.False(t, stub.warnCalled)
|
||||||
|
@@ -128,20 +128,22 @@ func Auth(options *AuthOptions) web.Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminOrFeatureEnabled creates a middleware that allows access
|
// AdminOrEditorAndFeatureEnabled creates a middleware that allows
|
||||||
// if the signed in user is either an Org Admin or if the
|
// access if the signed in user is either an Org Admin or if they
|
||||||
// feature flag is enabled.
|
// are an Org Editor and the feature flag is enabled.
|
||||||
// Intended for when feature flags open up access to APIs that
|
// Intended for when feature flags open up access to APIs that
|
||||||
// are otherwise only available to admins.
|
// are otherwise only available to admins.
|
||||||
func AdminOrFeatureEnabled(enabled bool) web.Handler {
|
func AdminOrEditorAndFeatureEnabled(enabled bool) web.Handler {
|
||||||
return func(c *models.ReqContext) {
|
return func(c *models.ReqContext) {
|
||||||
if c.OrgRole == models.ROLE_ADMIN {
|
if c.OrgRole == models.ROLE_ADMIN {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !enabled {
|
if c.OrgRole == models.ROLE_EDITOR && enabled {
|
||||||
accessForbidden(c)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessForbidden(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
pkg/middleware/csrf.go
Normal file
39
pkg/middleware/csrf.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CSRF(loginCookieName string) func(http.Handler) http.Handler {
|
||||||
|
// As per RFC 7231/4.2.2 these methods are idempotent:
|
||||||
|
// (GET is excluded because it may have side effects in some APIs)
|
||||||
|
safeMethods := []string{"HEAD", "OPTIONS", "TRACE"}
|
||||||
|
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// If request has no login cookie - skip CSRF checks
|
||||||
|
if _, err := r.Cookie(loginCookieName); errors.Is(err, http.ErrNoCookie) {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Skip CSRF checks for "safe" methods
|
||||||
|
for _, method := range safeMethods {
|
||||||
|
if r.Method == method {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise - verify that Origin matches the server origin
|
||||||
|
host := strings.Split(r.Host, ":")[0]
|
||||||
|
origin, err := url.Parse(r.Header.Get("Origin"))
|
||||||
|
if err != nil || (origin.String() != "" && origin.Hostname() != host) {
|
||||||
|
http.Error(w, "origin not allowed", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -55,8 +55,12 @@ type GetTeamByIdQuery struct {
|
|||||||
SignedInUser *SignedInUser
|
SignedInUser *SignedInUser
|
||||||
HiddenUsers map[string]struct{}
|
HiddenUsers map[string]struct{}
|
||||||
Result *TeamDTO
|
Result *TeamDTO
|
||||||
|
UserIdFilter int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterIgnoreUser is used in a get / search teams query when the caller does not want to filter teams by user ID / membership
|
||||||
|
const FilterIgnoreUser int64 = 0
|
||||||
|
|
||||||
type GetTeamsByUserQuery struct {
|
type GetTeamsByUserQuery struct {
|
||||||
OrgId int64
|
OrgId int64
|
||||||
UserId int64 `json:"userId"`
|
UserId int64 `json:"userId"`
|
||||||
|
@@ -43,6 +43,7 @@ func TestImportDashboardAPI(t *testing.T) {
|
|||||||
jsonBytes, err := json.Marshal(cmd)
|
jsonBytes, err := json.Marshal(cmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req := s.NewRequest(http.MethodPost, "/api/dashboards/import", bytes.NewReader(jsonBytes))
|
req := s.NewRequest(http.MethodPost, "/api/dashboards/import", bytes.NewReader(jsonBytes))
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
resp, err := s.Send(req)
|
resp, err := s.Send(req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, resp.Body.Close())
|
require.NoError(t, resp.Body.Close())
|
||||||
@@ -57,6 +58,7 @@ func TestImportDashboardAPI(t *testing.T) {
|
|||||||
jsonBytes, err := json.Marshal(cmd)
|
jsonBytes, err := json.Marshal(cmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req := s.NewRequest(http.MethodPost, "/api/dashboards/import", bytes.NewReader(jsonBytes))
|
req := s.NewRequest(http.MethodPost, "/api/dashboards/import", bytes.NewReader(jsonBytes))
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
})
|
})
|
||||||
@@ -73,6 +75,7 @@ func TestImportDashboardAPI(t *testing.T) {
|
|||||||
jsonBytes, err := json.Marshal(cmd)
|
jsonBytes, err := json.Marshal(cmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req := s.NewRequest(http.MethodPost, "/api/dashboards/import", bytes.NewReader(jsonBytes))
|
req := s.NewRequest(http.MethodPost, "/api/dashboards/import", bytes.NewReader(jsonBytes))
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
})
|
})
|
||||||
@@ -90,6 +93,7 @@ func TestImportDashboardAPI(t *testing.T) {
|
|||||||
jsonBytes, err := json.Marshal(cmd)
|
jsonBytes, err := json.Marshal(cmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req := s.NewRequest(http.MethodPost, "/api/dashboards/import?trimdefaults=true", bytes.NewReader(jsonBytes))
|
req := s.NewRequest(http.MethodPost, "/api/dashboards/import?trimdefaults=true", bytes.NewReader(jsonBytes))
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
})
|
})
|
||||||
@@ -132,6 +136,7 @@ func TestImportDashboardAPI(t *testing.T) {
|
|||||||
jsonBytes, err := json.Marshal(cmd)
|
jsonBytes, err := json.Marshal(cmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req := s.NewRequest(http.MethodPost, "/api/dashboards/import?trimdefaults=true", bytes.NewReader(jsonBytes))
|
req := s.NewRequest(http.MethodPost, "/api/dashboards/import?trimdefaults=true", bytes.NewReader(jsonBytes))
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
})
|
})
|
||||||
@@ -160,6 +165,7 @@ func TestImportDashboardAPI(t *testing.T) {
|
|||||||
jsonBytes, err := json.Marshal(cmd)
|
jsonBytes, err := json.Marshal(cmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
req := s.NewRequest(http.MethodPost, "/api/dashboards/import", bytes.NewReader(jsonBytes))
|
req := s.NewRequest(http.MethodPost, "/api/dashboards/import", bytes.NewReader(jsonBytes))
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
webtest.RequestWithSignedInUser(req, &models.SignedInUser{
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
})
|
})
|
||||||
|
@@ -289,7 +289,11 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
t.Run(desc, func(t *testing.T) {
|
t.Run(desc, func(t *testing.T) {
|
||||||
ctx := web.Context{Req: &http.Request{}}
|
ctx := web.Context{Req: &http.Request{
|
||||||
|
Header: http.Header{
|
||||||
|
"Content-Type": []string{"application/json"},
|
||||||
|
},
|
||||||
|
}}
|
||||||
orgID := int64(1)
|
orgID := int64(1)
|
||||||
role := models.ROLE_ADMIN
|
role := models.ROLE_ADMIN
|
||||||
sqlStore := sqlstore.InitTestDB(t)
|
sqlStore := sqlstore.InitTestDB(t)
|
||||||
|
@@ -35,7 +35,10 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
t.Run(desc, func(t *testing.T) {
|
t.Run(desc, func(t *testing.T) {
|
||||||
ctx := web.Context{Req: &http.Request{}}
|
ctx := web.Context{Req: &http.Request{
|
||||||
|
Header: http.Header{},
|
||||||
|
}}
|
||||||
|
ctx.Req.Header.Add("Content-Type", "application/json")
|
||||||
sqlStore := sqlstore.InitTestDB(t)
|
sqlStore := sqlstore.InitTestDB(t)
|
||||||
service := QueryHistoryService{
|
service := QueryHistoryService{
|
||||||
Cfg: setting.NewCfg(),
|
Cfg: setting.NewCfg(),
|
||||||
|
@@ -113,6 +113,7 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) {
|
|||||||
var requestResponse = func(server *web.Mux, httpMethod, requestpath string, requestBody io.Reader) *httptest.ResponseRecorder {
|
var requestResponse = func(server *web.Mux, httpMethod, requestpath string, requestBody io.Reader) *httptest.ResponseRecorder {
|
||||||
req, err := http.NewRequest(httpMethod, requestpath, requestBody)
|
req, err := http.NewRequest(httpMethod, requestpath, requestBody)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
server.ServeHTTP(recorder, req)
|
server.ServeHTTP(recorder, req)
|
||||||
return recorder
|
return recorder
|
||||||
@@ -206,6 +207,7 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) {
|
|||||||
var requestResponse = func(server *web.Mux, httpMethod, requestpath string, requestBody io.Reader) *httptest.ResponseRecorder {
|
var requestResponse = func(server *web.Mux, httpMethod, requestpath string, requestBody io.Reader) *httptest.ResponseRecorder {
|
||||||
req, err := http.NewRequest(httpMethod, requestpath, requestBody)
|
req, err := http.NewRequest(httpMethod, requestpath, requestBody)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
server.ServeHTTP(recorder, req)
|
server.ServeHTTP(recorder, req)
|
||||||
return recorder
|
return recorder
|
||||||
|
@@ -64,18 +64,6 @@ func getTeamMemberCount(filteredUsers []string) string {
|
|||||||
return "(SELECT COUNT(*) FROM team_member WHERE team_member.team_id = team.id) AS member_count "
|
return "(SELECT COUNT(*) FROM team_member WHERE team_member.team_id = team.id) AS member_count "
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTeamSearchSQLBase(filteredUsers []string) string {
|
|
||||||
return `SELECT
|
|
||||||
team.id AS id,
|
|
||||||
team.org_id,
|
|
||||||
team.name AS name,
|
|
||||||
team.email AS email,
|
|
||||||
team_member.permission, ` +
|
|
||||||
getTeamMemberCount(filteredUsers) +
|
|
||||||
` FROM team AS team
|
|
||||||
INNER JOIN team_member ON team.id = team_member.team_id AND team_member.user_id = ? `
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTeamSelectSQLBase(filteredUsers []string) string {
|
func getTeamSelectSQLBase(filteredUsers []string) string {
|
||||||
return `SELECT
|
return `SELECT
|
||||||
team.id as id,
|
team.id as id,
|
||||||
@@ -198,17 +186,15 @@ func (ss *SQLStore) SearchTeams(ctx context.Context, query *models.SearchTeamsQu
|
|||||||
params := make([]interface{}, 0)
|
params := make([]interface{}, 0)
|
||||||
|
|
||||||
filteredUsers := getFilteredUsers(query.SignedInUser, query.HiddenUsers)
|
filteredUsers := getFilteredUsers(query.SignedInUser, query.HiddenUsers)
|
||||||
if query.UserIdFilter > 0 {
|
sql.WriteString(getTeamSelectSQLBase(filteredUsers))
|
||||||
sql.WriteString(getTeamSearchSQLBase(filteredUsers))
|
|
||||||
for _, user := range filteredUsers {
|
for _, user := range filteredUsers {
|
||||||
params = append(params, user)
|
params = append(params, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.UserIdFilter != models.FilterIgnoreUser {
|
||||||
|
sql.WriteString(` INNER JOIN team_member ON team.id = team_member.team_id AND team_member.user_id = ?`)
|
||||||
params = append(params, query.UserIdFilter)
|
params = append(params, query.UserIdFilter)
|
||||||
} else {
|
|
||||||
sql.WriteString(getTeamSelectSQLBase(filteredUsers))
|
|
||||||
for _, user := range filteredUsers {
|
|
||||||
params = append(params, user)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sql.WriteString(` WHERE team.org_id = ?`)
|
sql.WriteString(` WHERE team.org_id = ?`)
|
||||||
@@ -237,6 +223,8 @@ func (ss *SQLStore) SearchTeams(ctx context.Context, query *models.SearchTeamsQu
|
|||||||
|
|
||||||
team := models.Team{}
|
team := models.Team{}
|
||||||
countSess := x.Table("team")
|
countSess := x.Table("team")
|
||||||
|
countSess.Where("team.org_id=?", query.OrgId)
|
||||||
|
|
||||||
if query.Query != "" {
|
if query.Query != "" {
|
||||||
countSess.Where(`name `+dialect.LikeStr()+` ?`, queryWithWildcards)
|
countSess.Where(`name `+dialect.LikeStr()+` ?`, queryWithWildcards)
|
||||||
}
|
}
|
||||||
@@ -245,6 +233,18 @@ func (ss *SQLStore) SearchTeams(ctx context.Context, query *models.SearchTeamsQu
|
|||||||
countSess.Where("name=?", query.Name)
|
countSess.Where("name=?", query.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're not retrieving all results, then only search for teams that this user has access to
|
||||||
|
if query.UserIdFilter != models.FilterIgnoreUser {
|
||||||
|
countSess.
|
||||||
|
Where(`
|
||||||
|
team.id IN (
|
||||||
|
SELECT
|
||||||
|
team_id
|
||||||
|
FROM team_member
|
||||||
|
WHERE team_member.user_id = ?
|
||||||
|
)`, query.UserIdFilter)
|
||||||
|
}
|
||||||
|
|
||||||
count, err := countSess.Count(&team)
|
count, err := countSess.Count(&team)
|
||||||
query.Result.TotalCount = count
|
query.Result.TotalCount = count
|
||||||
|
|
||||||
@@ -261,6 +261,11 @@ func (ss *SQLStore) GetTeamById(ctx context.Context, query *models.GetTeamByIdQu
|
|||||||
params = append(params, user)
|
params = append(params, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.UserIdFilter != models.FilterIgnoreUser {
|
||||||
|
sql.WriteString(` INNER JOIN team_member ON team.id = team_member.team_id AND team_member.user_id = ?`)
|
||||||
|
params = append(params, query.UserIdFilter)
|
||||||
|
}
|
||||||
|
|
||||||
sql.WriteString(` WHERE team.org_id = ? and team.id = ?`)
|
sql.WriteString(` WHERE team.org_id = ? and team.id = ?`)
|
||||||
params = append(params, query.OrgId, query.Id)
|
params = append(params, query.OrgId, query.Id)
|
||||||
|
|
||||||
|
@@ -42,3 +42,9 @@ func ClearCookieHeader(req *http.Request, keepCookiesNames []string) {
|
|||||||
req.AddCookie(c)
|
req.AddCookie(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetProxyResponseHeaders sets proxy response headers.
|
||||||
|
// Sets Content-Security-Policy: sandbox
|
||||||
|
func SetProxyResponseHeaders(header http.Header) {
|
||||||
|
header.Set("Content-Security-Policy", "sandbox")
|
||||||
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
@@ -12,8 +13,15 @@ import (
|
|||||||
// Bind deserializes JSON payload from the request
|
// Bind deserializes JSON payload from the request
|
||||||
func Bind(req *http.Request, v interface{}) error {
|
func Bind(req *http.Request, v interface{}) error {
|
||||||
if req.Body != nil {
|
if req.Body != nil {
|
||||||
|
m, _, err := mime.ParseMediaType(req.Header.Get("Content-type"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if m != "application/json" {
|
||||||
|
return errors.New("bad content type")
|
||||||
|
}
|
||||||
defer func() { _ = req.Body.Close() }()
|
defer func() { _ = req.Body.Close() }()
|
||||||
err := json.NewDecoder(req.Body).Decode(v)
|
err = json.NewDecoder(req.Body).Decode(v)
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user