From d9cdcb550e5ea25bd7b1f8db2a01d3102f7b3918 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Mon, 29 Nov 2021 10:18:01 +0100 Subject: [PATCH] Chore: Refactor api handlers to use web.Bind (#42199) * Chore: Refactor api handlers to use web.Bind * fix comments * fix comment * trying to fix most of the tests and force routing.Wrap type check * fix library panels tests * fix frontend logging tests * allow passing nil as a response to skip writing * return nil instead of the response * rewrite login handler function types * remove handlerFuncCtx * make linter happy * remove old bindings from the libraryelements * restore comments --- pkg/api/admin_users.go | 26 +++- pkg/api/admin_users_test.go | 9 +- pkg/api/alerting.go | 43 +++++- pkg/api/alerting_test.go | 3 +- pkg/api/annotations.go | 32 +++- pkg/api/annotations_test.go | 12 +- pkg/api/api.go | 142 +++++++++--------- pkg/api/apikey.go | 16 +- pkg/api/common_test.go | 17 +-- pkg/api/dashboard.go | 31 +++- pkg/api/dashboard_permission.go | 8 +- pkg/api/dashboard_permission_test.go | 3 +- pkg/api/dashboard_test.go | 9 +- pkg/api/datasources.go | 13 +- pkg/api/datasources_test.go | 36 +++-- pkg/api/folder.go | 13 +- pkg/api/folder_permission.go | 7 +- pkg/api/folder_permission_test.go | 3 +- pkg/api/folder_test.go | 6 +- pkg/api/frontend_logging.go | 12 +- pkg/api/frontend_logging_test.go | 6 +- pkg/api/frontend_metrics.go | 8 +- pkg/api/frontendlogging/sentry.go | 14 ++ pkg/api/ldap_debug.go | 2 +- pkg/api/login_oauth.go | 34 +++-- pkg/api/login_test.go | 19 ++- pkg/api/metrics.go | 13 +- pkg/api/org.go | 31 +++- pkg/api/org_invite.go | 13 +- pkg/api/org_users.go | 29 +++- pkg/api/org_users_test.go | 4 +- pkg/api/password.go | 14 +- pkg/api/playlist.go | 14 +- pkg/api/plugins.go | 18 ++- pkg/api/preferences.go | 20 ++- pkg/api/quota.go | 14 +- pkg/api/routing/routing.go | 12 +- pkg/api/short_url.go | 7 +- pkg/api/short_url_test.go | 3 +- pkg/api/signup.go | 14 +- pkg/api/team.go | 20 ++- pkg/api/team_members.go | 14 +- pkg/api/team_test.go | 8 +- pkg/api/user.go | 20 ++- pkg/api/user_token.go | 8 +- pkg/api/user_token_test.go | 3 +- pkg/services/libraryelements/api.go | 20 ++- .../libraryelements_create_test.go | 18 ++- .../libraryelements_get_all_test.go | 54 ++++--- .../libraryelements_patch_test.go | 65 +++++--- .../libraryelements_permissions_test.go | 57 ++++--- .../libraryelements/libraryelements_test.go | 13 +- pkg/services/libraryelements/models.go | 2 +- pkg/services/live/live.go | 6 +- 54 files changed, 739 insertions(+), 299 deletions(-) diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index c481e8548b0..602d57f52d3 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -3,6 +3,7 @@ package api import ( "errors" "fmt" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -11,9 +12,14 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" ) -func (hs *HTTPServer) AdminCreateUser(c *models.ReqContext, form dtos.AdminCreateUserForm) response.Response { +func (hs *HTTPServer) AdminCreateUser(c *models.ReqContext) response.Response { + form := dtos.AdminCreateUserForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd := models.CreateUserCommand{ Login: form.Login, Email: form.Email, @@ -56,7 +62,11 @@ func (hs *HTTPServer) AdminCreateUser(c *models.ReqContext, form dtos.AdminCreat return response.JSON(200, result) } -func AdminUpdateUserPassword(c *models.ReqContext, form dtos.AdminUpdateUserPasswordForm) response.Response { +func AdminUpdateUserPassword(c *models.ReqContext) response.Response { + form := dtos.AdminUpdateUserPasswordForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } userID := c.ParamsInt64(":id") if len(form.Password) < 4 { @@ -87,7 +97,11 @@ func AdminUpdateUserPassword(c *models.ReqContext, form dtos.AdminUpdateUserPass } // PUT /api/admin/users/:id/permissions -func (hs *HTTPServer) AdminUpdateUserPermissions(c *models.ReqContext, form dtos.AdminUpdateUserPermissionsForm) response.Response { +func (hs *HTTPServer) AdminUpdateUserPermissions(c *models.ReqContext) response.Response { + form := dtos.AdminUpdateUserPermissionsForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } userID := c.ParamsInt64(":id") err := updateUserPermissions(hs.SQLStore, userID, form.IsGrafanaAdmin) @@ -182,7 +196,11 @@ func (hs *HTTPServer) AdminGetUserAuthTokens(c *models.ReqContext) response.Resp } // POST /api/admin/users/:id/revoke-auth-token -func (hs *HTTPServer) AdminRevokeUserAuthToken(c *models.ReqContext, cmd models.RevokeAuthTokenCmd) response.Response { +func (hs *HTTPServer) AdminRevokeUserAuthToken(c *models.ReqContext) response.Response { + cmd := models.RevokeAuthTokenCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } userID := c.ParamsInt64(":id") return hs.revokeUserAuthTokenInternal(c, userID, cmd) } diff --git a/pkg/api/admin_users_test.go b/pkg/api/admin_users_test.go index cce77a46abe..a8b0959a131 100644 --- a/pkg/api/admin_users_test.go +++ b/pkg/api/admin_users_test.go @@ -315,12 +315,13 @@ func putAdminScenario(t *testing.T, desc string, url string, routePattern string sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.UserId = testUserID sc.context.OrgId = testOrgID sc.context.OrgRole = role - return hs.AdminUpdateUserPermissions(c, cmd) + return hs.AdminUpdateUserPermissions(c) }) sc.m.Put(routePattern, sc.defaultHandler) @@ -370,12 +371,13 @@ func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, rou sc := setupScenarioContext(t, url) sc.userAuthTokenService = fakeAuthTokenService sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.UserId = testUserID sc.context.OrgId = testOrgID sc.context.OrgRole = models.ROLE_ADMIN - return hs.AdminRevokeUserAuthToken(c, cmd) + return hs.AdminRevokeUserAuthToken(c) }) sc.m.Post(routePattern, sc.defaultHandler) @@ -470,10 +472,11 @@ func adminCreateUserScenario(t *testing.T, desc string, url string, routePattern sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.UserId = testUserID - return hs.AdminCreateUser(c, cmd) + return hs.AdminCreateUser(c) }) sc.m.Post(routePattern, sc.defaultHandler) diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index e9c1db47a39..c1899c72da6 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net/http" "strconv" "github.com/grafana/grafana/pkg/api/dtos" @@ -132,7 +133,11 @@ func GetAlerts(c *models.ReqContext) response.Response { } // POST /api/alerts/test -func (hs *HTTPServer) AlertTest(c *models.ReqContext, dto dtos.AlertTestCommand) response.Response { +func (hs *HTTPServer) AlertTest(c *models.ReqContext) response.Response { + dto := dtos.AlertTestCommand{} + if err := web.Bind(c.Req, &dto); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if _, idErr := dto.Dashboard.Get("id").Int64(); idErr != nil { return response.Error(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil) } @@ -276,7 +281,11 @@ func GetAlertNotificationByUID(c *models.ReqContext) response.Response { return response.JSON(200, dtos.NewAlertNotification(query.Result)) } -func CreateAlertNotification(c *models.ReqContext, cmd models.CreateAlertNotificationCommand) response.Response { +func CreateAlertNotification(c *models.ReqContext) response.Response { + cmd := models.CreateAlertNotificationCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.OrgId if err := bus.DispatchCtx(c.Req.Context(), &cmd); err != nil { @@ -293,7 +302,11 @@ func CreateAlertNotification(c *models.ReqContext, cmd models.CreateAlertNotific return response.JSON(200, dtos.NewAlertNotification(cmd.Result)) } -func (hs *HTTPServer) UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) response.Response { +func (hs *HTTPServer) UpdateAlertNotification(c *models.ReqContext) response.Response { + cmd := models.UpdateAlertNotificationCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.OrgId err := hs.fillWithSecureSettingsData(c.Req.Context(), &cmd) @@ -324,7 +337,11 @@ func (hs *HTTPServer) UpdateAlertNotification(c *models.ReqContext, cmd models.U return response.JSON(200, dtos.NewAlertNotification(query.Result)) } -func (hs *HTTPServer) UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) response.Response { +func (hs *HTTPServer) UpdateAlertNotificationByUID(c *models.ReqContext) response.Response { + cmd := models.UpdateAlertNotificationWithUidCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.OrgId cmd.Uid = web.Params(c.Req)[":uid"] @@ -444,7 +461,11 @@ func DeleteAlertNotificationByUID(c *models.ReqContext) response.Response { } // POST /api/alert-notifications/test -func NotificationTest(c *models.ReqContext, dto dtos.NotificationTestCommand) response.Response { +func NotificationTest(c *models.ReqContext) response.Response { + dto := dtos.NotificationTestCommand{} + if err := web.Bind(c.Req, &dto); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd := &alerting.NotificationTestCommand{ OrgID: c.OrgId, ID: dto.ID, @@ -470,7 +491,11 @@ func NotificationTest(c *models.ReqContext, dto dtos.NotificationTestCommand) re } // POST /api/alerts/:alertId/pause -func PauseAlert(c *models.ReqContext, dto dtos.PauseAlertCommand) response.Response { +func PauseAlert(c *models.ReqContext) response.Response { + dto := dtos.PauseAlertCommand{} + if err := web.Bind(c.Req, &dto); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } alertID := c.ParamsInt64(":alertId") result := make(map[string]interface{}) result["alertId"] = alertID @@ -523,7 +548,11 @@ func PauseAlert(c *models.ReqContext, dto dtos.PauseAlertCommand) response.Respo } // POST /api/admin/pause-all-alerts -func PauseAllAlerts(c *models.ReqContext, dto dtos.PauseAllAlertsCommand) response.Response { +func PauseAllAlerts(c *models.ReqContext) response.Response { + dto := dtos.PauseAllAlertsCommand{} + if err := web.Bind(c.Req, &dto); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } updateCmd := models.PauseAllAlertCommand{ Paused: dto.Paused, } diff --git a/pkg/api/alerting_test.go b/pkg/api/alerting_test.go index 739eb1b3beb..80f6e8ccbb5 100644 --- a/pkg/api/alerting_test.go +++ b/pkg/api/alerting_test.go @@ -166,12 +166,13 @@ func postAlertScenario(t *testing.T, desc string, url string, routePattern strin sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.UserId = testUserID sc.context.OrgId = testOrgID sc.context.OrgRole = role - return PauseAlert(c, cmd) + return PauseAlert(c) }) sc.m.Post(routePattern, sc.defaultHandler) diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 031fd55f2ca..1d90aaba4dc 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -2,6 +2,7 @@ package api import ( "errors" + "net/http" "strings" "github.com/grafana/grafana/pkg/api/dtos" @@ -10,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" ) func GetAnnotations(c *models.ReqContext) response.Response { @@ -51,7 +53,11 @@ func (e *CreateAnnotationError) Error() string { return e.message } -func PostAnnotation(c *models.ReqContext, cmd dtos.PostAnnotationsCmd) response.Response { +func PostAnnotation(c *models.ReqContext) response.Response { + cmd := dtos.PostAnnotationsCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if canSave, err := canSaveByDashboardID(c, cmd.DashboardId); err != nil || !canSave { return dashboardGuardianResponse(err) } @@ -98,7 +104,11 @@ func formatGraphiteAnnotation(what string, data string) string { return text } -func PostGraphiteAnnotation(c *models.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd) response.Response { +func PostGraphiteAnnotation(c *models.ReqContext) response.Response { + cmd := dtos.PostGraphiteAnnotationsCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } repo := annotations.GetRepository() if cmd.What == "" { @@ -149,7 +159,11 @@ func PostGraphiteAnnotation(c *models.ReqContext, cmd dtos.PostGraphiteAnnotatio }) } -func UpdateAnnotation(c *models.ReqContext, cmd dtos.UpdateAnnotationsCmd) response.Response { +func UpdateAnnotation(c *models.ReqContext) response.Response { + cmd := dtos.UpdateAnnotationsCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } annotationID := c.ParamsInt64(":annotationId") repo := annotations.GetRepository() @@ -175,7 +189,11 @@ func UpdateAnnotation(c *models.ReqContext, cmd dtos.UpdateAnnotationsCmd) respo return response.Success("Annotation updated") } -func PatchAnnotation(c *models.ReqContext, cmd dtos.PatchAnnotationsCmd) response.Response { +func PatchAnnotation(c *models.ReqContext) response.Response { + cmd := dtos.PatchAnnotationsCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } annotationID := c.ParamsInt64(":annotationId") repo := annotations.GetRepository() @@ -223,7 +241,11 @@ func PatchAnnotation(c *models.ReqContext, cmd dtos.PatchAnnotationsCmd) respons return response.Success("Annotation patched") } -func DeleteAnnotations(c *models.ReqContext, cmd dtos.DeleteAnnotationsCmd) response.Response { +func DeleteAnnotations(c *models.ReqContext) response.Response { + cmd := dtos.DeleteAnnotationsCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } repo := annotations.GetRepository() err := repo.Delete(&annotations.DeleteParams{ diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index b5118494bcc..595704d96be 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -275,12 +275,13 @@ func postAnnotationScenario(t *testing.T, desc string, url string, routePattern sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.UserId = testUserID sc.context.OrgId = testOrgID sc.context.OrgRole = role - return PostAnnotation(c, cmd) + return PostAnnotation(c) }) fakeAnnoRepo = &fakeAnnotationsRepo{} @@ -299,12 +300,13 @@ func putAnnotationScenario(t *testing.T, desc string, url string, routePattern s sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.UserId = testUserID sc.context.OrgId = testOrgID sc.context.OrgRole = role - return UpdateAnnotation(c, cmd) + return UpdateAnnotation(c) }) fakeAnnoRepo = &fakeAnnotationsRepo{} @@ -322,12 +324,13 @@ func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.UserId = testUserID sc.context.OrgId = testOrgID sc.context.OrgRole = role - return PatchAnnotation(c, cmd) + return PatchAnnotation(c) }) fakeAnnoRepo = &fakeAnnotationsRepo{} @@ -346,12 +349,13 @@ func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePatte sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.UserId = testUserID sc.context.OrgId = testOrgID sc.context.OrgRole = role - return DeleteAnnotations(c, cmd) + return DeleteAnnotations(c) }) fakeAnnoRepo = &fakeAnnotationsRepo{} diff --git a/pkg/api/api.go b/pkg/api/api.go index fcaac446ec7..98a2751797c 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -4,13 +4,10 @@ package api import ( "time" - "github.com/go-macaron/binding" "github.com/grafana/grafana/pkg/api/avatar" - "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/frontendlogging" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" ac "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -35,7 +32,6 @@ func (hs *HTTPServer) registerRoutes() { authorize := acmiddleware.Middleware(hs.AccessControl) authorizeInOrg := acmiddleware.AuthorizeInOrgMiddleware(hs.AccessControl, hs.SQLStore) quota := middleware.Quota(hs.QuotaService) - bind := binding.Bind r := hs.RouteRegister @@ -116,19 +112,19 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/verify", hs.Index) r.Get("/signup", hs.Index) r.Get("/api/user/signup/options", routing.Wrap(GetSignUpOptions)) - r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), routing.Wrap(SignUp)) - r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), routing.Wrap(hs.SignUpStep2)) + r.Post("/api/user/signup", quota("user"), routing.Wrap(SignUp)) + r.Post("/api/user/signup/step2", routing.Wrap(hs.SignUpStep2)) // invited r.Get("/api/user/invite/:code", routing.Wrap(GetInviteInfoByCode)) - r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), routing.Wrap(hs.CompleteInvite)) + r.Post("/api/user/invite/complete", routing.Wrap(hs.CompleteInvite)) // reset password r.Get("/user/password/send-reset-email", reqNotSignedIn, hs.Index) r.Get("/user/password/reset", hs.Index) - r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), routing.Wrap(SendResetPasswordEmail)) - r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), routing.Wrap(ResetPassword)) + r.Post("/api/user/password/send-reset-email", routing.Wrap(SendResetPasswordEmail)) + r.Post("/api/user/password/reset", routing.Wrap(ResetPassword)) // dashboard snapshots r.Get("/dashboard/snapshot/*", reqNoAuth, hs.Index) @@ -145,7 +141,7 @@ func (hs *HTTPServer) registerRoutes() { // user (signed in) apiRoute.Group("/user", func(userRoute routing.RouteRegister) { userRoute.Get("/", routing.Wrap(GetSignedInUser)) - userRoute.Put("/", bind(models.UpdateUserCommand{}), routing.Wrap(UpdateSignedInUser)) + userRoute.Put("/", routing.Wrap(UpdateSignedInUser)) userRoute.Post("/using/:id", routing.Wrap(UserSetUsingOrg)) userRoute.Get("/orgs", routing.Wrap(GetSignedInUserOrgList)) userRoute.Get("/teams", routing.Wrap(GetSignedInUserTeamList)) @@ -153,17 +149,17 @@ func (hs *HTTPServer) registerRoutes() { userRoute.Post("/stars/dashboard/:id", routing.Wrap(StarDashboard)) userRoute.Delete("/stars/dashboard/:id", routing.Wrap(UnstarDashboard)) - userRoute.Put("/password", bind(models.ChangeUserPasswordCommand{}), routing.Wrap(ChangeUserPassword)) + userRoute.Put("/password", routing.Wrap(ChangeUserPassword)) userRoute.Get("/quotas", routing.Wrap(GetUserQuotas)) userRoute.Put("/helpflags/:id", routing.Wrap(SetHelpFlag)) // For dev purpose userRoute.Get("/helpflags/clear", routing.Wrap(ClearHelpFlags)) userRoute.Get("/preferences", routing.Wrap(hs.GetUserPreferences)) - userRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), routing.Wrap(hs.UpdateUserPreferences)) + userRoute.Put("/preferences", routing.Wrap(hs.UpdateUserPreferences)) userRoute.Get("/auth-tokens", routing.Wrap(hs.GetUserAuthTokens)) - userRoute.Post("/revoke-auth-token", bind(models.RevokeAuthTokenCmd{}), routing.Wrap(hs.RevokeUserAuthToken)) + userRoute.Post("/revoke-auth-token", routing.Wrap(hs.RevokeUserAuthToken)) }, reqSignedInNoAnonymous) apiRoute.Group("/users", func(usersRoute routing.RouteRegister) { @@ -175,21 +171,21 @@ func (hs *HTTPServer) registerRoutes() { usersRoute.Get("/:id/orgs", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(GetUserOrgList)) // query parameters /users/lookup?loginOrEmail=admin@example.com usersRoute.Get("/lookup", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), routing.Wrap(GetUserByLoginOrEmail)) - usersRoute.Put("/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), bind(models.UpdateUserCommand{}), routing.Wrap(UpdateUser)) + usersRoute.Put("/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), routing.Wrap(UpdateUser)) usersRoute.Post("/:id/using/:orgId", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersWrite, userIDScope)), routing.Wrap(UpdateUserActiveOrg)) }) // team (admin permission required) apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) { - teamsRoute.Post("/", bind(models.CreateTeamCommand{}), routing.Wrap(hs.CreateTeam)) - teamsRoute.Put("/:teamId", bind(models.UpdateTeamCommand{}), routing.Wrap(hs.UpdateTeam)) + teamsRoute.Post("/", routing.Wrap(hs.CreateTeam)) + teamsRoute.Put("/:teamId", routing.Wrap(hs.UpdateTeam)) teamsRoute.Delete("/:teamId", routing.Wrap(hs.DeleteTeamByID)) teamsRoute.Get("/:teamId/members", routing.Wrap(hs.GetTeamMembers)) - teamsRoute.Post("/:teamId/members", bind(models.AddTeamMemberCommand{}), routing.Wrap(hs.AddTeamMember)) - teamsRoute.Put("/:teamId/members/:userId", bind(models.UpdateTeamMemberCommand{}), routing.Wrap(hs.UpdateTeamMember)) + teamsRoute.Post("/:teamId/members", routing.Wrap(hs.AddTeamMember)) + teamsRoute.Put("/:teamId/members/:userId", routing.Wrap(hs.UpdateTeamMember)) teamsRoute.Delete("/:teamId/members/:userId", routing.Wrap(hs.RemoveTeamMember)) teamsRoute.Get("/:teamId/preferences", routing.Wrap(hs.GetTeamPreferences)) - teamsRoute.Put("/:teamId/preferences", bind(dtos.UpdatePrefsCmd{}), routing.Wrap(hs.UpdateTeamPreferences)) + teamsRoute.Put("/:teamId/preferences", routing.Wrap(hs.UpdateTeamPreferences)) }, reqCanAccessTeams) // team without requirement of user to be org admin @@ -207,22 +203,22 @@ func (hs *HTTPServer) registerRoutes() { // current org apiRoute.Group("/org", func(orgRoute routing.RouteRegister) { userIDScope := ac.Scope("users", "id", ac.Parameter(":userId")) - orgRoute.Put("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite)), bind(dtos.UpdateOrgForm{}), routing.Wrap(UpdateCurrentOrg)) - orgRoute.Put("/address", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite)), bind(dtos.UpdateOrgAddressForm{}), routing.Wrap(UpdateCurrentOrgAddress)) + orgRoute.Put("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(UpdateCurrentOrg)) + orgRoute.Put("/address", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(UpdateCurrentOrgAddress)) orgRoute.Get("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.GetOrgUsersForCurrentOrg)) orgRoute.Get("/users/search", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.SearchOrgUsersWithPaging)) - orgRoute.Post("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), quota("user"), bind(models.AddOrgUserCommand{}), routing.Wrap(hs.AddOrgUserToCurrentOrg)) - orgRoute.Patch("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), bind(models.UpdateOrgUserCommand{}), routing.Wrap(hs.UpdateOrgUserForCurrentOrg)) + orgRoute.Post("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), quota("user"), routing.Wrap(hs.AddOrgUserToCurrentOrg)) + orgRoute.Patch("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), routing.Wrap(hs.UpdateOrgUserForCurrentOrg)) orgRoute.Delete("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUserForCurrentOrg)) // invites orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(GetPendingOrgInvites)) - orgRoute.Post("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), quota("user"), bind(dtos.AddInviteForm{}), routing.Wrap(AddOrgInvite)) + orgRoute.Post("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), quota("user"), routing.Wrap(AddOrgInvite)) orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(RevokeInvite)) // prefs orgRoute.Get("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesRead)), routing.Wrap(hs.GetOrgPreferences)) - orgRoute.Put("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesWrite)), bind(dtos.UpdatePrefsCmd{}), routing.Wrap(hs.UpdateOrgPreferences)) + orgRoute.Put("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesWrite)), routing.Wrap(hs.UpdateOrgPreferences)) }) // current org without requirement of user to be org admin @@ -231,7 +227,7 @@ func (hs *HTTPServer) registerRoutes() { }) // create new org - apiRoute.Post("/orgs", authorizeInOrg(reqSignedIn, acmiddleware.UseGlobalOrg, ac.EvalPermission(ActionOrgsCreate)), quota("org"), bind(models.CreateOrgCommand{}), routing.Wrap(hs.CreateOrg)) + apiRoute.Post("/orgs", authorizeInOrg(reqSignedIn, acmiddleware.UseGlobalOrg, ac.EvalPermission(ActionOrgsCreate)), quota("org"), routing.Wrap(hs.CreateOrg)) // search all orgs apiRoute.Get("/orgs", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseGlobalOrg, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(SearchOrgs)) @@ -240,15 +236,15 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Group("/orgs/:orgId", func(orgsRoute routing.RouteRegister) { userIDScope := ac.Scope("users", "id", ac.Parameter(":userId")) orgsRoute.Get("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(GetOrgByID)) - orgsRoute.Put("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), bind(dtos.UpdateOrgForm{}), routing.Wrap(UpdateOrg)) - orgsRoute.Put("/address", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), bind(dtos.UpdateOrgAddressForm{}), routing.Wrap(UpdateOrgAddress)) + orgsRoute.Put("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(UpdateOrg)) + orgsRoute.Put("/address", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(UpdateOrgAddress)) orgsRoute.Delete("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsDelete)), routing.Wrap(DeleteOrgByID)) orgsRoute.Get("/users", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.GetOrgUsers)) - orgsRoute.Post("/users", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), bind(models.AddOrgUserCommand{}), routing.Wrap(hs.AddOrgUser)) - orgsRoute.Patch("/users/:userId", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), bind(models.UpdateOrgUserCommand{}), routing.Wrap(hs.UpdateOrgUser)) + orgsRoute.Post("/users", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), routing.Wrap(hs.AddOrgUser)) + orgsRoute.Patch("/users/:userId", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), routing.Wrap(hs.UpdateOrgUser)) orgsRoute.Delete("/users/:userId", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUser)) orgsRoute.Get("/quotas", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsQuotasRead)), routing.Wrap(hs.GetOrgQuotas)) - orgsRoute.Put("/quotas/:target", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsQuotasWrite)), bind(models.UpdateOrgQuotaCmd{}), routing.Wrap(hs.UpdateOrgQuota)) + orgsRoute.Put("/quotas/:target", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsQuotasWrite)), routing.Wrap(hs.UpdateOrgQuota)) }) // orgs (admin routes) @@ -257,21 +253,21 @@ func (hs *HTTPServer) registerRoutes() { // auth api keys apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) { keysRoute.Get("/", routing.Wrap(GetAPIKeys)) - keysRoute.Post("/", quota("api_key"), bind(models.AddApiKeyCommand{}), routing.Wrap(hs.AddAPIKey)) - keysRoute.Post("/additional", quota("api_key"), bind(models.AddApiKeyCommand{}), routing.Wrap(hs.AdditionalAPIKey)) + keysRoute.Post("/", quota("api_key"), routing.Wrap(hs.AddAPIKey)) + keysRoute.Post("/additional", quota("api_key"), routing.Wrap(hs.AdditionalAPIKey)) keysRoute.Delete("/:id", routing.Wrap(DeleteAPIKey)) }, reqOrgAdmin) // Preferences apiRoute.Group("/preferences", func(prefRoute routing.RouteRegister) { - prefRoute.Post("/set-home-dash", bind(models.SavePreferencesCommand{}), routing.Wrap(SetHomeDashboard)) + prefRoute.Post("/set-home-dash", routing.Wrap(SetHomeDashboard)) }) // Data sources apiRoute.Group("/datasources", func(datasourceRoute routing.RouteRegister) { datasourceRoute.Get("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesRead, ScopeDatasourcesAll)), routing.Wrap(hs.GetDataSources)) - datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesCreate)), quota("data_source"), bind(models.AddDataSourceCommand{}), routing.Wrap(AddDataSource)) - datasourceRoute.Put("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesWrite, ScopeDatasourceID)), bind(models.UpdateDataSourceCommand{}), routing.Wrap(hs.UpdateDataSource)) + datasourceRoute.Post("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesCreate)), quota("data_source"), routing.Wrap(AddDataSource)) + datasourceRoute.Put("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesWrite, ScopeDatasourceID)), routing.Wrap(hs.UpdateDataSource)) datasourceRoute.Delete("/:id", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesDelete, ScopeDatasourceID)), routing.Wrap(hs.DeleteDataSourceById)) datasourceRoute.Delete("/uid/:uid", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesDelete, ScopeDatasourceUID)), routing.Wrap(hs.DeleteDataSourceByUID)) datasourceRoute.Delete("/name/:name", authorize(reqOrgAdmin, ac.EvalPermission(ActionDatasourcesDelete, ScopeDatasourceName)), routing.Wrap(hs.DeleteDataSourceByName)) @@ -291,13 +287,13 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Get("/plugins/errors", routing.Wrap(hs.GetPluginErrorsList)) apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) { - pluginRoute.Post("/:pluginId/install", bind(dtos.InstallPluginCommand{}), routing.Wrap(hs.InstallPlugin)) + pluginRoute.Post("/:pluginId/install", routing.Wrap(hs.InstallPlugin)) pluginRoute.Post("/:pluginId/uninstall", routing.Wrap(hs.UninstallPlugin)) }, reqGrafanaAdmin) apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) { pluginRoute.Get("/:pluginId/dashboards/", routing.Wrap(hs.GetPluginDashboards)) - pluginRoute.Post("/:pluginId/settings", bind(models.UpdatePluginSettingCmd{}), routing.Wrap(hs.UpdatePluginSetting)) + pluginRoute.Post("/:pluginId/settings", routing.Wrap(hs.UpdatePluginSetting)) pluginRoute.Get("/:pluginId/metrics", routing.Wrap(hs.CollectPluginMetrics)) }, reqOrgAdmin) @@ -312,16 +308,16 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) { folderRoute.Get("/", routing.Wrap(hs.GetFolders)) folderRoute.Get("/id/:id", routing.Wrap(hs.GetFolderByID)) - folderRoute.Post("/", bind(models.CreateFolderCommand{}), routing.Wrap(hs.CreateFolder)) + folderRoute.Post("/", routing.Wrap(hs.CreateFolder)) folderRoute.Group("/:uid", func(folderUidRoute routing.RouteRegister) { folderUidRoute.Get("/", routing.Wrap(hs.GetFolderByUID)) - folderUidRoute.Put("/", bind(models.UpdateFolderCommand{}), routing.Wrap(hs.UpdateFolder)) + folderUidRoute.Put("/", routing.Wrap(hs.UpdateFolder)) folderUidRoute.Delete("/", routing.Wrap(hs.DeleteFolder)) folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) { folderPermissionRoute.Get("/", routing.Wrap(hs.GetFolderPermissionList)) - folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), routing.Wrap(hs.UpdateFolderPermissions)) + folderPermissionRoute.Post("/", routing.Wrap(hs.UpdateFolderPermissions)) }) }) }) @@ -331,22 +327,22 @@ func (hs *HTTPServer) registerRoutes() { dashboardRoute.Get("/uid/:uid", routing.Wrap(hs.GetDashboard)) dashboardRoute.Delete("/uid/:uid", routing.Wrap(hs.DeleteDashboardByUID)) - dashboardRoute.Post("/calculate-diff", bind(dtos.CalculateDiffOptions{}), routing.Wrap(CalculateDashboardDiff)) - dashboardRoute.Post("/trim", bind(models.TrimDashboardCommand{}), routing.Wrap(hs.TrimDashboard)) + dashboardRoute.Post("/calculate-diff", routing.Wrap(CalculateDashboardDiff)) + dashboardRoute.Post("/trim", routing.Wrap(hs.TrimDashboard)) - dashboardRoute.Post("/db", bind(models.SaveDashboardCommand{}), routing.Wrap(hs.PostDashboard)) + dashboardRoute.Post("/db", routing.Wrap(hs.PostDashboard)) dashboardRoute.Get("/home", routing.Wrap(hs.GetHomeDashboard)) dashboardRoute.Get("/tags", GetDashboardTags) - dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), routing.Wrap(hs.ImportDashboard)) + dashboardRoute.Post("/import", routing.Wrap(hs.ImportDashboard)) dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute routing.RouteRegister) { dashIdRoute.Get("/versions", routing.Wrap(GetDashboardVersions)) dashIdRoute.Get("/versions/:id", routing.Wrap(GetDashboardVersion)) - dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), routing.Wrap(hs.RestoreDashboardVersion)) + dashIdRoute.Post("/restore", routing.Wrap(hs.RestoreDashboardVersion)) dashIdRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) { dashboardPermissionRoute.Get("/", routing.Wrap(hs.GetDashboardPermissionList)) - dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), routing.Wrap(hs.UpdateDashboardPermissions)) + dashboardPermissionRoute.Post("/", routing.Wrap(hs.UpdateDashboardPermissions)) }) }) }) @@ -363,8 +359,8 @@ func (hs *HTTPServer) registerRoutes() { playlistRoute.Get("/:id/items", ValidateOrgPlaylist, routing.Wrap(GetPlaylistItems)) playlistRoute.Get("/:id/dashboards", ValidateOrgPlaylist, routing.Wrap(GetPlaylistDashboards)) playlistRoute.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, routing.Wrap(DeletePlaylist)) - playlistRoute.Put("/:id", reqEditorRole, bind(models.UpdatePlaylistCommand{}), ValidateOrgPlaylist, routing.Wrap(UpdatePlaylist)) - playlistRoute.Post("/", reqEditorRole, bind(models.CreatePlaylistCommand{}), routing.Wrap(CreatePlaylist)) + playlistRoute.Put("/:id", reqEditorRole, ValidateOrgPlaylist, routing.Wrap(UpdatePlaylist)) + playlistRoute.Post("/", reqEditorRole, routing.Wrap(CreatePlaylist)) }) // Search @@ -372,14 +368,14 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Get("/search/", routing.Wrap(Search)) // metrics - apiRoute.Post("/tsdb/query", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetrics)) + apiRoute.Post("/tsdb/query", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), routing.Wrap(hs.QueryMetrics)) // DataSource w/ expressions - apiRoute.Post("/ds/query", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetricsV2)) + apiRoute.Post("/ds/query", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), routing.Wrap(hs.QueryMetricsV2)) apiRoute.Group("/alerts", func(alertsRoute routing.RouteRegister) { - alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), routing.Wrap(hs.AlertTest)) - alertsRoute.Post("/:alertId/pause", reqEditorRole, bind(dtos.PauseAlertCommand{}), routing.Wrap(PauseAlert)) + alertsRoute.Post("/test", routing.Wrap(hs.AlertTest)) + alertsRoute.Post("/:alertId/pause", reqEditorRole, routing.Wrap(PauseAlert)) alertsRoute.Get("/:alertId", ValidateOrgAlert, routing.Wrap(GetAlert)) alertsRoute.Get("/", routing.Wrap(GetAlerts)) alertsRoute.Get("/states-for-dashboard", routing.Wrap(GetAlertStatesForDashboard)) @@ -391,13 +387,13 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Group("/alert-notifications", func(alertNotifications routing.RouteRegister) { alertNotifications.Get("/", routing.Wrap(GetAlertNotifications)) - alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), routing.Wrap(NotificationTest)) - alertNotifications.Post("/", bind(models.CreateAlertNotificationCommand{}), routing.Wrap(CreateAlertNotification)) - alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), routing.Wrap(hs.UpdateAlertNotification)) + alertNotifications.Post("/test", routing.Wrap(NotificationTest)) + alertNotifications.Post("/", routing.Wrap(CreateAlertNotification)) + alertNotifications.Put("/:notificationId", routing.Wrap(hs.UpdateAlertNotification)) alertNotifications.Get("/:notificationId", routing.Wrap(GetAlertNotificationByID)) alertNotifications.Delete("/:notificationId", routing.Wrap(DeleteAlertNotification)) alertNotifications.Get("/uid/:uid", routing.Wrap(GetAlertNotificationByUID)) - alertNotifications.Put("/uid/:uid", bind(models.UpdateAlertNotificationWithUidCommand{}), routing.Wrap(hs.UpdateAlertNotificationByUID)) + alertNotifications.Put("/uid/:uid", routing.Wrap(hs.UpdateAlertNotificationByUID)) alertNotifications.Delete("/uid/:uid", routing.Wrap(DeleteAlertNotificationByUID)) }, reqEditorRole) @@ -407,22 +403,22 @@ func (hs *HTTPServer) registerRoutes() { }) apiRoute.Get("/annotations", routing.Wrap(GetAnnotations)) - apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, bind(dtos.DeleteAnnotationsCmd{}), routing.Wrap(DeleteAnnotations)) + apiRoute.Post("/annotations/mass-delete", reqOrgAdmin, routing.Wrap(DeleteAnnotations)) apiRoute.Group("/annotations", func(annotationsRoute routing.RouteRegister) { - annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), routing.Wrap(PostAnnotation)) + annotationsRoute.Post("/", routing.Wrap(PostAnnotation)) annotationsRoute.Delete("/:annotationId", routing.Wrap(DeleteAnnotationByID)) - annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), routing.Wrap(UpdateAnnotation)) - annotationsRoute.Patch("/:annotationId", bind(dtos.PatchAnnotationsCmd{}), routing.Wrap(PatchAnnotation)) - annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), routing.Wrap(PostGraphiteAnnotation)) + annotationsRoute.Put("/:annotationId", routing.Wrap(UpdateAnnotation)) + annotationsRoute.Patch("/:annotationId", routing.Wrap(PatchAnnotation)) + annotationsRoute.Post("/graphite", reqEditorRole, routing.Wrap(PostGraphiteAnnotation)) annotationsRoute.Get("/tags", routing.Wrap(GetAnnotationTags)) }) - apiRoute.Post("/frontend-metrics", bind(metrics.PostFrontendMetricsCommand{}), routing.Wrap(hs.PostFrontendMetrics)) + apiRoute.Post("/frontend-metrics", routing.Wrap(hs.PostFrontendMetrics)) apiRoute.Group("/live", func(liveRoute routing.RouteRegister) { // the channel path is in the name - liveRoute.Post("/publish", bind(dtos.LivePublishCmd{}), routing.Wrap(hs.Live.HandleHTTPPublish)) + liveRoute.Post("/publish", routing.Wrap(hs.Live.HandleHTTPPublish)) // POST influx line protocol. liveRoute.Post("/push/:streamId", hs.LivePushGateway.Handle) @@ -450,14 +446,14 @@ func (hs *HTTPServer) registerRoutes() { }) // short urls - apiRoute.Post("/short-urls", bind(dtos.CreateShortURLCmd{}), routing.Wrap(hs.createShortURL)) + apiRoute.Post("/short-urls", routing.Wrap(hs.createShortURL)) }, reqSignedIn) // admin api r.Group("/api/admin", func(adminRoute routing.RouteRegister) { adminRoute.Get("/settings", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionSettingsRead)), routing.Wrap(hs.AdminGetSettings)) adminRoute.Get("/stats", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionServerStatsRead)), routing.Wrap(AdminGetStats)) - adminRoute.Post("/pause-all-alerts", reqGrafanaAdmin, bind(dtos.PauseAllAlertsCommand{}), routing.Wrap(PauseAllAlerts)) + adminRoute.Post("/pause-all-alerts", reqGrafanaAdmin, routing.Wrap(PauseAllAlerts)) adminRoute.Post("/provisioning/dashboards/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersDashboards)), routing.Wrap(hs.AdminProvisioningReloadDashboards)) adminRoute.Post("/provisioning/plugins/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersPlugins)), routing.Wrap(hs.AdminProvisioningReloadPlugins)) @@ -474,18 +470,18 @@ func (hs *HTTPServer) registerRoutes() { r.Group("/api/admin/users", func(adminUserRoute routing.RouteRegister) { userIDScope := ac.Scope("global", "users", "id", ac.Parameter(":id")) - adminUserRoute.Post("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersCreate)), bind(dtos.AdminCreateUserForm{}), routing.Wrap(hs.AdminCreateUser)) - adminUserRoute.Put("/:id/password", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersPasswordUpdate, userIDScope)), bind(dtos.AdminUpdateUserPasswordForm{}), routing.Wrap(AdminUpdateUserPassword)) - adminUserRoute.Put("/:id/permissions", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersPermissionsUpdate, userIDScope)), bind(dtos.AdminUpdateUserPermissionsForm{}), routing.Wrap(hs.AdminUpdateUserPermissions)) + adminUserRoute.Post("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.AdminCreateUser)) + adminUserRoute.Put("/:id/password", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersPasswordUpdate, userIDScope)), routing.Wrap(AdminUpdateUserPassword)) + adminUserRoute.Put("/:id/permissions", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersPermissionsUpdate, userIDScope)), routing.Wrap(hs.AdminUpdateUserPermissions)) adminUserRoute.Delete("/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersDelete, userIDScope)), routing.Wrap(AdminDeleteUser)) adminUserRoute.Post("/:id/disable", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersDisable, userIDScope)), routing.Wrap(hs.AdminDisableUser)) adminUserRoute.Post("/:id/enable", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersEnable, userIDScope)), routing.Wrap(AdminEnableUser)) adminUserRoute.Get("/:id/quotas", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersQuotasList, userIDScope)), routing.Wrap(GetUserQuotas)) - adminUserRoute.Put("/:id/quotas/:target", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersQuotasUpdate, userIDScope)), bind(models.UpdateUserQuotaCmd{}), routing.Wrap(UpdateUserQuota)) + adminUserRoute.Put("/:id/quotas/:target", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersQuotasUpdate, userIDScope)), routing.Wrap(UpdateUserQuota)) adminUserRoute.Post("/:id/logout", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersLogout, userIDScope)), routing.Wrap(hs.AdminLogoutUser)) adminUserRoute.Get("/:id/auth-tokens", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersAuthTokenList, userIDScope)), routing.Wrap(hs.AdminGetUserAuthTokens)) - adminUserRoute.Post("/:id/revoke-auth-token", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersAuthTokenUpdate, userIDScope)), bind(models.RevokeAuthTokenCmd{}), routing.Wrap(hs.AdminRevokeUserAuthToken)) + adminUserRoute.Post("/:id/revoke-auth-token", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersAuthTokenUpdate, userIDScope)), routing.Wrap(hs.AdminRevokeUserAuthToken)) }) // rendering @@ -499,7 +495,7 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/avatar/:hash", avatarCacheServer.Handler) // Snapshots - r.Post("/api/snapshots/", reqSnapshotPublicModeOrSignedIn, bind(models.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot) + r.Post("/api/snapshots/", reqSnapshotPublicModeOrSignedIn, CreateDashboardSnapshot) r.Get("/api/snapshot/shared-options/", reqSignedIn, GetSharingOptions) r.Get("/api/snapshots/:key", routing.Wrap(GetDashboardSnapshot)) r.Get("/api/snapshots-delete/:deleteKey", reqSnapshotPublicModeOrSignedIn, routing.Wrap(DeleteDashboardSnapshotByDeleteKey)) @@ -508,5 +504,5 @@ func (hs *HTTPServer) registerRoutes() { // Frontend logs sourceMapStore := frontendlogging.NewSourceMapStore(hs.Cfg, hs.pluginStaticRouteResolver, frontendlogging.ReadSourceMapFromFS) r.Post("/log", middleware.RateLimit(hs.Cfg.Sentry.EndpointRPS, hs.Cfg.Sentry.EndpointBurst, time.Now), - bind(frontendlogging.FrontendSentryEvent{}), routing.Wrap(NewFrontendLogMessageHandler(sourceMapStore))) + routing.Wrap(NewFrontendLogMessageHandler(sourceMapStore))) } diff --git a/pkg/api/apikey.go b/pkg/api/apikey.go index 8e2f92fde35..12a8ddc956c 100644 --- a/pkg/api/apikey.go +++ b/pkg/api/apikey.go @@ -2,6 +2,7 @@ package api import ( "errors" + "net/http" "time" "github.com/grafana/grafana/pkg/api/dtos" @@ -9,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/web" ) // GetAPIKeys returns a list of API keys @@ -58,7 +60,11 @@ func DeleteAPIKey(c *models.ReqContext) response.Response { } // AddAPIKey adds an API key -func (hs *HTTPServer) AddAPIKey(c *models.ReqContext, cmd models.AddApiKeyCommand) response.Response { +func (hs *HTTPServer) AddAPIKey(c *models.ReqContext) response.Response { + cmd := models.AddApiKeyCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if !cmd.Role.IsValid() { return response.Error(400, "Invalid role specified", nil) } @@ -129,7 +135,11 @@ func (hs *HTTPServer) AddAPIKey(c *models.ReqContext, cmd models.AddApiKeyComman } // AddAPIKey adds an additional API key to a service account -func (hs *HTTPServer) AdditionalAPIKey(c *models.ReqContext, cmd models.AddApiKeyCommand) response.Response { +func (hs *HTTPServer) AdditionalAPIKey(c *models.ReqContext) response.Response { + cmd := models.AddApiKeyCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if !hs.Cfg.FeatureToggles["service-accounts"] { return response.Error(500, "Requires services-accounts feature", errors.New("feature missing")) } @@ -137,5 +147,5 @@ func (hs *HTTPServer) AdditionalAPIKey(c *models.ReqContext, cmd models.AddApiKe return response.Error(500, "Can't create service account while adding additional API key", nil) } - return hs.AddAPIKey(c, cmd) + return hs.AddAPIKey(c) } diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index d01e926e261..65dff2d974c 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -1,7 +1,9 @@ package api import ( + "bytes" "context" + "encoding/json" "fmt" "io" "net/http" @@ -52,10 +54,6 @@ func loggedInUserScenarioWithRole(t *testing.T, desc string, method string, url return sc.handlerFunc(sc.context) } - if sc.handlerFuncCtx != nil { - return sc.handlerFuncCtx(context.Background(), sc.context) - } - return nil }) @@ -81,10 +79,6 @@ func anonymousUserScenario(t *testing.T, desc string, method string, url string, return sc.handlerFunc(sc.context) } - if sc.handlerFuncCtx != nil { - return sc.handlerFuncCtx(context.Background(), sc.context) - } - return nil }) @@ -154,7 +148,6 @@ type scenarioContext struct { context *models.ReqContext resp *httptest.ResponseRecorder handlerFunc handlerFunc - handlerFuncCtx handlerFuncCtx defaultHandler web.Handler req *http.Request url string @@ -167,7 +160,6 @@ func (sc *scenarioContext) exec() { type scenarioFunc func(c *scenarioContext) type handlerFunc func(c *models.ReqContext) response.Response -type handlerFuncCtx func(ctx context.Context, c *models.ReqContext) response.Response func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHandler { t.Helper() @@ -376,3 +368,8 @@ func callAPI(server *web.Mux, method, path string, body io.Reader, t *testing.T) server.ServeHTTP(recorder, req) return recorder } + +func mockRequestBody(v interface{}) io.ReadCloser { + b, _ := json.Marshal(v) + return io.NopCloser(bytes.NewReader(b)) +} diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 867af1f5501..ad8d984e43f 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "os" "path/filepath" "strconv" @@ -49,7 +50,11 @@ func dashboardGuardianResponse(err error) response.Response { return response.Error(403, "Access denied to this dashboard", nil) } -func (hs *HTTPServer) TrimDashboard(c *models.ReqContext, cmd models.TrimDashboardCommand) response.Response { +func (hs *HTTPServer) TrimDashboard(c *models.ReqContext) response.Response { + cmd := models.TrimDashboardCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } var err error dash := cmd.Dashboard meta := cmd.Meta @@ -277,7 +282,15 @@ func (hs *HTTPServer) deleteDashboard(c *models.ReqContext) response.Response { }) } -func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboardCommand) response.Response { +func (hs *HTTPServer) PostDashboard(c *models.ReqContext) response.Response { + cmd := models.SaveDashboardCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } + return hs.postDashboard(c, cmd) +} + +func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboardCommand) response.Response { ctx := c.Req.Context() var err error cmd.OrgId = c.OrgId @@ -585,7 +598,11 @@ func GetDashboardVersion(c *models.ReqContext) response.Response { } // POST /api/dashboards/calculate-diff performs diffs on two dashboards -func CalculateDashboardDiff(c *models.ReqContext, apiOptions dtos.CalculateDiffOptions) response.Response { +func CalculateDashboardDiff(c *models.ReqContext) response.Response { + apiOptions := dtos.CalculateDiffOptions{} + if err := web.Bind(c.Req, &apiOptions); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } guardianBase := guardian.New(c.Req.Context(), apiOptions.Base.DashboardId, c.OrgId, c.SignedInUser) if canSave, err := guardianBase.CanSave(); err != nil || !canSave { return dashboardGuardianResponse(err) @@ -629,7 +646,11 @@ func CalculateDashboardDiff(c *models.ReqContext, apiOptions dtos.CalculateDiffO } // RestoreDashboardVersion restores a dashboard to the given version. -func (hs *HTTPServer) RestoreDashboardVersion(c *models.ReqContext, apiCmd dtos.RestoreDashboardVersionCommand) response.Response { +func (hs *HTTPServer) RestoreDashboardVersion(c *models.ReqContext) response.Response { + apiCmd := dtos.RestoreDashboardVersionCommand{} + if err := web.Bind(c.Req, &apiCmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } dash, rsp := getDashboardHelper(c.Req.Context(), c.OrgId, c.ParamsInt64(":dashboardId"), "") if rsp != nil { return rsp @@ -657,7 +678,7 @@ func (hs *HTTPServer) RestoreDashboardVersion(c *models.ReqContext, apiCmd dtos. saveCmd.Message = fmt.Sprintf("Restored from version %d", version.Version) saveCmd.FolderId = dash.FolderId - return hs.PostDashboard(c, saveCmd) + return hs.postDashboard(c, saveCmd) } func GetDashboardTags(c *models.ReqContext) { diff --git a/pkg/api/dashboard_permission.go b/pkg/api/dashboard_permission.go index 0ca84595eea..d51c8cf1c7e 100644 --- a/pkg/api/dashboard_permission.go +++ b/pkg/api/dashboard_permission.go @@ -2,12 +2,14 @@ package api import ( "errors" + "net/http" "time" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/guardian" + "github.com/grafana/grafana/pkg/web" ) func (hs *HTTPServer) GetDashboardPermissionList(c *models.ReqContext) response.Response { @@ -50,7 +52,11 @@ func (hs *HTTPServer) GetDashboardPermissionList(c *models.ReqContext) response. return response.JSON(200, filteredAcls) } -func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) response.Response { +func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response.Response { + apiCmd := dtos.UpdateDashboardAclCommand{} + if err := web.Bind(c.Req, &apiCmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if err := validatePermissionsUpdate(apiCmd); err != nil { return response.Error(400, err.Error(), err) } diff --git a/pkg/api/dashboard_permission_test.go b/pkg/api/dashboard_permission_test.go index 67ba396f979..a54f61f893b 100644 --- a/pkg/api/dashboard_permission_test.go +++ b/pkg/api/dashboard_permission_test.go @@ -433,11 +433,12 @@ func updateDashboardPermissionScenario(t *testing.T, ctx updatePermissionContext sc := setupScenarioContext(t, ctx.url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(ctx.cmd) sc.context = c sc.context.OrgId = testOrgID sc.context.UserId = testUserID - return hs.UpdateDashboardPermissions(c, ctx.cmd) + return hs.UpdateDashboardPermissions(c) }) sc.m.Post(ctx.routePattern, sc.defaultHandler) diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index f87105abf90..d5ef96b695a 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -1122,10 +1122,11 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.SignedInUser = &models.SignedInUser{OrgId: cmd.OrgId, UserId: cmd.UserId} - return hs.PostDashboard(c, cmd) + return hs.PostDashboard(c) }) origNewDashboardService := dashboards.NewService @@ -1154,6 +1155,7 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.SignedInUser = &models.SignedInUser{ OrgId: testOrgID, @@ -1161,7 +1163,7 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string } sc.context.OrgRole = role - return CalculateDashboardDiff(c, cmd) + return CalculateDashboardDiff(c) }) sc.m.Post(routePattern, sc.defaultHandler) @@ -1188,6 +1190,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.SignedInUser = &models.SignedInUser{ OrgId: testOrgID, @@ -1195,7 +1198,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout } sc.context.OrgRole = models.ROLE_ADMIN - return hs.RestoreDashboardVersion(c, cmd) + return hs.RestoreDashboardVersion(c) }) origProvisioningService := dashboards.NewProvisioningService diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 28464852d7c..610d9eba14b 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "sort" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -211,7 +212,11 @@ func validateURL(tp string, u string) response.Response { return nil } -func AddDataSource(c *models.ReqContext, cmd models.AddDataSourceCommand) response.Response { +func AddDataSource(c *models.ReqContext) response.Response { + cmd := models.AddDataSourceCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } datasourcesLogger.Debug("Received command to add data source", "url", cmd.Url) cmd.OrgId = c.OrgId if resp := validateURL(cmd.Type, cmd.Url); resp != nil { @@ -235,7 +240,11 @@ func AddDataSource(c *models.ReqContext, cmd models.AddDataSourceCommand) respon }) } -func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext, cmd models.UpdateDataSourceCommand) response.Response { +func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext) response.Response { + cmd := models.UpdateDataSourceCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } datasourcesLogger.Debug("Received command to update data source", "url", cmd.Url) cmd.OrgId = c.OrgId cmd.Id = c.ParamsInt64(":id") diff --git a/pkg/api/datasources_test.go b/pkg/api/datasources_test.go index ff5637b022f..d41d53a1739 100644 --- a/pkg/api/datasources_test.go +++ b/pkg/api/datasources_test.go @@ -80,10 +80,13 @@ func TestAddDataSource_InvalidURL(t *testing.T) { sc := setupScenarioContext(t, "/api/datasources") sc.m.Post(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response { - return AddDataSource(c, models.AddDataSourceCommand{ - Name: "Test", - Url: "invalid:url", + c.Req.Body = mockRequestBody(models.AddDataSourceCommand{ + Name: "Test", + Url: "invalid:url", + Access: "direct", + Type: "test", }) + return AddDataSource(c) })) sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() @@ -110,10 +113,13 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) { sc := setupScenarioContext(t, "/api/datasources") sc.m.Post(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response { - return AddDataSource(c, models.AddDataSourceCommand{ - Name: name, - Url: url, + c.Req.Body = mockRequestBody(models.AddDataSourceCommand{ + Name: name, + Url: url, + Access: "direct", + Type: "test", }) + return AddDataSource(c) })) sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() @@ -128,10 +134,13 @@ func TestUpdateDataSource_InvalidURL(t *testing.T) { sc := setupScenarioContext(t, "/api/datasources/1234") sc.m.Put(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response { - return AddDataSource(c, models.AddDataSourceCommand{ - Name: "Test", - Url: "invalid:url", + c.Req.Body = mockRequestBody(models.AddDataSourceCommand{ + Name: "Test", + Url: "invalid:url", + Access: "direct", + Type: "test", }) + return AddDataSource(c) })) sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() @@ -158,10 +167,13 @@ func TestUpdateDataSource_URLWithoutProtocol(t *testing.T) { sc := setupScenarioContext(t, "/api/datasources/1234") sc.m.Put(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response { - return AddDataSource(c, models.AddDataSourceCommand{ - Name: name, - Url: url, + c.Req.Body = mockRequestBody(models.AddDataSourceCommand{ + Name: name, + Url: url, + Access: "direct", + Type: "test", }) + return AddDataSource(c) })) sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec() diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 27f1a0cc5ef..a59c3cb483b 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net/http" "github.com/grafana/grafana/pkg/api/apierrors" "github.com/grafana/grafana/pkg/api/dtos" @@ -59,7 +60,11 @@ func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response { return response.JSON(200, toFolderDto(c.Req.Context(), g, folder)) } -func (hs *HTTPServer) CreateFolder(c *models.ReqContext, cmd models.CreateFolderCommand) response.Response { +func (hs *HTTPServer) CreateFolder(c *models.ReqContext) response.Response { + cmd := models.CreateFolderCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore) folder, err := s.CreateFolder(c.Req.Context(), cmd.Title, cmd.Uid) if err != nil { @@ -77,7 +82,11 @@ func (hs *HTTPServer) CreateFolder(c *models.ReqContext, cmd models.CreateFolder return response.JSON(200, toFolderDto(c.Req.Context(), g, folder)) } -func (hs *HTTPServer) UpdateFolder(c *models.ReqContext, cmd models.UpdateFolderCommand) response.Response { +func (hs *HTTPServer) UpdateFolder(c *models.ReqContext) response.Response { + cmd := models.UpdateFolderCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore) err := s.UpdateFolder(c.Req.Context(), web.Params(c.Req)[":uid"], &cmd) if err != nil { diff --git a/pkg/api/folder_permission.go b/pkg/api/folder_permission.go index a252c6f6779..3ffd756b3f0 100644 --- a/pkg/api/folder_permission.go +++ b/pkg/api/folder_permission.go @@ -2,6 +2,7 @@ package api import ( "errors" + "net/http" "time" "github.com/grafana/grafana/pkg/api/apierrors" @@ -58,7 +59,11 @@ func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Res return response.JSON(200, filteredAcls) } -func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) response.Response { +func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Response { + apiCmd := dtos.UpdateDashboardAclCommand{} + if err := web.Bind(c.Req, &apiCmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if err := validatePermissionsUpdate(apiCmd); err != nil { return response.Error(400, err.Error(), err) } diff --git a/pkg/api/folder_permission_test.go b/pkg/api/folder_permission_test.go index e5eee71ce06..410ae187862 100644 --- a/pkg/api/folder_permission_test.go +++ b/pkg/api/folder_permission_test.go @@ -402,11 +402,12 @@ func updateFolderPermissionScenario(t *testing.T, ctx updatePermissionContext, h sc := setupScenarioContext(t, ctx.url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(ctx.cmd) sc.context = c sc.context.OrgId = testOrgID sc.context.UserId = testUserID - return hs.UpdateFolderPermissions(c, ctx.cmd) + return hs.UpdateFolderPermissions(c) }) sc.m.Post(ctx.routePattern, sc.defaultHandler) diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index a50d2338c93..1a167304ecc 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -148,10 +148,11 @@ func createFolderScenario(t *testing.T, desc string, url string, routePattern st sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID} - return hs.CreateFolder(c, cmd) + return hs.CreateFolder(c) }) origNewFolderService := dashboards.NewFolderService @@ -182,10 +183,11 @@ func updateFolderScenario(t *testing.T, desc string, url string, routePattern st sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID} - return hs.UpdateFolder(c, cmd) + return hs.UpdateFolder(c) }) origNewFolderService := dashboards.NewFolderService diff --git a/pkg/api/frontend_logging.go b/pkg/api/frontend_logging.go index 2468f69684e..fd57fcb7a5a 100644 --- a/pkg/api/frontend_logging.go +++ b/pkg/api/frontend_logging.go @@ -1,19 +1,27 @@ package api import ( + "net/http" + "github.com/getsentry/sentry-go" "github.com/grafana/grafana/pkg/api/frontendlogging" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/web" ) var frontendLogger = log.New("frontend") -type frontendLogMessageHandler func(c *models.ReqContext, event frontendlogging.FrontendSentryEvent) response.Response +type frontendLogMessageHandler func(c *models.ReqContext) response.Response func NewFrontendLogMessageHandler(store *frontendlogging.SourceMapStore) frontendLogMessageHandler { - return func(c *models.ReqContext, event frontendlogging.FrontendSentryEvent) response.Response { + return func(c *models.ReqContext) response.Response { + event := frontendlogging.FrontendSentryEvent{} + if err := web.Bind(c.Req, &event); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } + var msg = "unknown" if len(event.Message) > 0 { diff --git a/pkg/api/frontend_logging_test.go b/pkg/api/frontend_logging_test.go index 03484f7492a..87130c12909 100644 --- a/pkg/api/frontend_logging_test.go +++ b/pkg/api/frontend_logging_test.go @@ -3,7 +3,6 @@ package api import ( "errors" "io/ioutil" - "net/http" "net/url" "os" "strings" @@ -84,9 +83,10 @@ func logSentryEventScenario(t *testing.T, desc string, event frontendlogging.Fro loggingHandler := NewFrontendLogMessageHandler(sourceMapStore) - handler := routing.Wrap(func(w http.ResponseWriter, c *models.ReqContext) response.Response { + handler := routing.Wrap(func(c *models.ReqContext) response.Response { sc.context = c - return loggingHandler(c, event) + c.Req.Body = mockRequestBody(event) + return loggingHandler(c) }) sc.m.Post(sc.url, handler) diff --git a/pkg/api/frontend_metrics.go b/pkg/api/frontend_metrics.go index 68f760fd02a..70516447258 100644 --- a/pkg/api/frontend_metrics.go +++ b/pkg/api/frontend_metrics.go @@ -1,14 +1,20 @@ package api import ( + "net/http" "strings" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/web" ) -func (hs *HTTPServer) PostFrontendMetrics(c *models.ReqContext, cmd metrics.PostFrontendMetricsCommand) response.Response { +func (hs *HTTPServer) PostFrontendMetrics(c *models.ReqContext) response.Response { + cmd := metrics.PostFrontendMetricsCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } for _, event := range cmd.Events { name := strings.Replace(event.Name, "-", "_", -1) if recorder, ok := metrics.FrontendMetrics[name]; ok { diff --git a/pkg/api/frontendlogging/sentry.go b/pkg/api/frontendlogging/sentry.go index 1091f056781..56eff6a1e3b 100644 --- a/pkg/api/frontendlogging/sentry.go +++ b/pkg/api/frontendlogging/sentry.go @@ -1,6 +1,7 @@ package frontendlogging import ( + "encoding/json" "fmt" "strings" @@ -93,3 +94,16 @@ func (event *FrontendSentryEvent) ToLogContext(store *SourceMapStore) log15.Ctx return ctx } + +func (event *FrontendSentryEvent) MarshalJSON() ([]byte, error) { + eventJSON, err := json.Marshal(event.Event) + if err != nil { + return nil, err + } + exceptionJSON, err := json.Marshal(map[string]interface{}{"exception": event.Exception}) + if err != nil { + return nil, err + } + exceptionJSON[0] = ',' + return append(eventJSON[:len(eventJSON)-1], exceptionJSON...), nil +} diff --git a/pkg/api/ldap_debug.go b/pkg/api/ldap_debug.go index 753b76a1a92..1bfe3a4cd57 100644 --- a/pkg/api/ldap_debug.go +++ b/pkg/api/ldap_debug.go @@ -100,7 +100,7 @@ func (user *LDAPUserDTO) FetchOrgs(ctx context.Context) error { } // ReloadLDAPCfg reloads the LDAP configuration -func (hs *HTTPServer) ReloadLDAPCfg() response.Response { +func (hs *HTTPServer) ReloadLDAPCfg(c *models.ReqContext) response.Response { if !ldap.IsEnabled() { return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil) } diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 751115c900d..bf2c7d0c197 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -11,6 +11,7 @@ import ( "net/http" "net/url" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -67,7 +68,7 @@ func genPKCECode() (string, string, error) { return string(ascii), pkce, nil } -func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { +func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) response.Response { loginInfo := models.LoginInfo{ AuthModule: "oauth", } @@ -79,7 +80,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { HttpStatus: http.StatusNotFound, PublicMessage: "OAuth not enabled", }) - return + return nil } connect, err := hs.SocialService.GetConnector(name) @@ -88,7 +89,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { HttpStatus: http.StatusNotFound, PublicMessage: fmt.Sprintf("No OAuth with name %s configured", name), }) - return + return nil } errorParam := ctx.Query("error") @@ -96,7 +97,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { errorDesc := ctx.Query("error_description") oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc) hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc) - return + return nil } code := ctx.Query("code") @@ -128,7 +129,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { HttpStatus: http.StatusInternalServerError, PublicMessage: "An internal error occurred", }) - return + return nil } hashedState := hashStatecode(state, provider.ClientSecret) @@ -138,7 +139,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { } ctx.Redirect(connect.AuthCodeURL(state, opts...)) - return + return nil } cookieState := ctx.GetCookie(OauthStateCookieName) @@ -151,7 +152,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { HttpStatus: http.StatusInternalServerError, PublicMessage: "login.OAuthLogin(missing saved state)", }) - return + return nil } queryState := hashStatecode(ctx.Query("state"), provider.ClientSecret) @@ -161,7 +162,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { HttpStatus: http.StatusInternalServerError, PublicMessage: "login.OAuthLogin(state mismatch)", }) - return + return nil } oauthClient, err := hs.SocialService.GetOAuthHttpClient(name) @@ -171,7 +172,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { HttpStatus: http.StatusInternalServerError, PublicMessage: "login.OAuthLogin(" + err.Error() + ")", }) - return + return nil } oauthCtx := context.WithValue(context.Background(), oauth2.HTTPClient, oauthClient) @@ -193,7 +194,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { PublicMessage: "login.OAuthLogin(NewTransportWithCode)", Err: err, }) - return + return nil } // token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer" token.TokenType = "Bearer" @@ -216,7 +217,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { Err: err, }) } - return + return nil } oauthLogger.Debug("OAuthLogin got user info", "userInfo", userInfo) @@ -224,26 +225,26 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { // validate that we got at least an email address if userInfo.Email == "" { hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrNoEmail) - return + return nil } // validate that the email is allowed to login to grafana if !connect.IsEmailAllowed(userInfo.Email) { hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrEmailNotAllowed) - return + return nil } loginInfo.ExternalUser = *buildExternalUserInfo(token, userInfo, name) loginInfo.User, err = syncUser(ctx, &loginInfo.ExternalUser, connect) if err != nil { hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err) - return + return nil } // login if err := hs.loginUserWithUser(loginInfo.User, ctx); err != nil { hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err) - return + return nil } loginInfo.HTTPStatus = http.StatusOK @@ -254,12 +255,13 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { if err := hs.ValidateRedirectTo(redirectTo); err == nil { cookies.DeleteCookie(ctx.Resp, "redirect_to", hs.CookieOptionsFromCfg) ctx.Redirect(redirectTo) - return + return nil } ctx.Logger.Debug("Ignored invalid redirect_to cookie value", "redirect_to", redirectTo) } ctx.Redirect(setting.AppSubUrl + "/") + return nil } // buildExternalUserInfo returns a ExternalUserInfo struct from OAuth user profile diff --git a/pkg/api/login_test.go b/pkg/api/login_test.go index ade901eaecc..28814b6b5bf 100644 --- a/pkg/api/login_test.go +++ b/pkg/api/login_test.go @@ -118,8 +118,9 @@ func TestLoginErrorCookieAPIEndpoint(t *testing.T) { SecretsService: secretsService, } - sc.defaultHandler = routing.Wrap(func(w http.ResponseWriter, c *models.ReqContext) { + sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { hs.LoginView(c) + return response.Empty(http.StatusOK) }) cfg.LoginCookieName = "grafana_session" @@ -166,12 +167,13 @@ func TestLoginViewRedirect(t *testing.T) { } hs.Cfg.CookieSecure = true - sc.defaultHandler = routing.Wrap(func(w http.ResponseWriter, c *models.ReqContext) { + sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { c.IsSignedIn = true c.SignedInUser = &models.SignedInUser{ UserId: 10, } hs.LoginView(c) + return response.Empty(http.StatusOK) }) redirectCases := []redirectCase{ @@ -340,7 +342,7 @@ func TestLoginPostRedirect(t *testing.T) { } hs.Cfg.CookieSecure = true - sc.defaultHandler = routing.Wrap(func(w http.ResponseWriter, c *models.ReqContext) response.Response { + sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { c.Req.Header.Set("Content-Type", "application/json") c.Req.Body = io.NopCloser(bytes.NewBufferString(`{"user":"admin","password":"admin"}`)) return hs.LoginPost(c) @@ -504,8 +506,9 @@ func TestLoginOAuthRedirect(t *testing.T) { SocialService: mock, } - sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) { + sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { hs.LoginView(c) + return response.Empty(http.StatusOK) }) setting.OAuthAutoLogin = true @@ -529,9 +532,10 @@ func TestLoginInternal(t *testing.T) { log: log.New("test"), } - sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) { + sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { c.Req.URL.RawQuery = "disableAutoLogin=true" hs.LoginView(c) + return response.Empty(http.StatusOK) }) setting.OAuthAutoLogin = true @@ -580,12 +584,13 @@ func setupAuthProxyLoginTest(t *testing.T, enableLoginToken bool) *scenarioConte SocialService: &mockSocialService{}, } - sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) { + sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { c.IsSignedIn = true c.SignedInUser = &models.SignedInUser{ UserId: 10, } hs.LoginView(c) + return response.Empty(http.StatusOK) }) sc.cfg.AuthProxyEnabled = true @@ -616,7 +621,7 @@ func TestLoginPostRunLokingHook(t *testing.T) { HooksService: hookService, } - sc.defaultHandler = routing.Wrap(func(w http.ResponseWriter, c *models.ReqContext) response.Response { + sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { c.Req.Header.Set("Content-Type", "application/json") c.Req.Body = io.NopCloser(bytes.NewBufferString(`{"user":"admin","password":"admin"}`)) x := hs.LoginPost(c) diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 369088820ab..86ef24b6f97 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -16,11 +16,16 @@ import ( "github.com/grafana/grafana/pkg/plugins/adapters" "github.com/grafana/grafana/pkg/tsdb/grafanads" "github.com/grafana/grafana/pkg/tsdb/legacydata" + "github.com/grafana/grafana/pkg/web" ) // QueryMetricsV2 returns query metrics. // POST /api/ds/query DataSource query w/ expressions -func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricRequest) response.Response { +func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext) response.Response { + reqDTO := dtos.MetricRequest{} + if err := web.Bind(c.Req, &reqDTO); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if len(reqDTO.Queries) == 0 { return response.Error(http.StatusBadRequest, "No queries found in query", nil) } @@ -162,7 +167,11 @@ func (hs *HTTPServer) handleGetDataSourceError(err error, datasourceRef interfac // QueryMetrics returns query metrics // POST /api/tsdb/query -func (hs *HTTPServer) QueryMetrics(c *models.ReqContext, reqDto dtos.MetricRequest) response.Response { +func (hs *HTTPServer) QueryMetrics(c *models.ReqContext) response.Response { + reqDto := dtos.MetricRequest{} + if err := web.Bind(c.Req, &reqDto); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if len(reqDto.Queries) == 0 { return response.Error(http.StatusBadRequest, "No queries found in query", nil) } diff --git a/pkg/api/org.go b/pkg/api/org.go index 4f95bbd05ad..68e88d25d4c 100644 --- a/pkg/api/org.go +++ b/pkg/api/org.go @@ -3,6 +3,7 @@ package api import ( "context" "errors" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -78,7 +79,11 @@ func getOrgHelper(ctx context.Context, orgID int64) response.Response { } // POST /api/orgs -func (hs *HTTPServer) CreateOrg(c *models.ReqContext, cmd models.CreateOrgCommand) response.Response { +func (hs *HTTPServer) CreateOrg(c *models.ReqContext) response.Response { + cmd := models.CreateOrgCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } acEnabled := hs.Cfg.FeatureToggles["accesscontrol"] if !acEnabled && !(setting.AllowUserOrgCreate || c.IsGrafanaAdmin) { return response.Error(403, "Access denied", nil) @@ -101,12 +106,20 @@ func (hs *HTTPServer) CreateOrg(c *models.ReqContext, cmd models.CreateOrgComman } // PUT /api/org -func UpdateCurrentOrg(c *models.ReqContext, form dtos.UpdateOrgForm) response.Response { +func UpdateCurrentOrg(c *models.ReqContext) response.Response { + form := dtos.UpdateOrgForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } return updateOrgHelper(c.Req.Context(), form, c.OrgId) } // PUT /api/orgs/:orgId -func UpdateOrg(c *models.ReqContext, form dtos.UpdateOrgForm) response.Response { +func UpdateOrg(c *models.ReqContext) response.Response { + form := dtos.UpdateOrgForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } return updateOrgHelper(c.Req.Context(), form, c.ParamsInt64(":orgId")) } @@ -123,12 +136,20 @@ func updateOrgHelper(ctx context.Context, form dtos.UpdateOrgForm, orgID int64) } // PUT /api/org/address -func UpdateCurrentOrgAddress(c *models.ReqContext, form dtos.UpdateOrgAddressForm) response.Response { +func UpdateCurrentOrgAddress(c *models.ReqContext) response.Response { + form := dtos.UpdateOrgAddressForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } return updateOrgAddressHelper(c.Req.Context(), form, c.OrgId) } // PUT /api/orgs/:orgId/address -func UpdateOrgAddress(c *models.ReqContext, form dtos.UpdateOrgAddressForm) response.Response { +func UpdateOrgAddress(c *models.ReqContext) response.Response { + form := dtos.UpdateOrgAddressForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } return updateOrgAddressHelper(c.Req.Context(), form, c.ParamsInt64(":orgId")) } diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index f44a835bf4c..ac8b1fe9a6e 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -30,7 +31,11 @@ func GetPendingOrgInvites(c *models.ReqContext) response.Response { return response.JSON(200, query.Result) } -func AddOrgInvite(c *models.ReqContext, inviteDto dtos.AddInviteForm) response.Response { +func AddOrgInvite(c *models.ReqContext) response.Response { + inviteDto := dtos.AddInviteForm{} + if err := web.Bind(c.Req, &inviteDto); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if !inviteDto.Role.IsValid() { return response.Error(400, "Invalid role specified", nil) } @@ -165,7 +170,11 @@ func GetInviteInfoByCode(c *models.ReqContext) response.Response { }) } -func (hs *HTTPServer) CompleteInvite(c *models.ReqContext, completeInvite dtos.CompleteInviteForm) response.Response { +func (hs *HTTPServer) CompleteInvite(c *models.ReqContext) response.Response { + completeInvite := dtos.CompleteInviteForm{} + if err := web.Bind(c.Req, &completeInvite); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } query := models.GetTempUserByCodeQuery{Code: completeInvite.InviteCode} if err := bus.DispatchCtx(c.Req.Context(), &query); err != nil { diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go index 0b08d38211f..9b4fe3dfdfd 100644 --- a/pkg/api/org_users.go +++ b/pkg/api/org_users.go @@ -3,21 +3,31 @@ package api import ( "context" "errors" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" ) // POST /api/org/users -func (hs *HTTPServer) AddOrgUserToCurrentOrg(c *models.ReqContext, cmd models.AddOrgUserCommand) response.Response { +func (hs *HTTPServer) AddOrgUserToCurrentOrg(c *models.ReqContext) response.Response { + cmd := models.AddOrgUserCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.OrgId return hs.addOrgUserHelper(c.Req.Context(), cmd) } // POST /api/orgs/:orgId/users -func (hs *HTTPServer) AddOrgUser(c *models.ReqContext, cmd models.AddOrgUserCommand) response.Response { +func (hs *HTTPServer) AddOrgUser(c *models.ReqContext) response.Response { + cmd := models.AddOrgUserCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.ParamsInt64(":orgId") return hs.addOrgUserHelper(c.Req.Context(), cmd) } @@ -128,7 +138,8 @@ func (hs *HTTPServer) getOrgUsersHelper(ctx context.Context, query *models.GetOr // SearchOrgUsersWithPaging is an HTTP handler to search for org users with paging. // GET /api/org/users/search -func (hs *HTTPServer) SearchOrgUsersWithPaging(ctx context.Context, c *models.ReqContext) response.Response { +func (hs *HTTPServer) SearchOrgUsersWithPaging(c *models.ReqContext) response.Response { + ctx := c.Req.Context() perPage := c.QueryInt("perpage") if perPage <= 0 { perPage = 1000 @@ -168,14 +179,22 @@ func (hs *HTTPServer) SearchOrgUsersWithPaging(ctx context.Context, c *models.Re } // PATCH /api/org/users/:userId -func (hs *HTTPServer) UpdateOrgUserForCurrentOrg(c *models.ReqContext, cmd models.UpdateOrgUserCommand) response.Response { +func (hs *HTTPServer) UpdateOrgUserForCurrentOrg(c *models.ReqContext) response.Response { + cmd := models.UpdateOrgUserCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.OrgId cmd.UserId = c.ParamsInt64(":userId") return hs.updateOrgUserHelper(c.Req.Context(), cmd) } // PATCH /api/orgs/:orgId/users/:userId -func (hs *HTTPServer) UpdateOrgUser(c *models.ReqContext, cmd models.UpdateOrgUserCommand) response.Response { +func (hs *HTTPServer) UpdateOrgUser(c *models.ReqContext) response.Response { + cmd := models.UpdateOrgUserCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.ParamsInt64(":orgId") cmd.UserId = c.ParamsInt64(":userId") return hs.updateOrgUserHelper(c.Req.Context(), cmd) diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index fb506bdfaab..932a9a23e6c 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -57,7 +57,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { loggedInUserScenario(t, "When calling GET on", "api/org/users/search", func(sc *scenarioContext) { setUpGetOrgUsersDB(t, sqlStore) - sc.handlerFuncCtx = hs.SearchOrgUsersWithPaging + sc.handlerFunc = hs.SearchOrgUsersWithPaging sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() require.Equal(t, http.StatusOK, sc.resp.Code) @@ -75,7 +75,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) { loggedInUserScenario(t, "When calling GET with page and limit query parameters on", "api/org/users/search", func(sc *scenarioContext) { setUpGetOrgUsersDB(t, sqlStore) - sc.handlerFuncCtx = hs.SearchOrgUsersWithPaging + sc.handlerFunc = hs.SearchOrgUsersWithPaging sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "2", "page": "2"}).exec() require.Equal(t, http.StatusOK, sc.resp.Code) diff --git a/pkg/api/password.go b/pkg/api/password.go index 6d529f6df88..04219b628ee 100644 --- a/pkg/api/password.go +++ b/pkg/api/password.go @@ -2,6 +2,7 @@ package api import ( "errors" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -9,9 +10,14 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" ) -func SendResetPasswordEmail(c *models.ReqContext, form dtos.SendResetPasswordEmailForm) response.Response { +func SendResetPasswordEmail(c *models.ReqContext) response.Response { + form := dtos.SendResetPasswordEmailForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if setting.LDAPEnabled || setting.AuthProxyEnabled { return response.Error(401, "Not allowed to reset password when LDAP or Auth Proxy is enabled", nil) } @@ -34,7 +40,11 @@ func SendResetPasswordEmail(c *models.ReqContext, form dtos.SendResetPasswordEma return response.Success("Email sent") } -func ResetPassword(c *models.ReqContext, form dtos.ResetUserPasswordForm) response.Response { +func ResetPassword(c *models.ReqContext) response.Response { + form := dtos.ResetUserPasswordForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } query := models.ValidateResetPasswordCodeQuery{Code: form.Code} if err := bus.Dispatch(&query); err != nil { diff --git a/pkg/api/playlist.go b/pkg/api/playlist.go index 31b37b5fcd2..9f5577739dc 100644 --- a/pkg/api/playlist.go +++ b/pkg/api/playlist.go @@ -2,10 +2,12 @@ package api import ( "context" + "net/http" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/web" ) func ValidateOrgPlaylist(c *models.ReqContext) { @@ -138,7 +140,11 @@ func DeletePlaylist(c *models.ReqContext) response.Response { return response.JSON(200, "") } -func CreatePlaylist(c *models.ReqContext, cmd models.CreatePlaylistCommand) response.Response { +func CreatePlaylist(c *models.ReqContext) response.Response { + cmd := models.CreatePlaylistCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.OrgId if err := bus.DispatchCtx(c.Req.Context(), &cmd); err != nil { @@ -148,7 +154,11 @@ func CreatePlaylist(c *models.ReqContext, cmd models.CreatePlaylistCommand) resp return response.JSON(200, cmd.Result) } -func UpdatePlaylist(c *models.ReqContext, cmd models.UpdatePlaylistCommand) response.Response { +func UpdatePlaylist(c *models.ReqContext) response.Response { + cmd := models.UpdatePlaylistCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.OrgId cmd.Id = c.ParamsInt64(":id") diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index aa889a4bd8f..859d61b821d 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -149,7 +149,11 @@ func (hs *HTTPServer) GetPluginSettingByID(c *models.ReqContext) response.Respon return response.JSON(200, dto) } -func (hs *HTTPServer) UpdatePluginSetting(c *models.ReqContext, cmd models.UpdatePluginSettingCmd) response.Response { +func (hs *HTTPServer) UpdatePluginSetting(c *models.ReqContext) response.Response { + cmd := models.UpdatePluginSettingCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } pluginID := web.Params(c.Req)[":pluginId"] if _, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID); !exists { @@ -208,7 +212,11 @@ func (hs *HTTPServer) GetPluginMarkdown(c *models.ReqContext) response.Response return resp } -func (hs *HTTPServer) ImportDashboard(c *models.ReqContext, apiCmd dtos.ImportDashboardCommand) response.Response { +func (hs *HTTPServer) ImportDashboard(c *models.ReqContext) response.Response { + apiCmd := dtos.ImportDashboardCommand{} + if err := web.Bind(c.Req, &apiCmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } var err error if apiCmd.PluginId == "" && apiCmd.Dashboard == nil { return response.Error(422, "Dashboard must be set", nil) @@ -387,7 +395,11 @@ func (hs *HTTPServer) GetPluginErrorsList(_ *models.ReqContext) response.Respons return response.JSON(200, hs.pluginErrorResolver.PluginErrors()) } -func (hs *HTTPServer) InstallPlugin(c *models.ReqContext, dto dtos.InstallPluginCommand) response.Response { +func (hs *HTTPServer) InstallPlugin(c *models.ReqContext) response.Response { + dto := dtos.InstallPluginCommand{} + if err := web.Bind(c.Req, &dto); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } pluginID := web.Params(c.Req)[":pluginId"] err := hs.pluginStore.Add(c.Req.Context(), pluginID, dto.Version, plugins.AddOpts{}) diff --git a/pkg/api/preferences.go b/pkg/api/preferences.go index bbea5f3ed89..bbe7908df0d 100644 --- a/pkg/api/preferences.go +++ b/pkg/api/preferences.go @@ -2,11 +2,13 @@ package api import ( "context" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/web" ) const ( @@ -16,7 +18,11 @@ const ( ) // POST /api/preferences/set-home-dash -func SetHomeDashboard(c *models.ReqContext, cmd models.SavePreferencesCommand) response.Response { +func SetHomeDashboard(c *models.ReqContext) response.Response { + cmd := models.SavePreferencesCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.UserId = c.UserId cmd.OrgId = c.OrgId @@ -50,7 +56,11 @@ func (hs *HTTPServer) getPreferencesFor(ctx context.Context, orgID, userID, team } // PUT /api/user/preferences -func (hs *HTTPServer) UpdateUserPreferences(c *models.ReqContext, dtoCmd dtos.UpdatePrefsCmd) response.Response { +func (hs *HTTPServer) UpdateUserPreferences(c *models.ReqContext) response.Response { + dtoCmd := dtos.UpdatePrefsCmd{} + if err := web.Bind(c.Req, &dtoCmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } return hs.updatePreferencesFor(c.Req.Context(), c.OrgId, c.UserId, 0, &dtoCmd) } @@ -81,6 +91,10 @@ func (hs *HTTPServer) GetOrgPreferences(c *models.ReqContext) response.Response } // PUT /api/org/preferences -func (hs *HTTPServer) UpdateOrgPreferences(c *models.ReqContext, dtoCmd dtos.UpdatePrefsCmd) response.Response { +func (hs *HTTPServer) UpdateOrgPreferences(c *models.ReqContext) response.Response { + dtoCmd := dtos.UpdatePrefsCmd{} + if err := web.Bind(c.Req, &dtoCmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } return hs.updatePreferencesFor(c.Req.Context(), c.OrgId, 0, 0, &dtoCmd) } diff --git a/pkg/api/quota.go b/pkg/api/quota.go index 8d4297e0f6f..b9aa899ad38 100644 --- a/pkg/api/quota.go +++ b/pkg/api/quota.go @@ -1,6 +1,8 @@ package api import ( + "net/http" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" @@ -29,7 +31,11 @@ func (hs *HTTPServer) getOrgQuotasHelper(c *models.ReqContext, orgID int64) resp return response.JSON(200, query.Result) } -func (hs *HTTPServer) UpdateOrgQuota(c *models.ReqContext, cmd models.UpdateOrgQuotaCmd) response.Response { +func (hs *HTTPServer) UpdateOrgQuota(c *models.ReqContext) response.Response { + cmd := models.UpdateOrgQuotaCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if !hs.Cfg.Quota.Enabled { return response.Error(404, "Quotas not enabled", nil) } @@ -59,7 +65,11 @@ func GetUserQuotas(c *models.ReqContext) response.Response { return response.JSON(200, query.Result) } -func UpdateUserQuota(c *models.ReqContext, cmd models.UpdateUserQuotaCmd) response.Response { +func UpdateUserQuota(c *models.ReqContext) response.Response { + cmd := models.UpdateUserQuotaCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if !setting.Quota.Enabled { return response.Error(404, "Quotas not enabled", nil) } diff --git a/pkg/api/routing/routing.go b/pkg/api/routing/routing.go index e64ef59c9ce..204ede8bf56 100644 --- a/pkg/api/routing/routing.go +++ b/pkg/api/routing/routing.go @@ -12,16 +12,10 @@ var ( } ) -func Wrap(action interface{}) web.Handler { +func Wrap(handler func(c *models.ReqContext) response.Response) web.Handler { return func(c *models.ReqContext) { - var res response.Response - val, err := c.Invoke(action) - if err == nil && val != nil && len(val) > 0 { - res = val[0].Interface().(response.Response) - } else { - res = ServerError(err) + if res := handler(c); res != nil { + res.WriteTo(c) } - - res.WriteTo(c) } } diff --git a/pkg/api/short_url.go b/pkg/api/short_url.go index d077d5f0796..180aeedfd73 100644 --- a/pkg/api/short_url.go +++ b/pkg/api/short_url.go @@ -3,6 +3,7 @@ package api import ( "errors" "fmt" + "net/http" "path" "strings" @@ -15,7 +16,11 @@ import ( ) // createShortURL handles requests to create short URLs. -func (hs *HTTPServer) createShortURL(c *models.ReqContext, cmd dtos.CreateShortURLCmd) response.Response { +func (hs *HTTPServer) createShortURL(c *models.ReqContext) response.Response { + cmd := dtos.CreateShortURLCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } hs.log.Debug("Received request to create short URL", "path", cmd.Path) cmd.Path = strings.TrimSpace(cmd.Path) diff --git a/pkg/api/short_url_test.go b/pkg/api/short_url_test.go index c42ad60257e..ba95fb2c4b9 100644 --- a/pkg/api/short_url_test.go +++ b/pkg/api/short_url_test.go @@ -64,10 +64,11 @@ func createShortURLScenario(t *testing.T, desc string, url string, routePattern sc := setupScenarioContext(t, url) sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID} - return hs.createShortURL(c, cmd) + return hs.createShortURL(c) }) sc.m.Post(routePattern, sc.defaultHandler) diff --git a/pkg/api/signup.go b/pkg/api/signup.go index bbeab002ccb..c3777c7464d 100644 --- a/pkg/api/signup.go +++ b/pkg/api/signup.go @@ -3,6 +3,7 @@ package api import ( "context" "errors" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -12,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" ) // GET /api/user/signup/options @@ -23,7 +25,11 @@ func GetSignUpOptions(c *models.ReqContext) response.Response { } // POST /api/user/signup -func SignUp(c *models.ReqContext, form dtos.SignUpForm) response.Response { +func SignUp(c *models.ReqContext) response.Response { + form := dtos.SignUpForm{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if !setting.AllowUserSignUp { return response.Error(401, "User signup is disabled", nil) } @@ -61,7 +67,11 @@ func SignUp(c *models.ReqContext, form dtos.SignUpForm) response.Response { return response.JSON(200, util.DynMap{"status": "SignUpCreated"}) } -func (hs *HTTPServer) SignUpStep2(c *models.ReqContext, form dtos.SignUpStep2Form) response.Response { +func (hs *HTTPServer) SignUpStep2(c *models.ReqContext) response.Response { + form := dtos.SignUpStep2Form{} + if err := web.Bind(c.Req, &form); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if !setting.AllowUserSignUp { return response.Error(401, "User signup is disabled", nil) } diff --git a/pkg/api/team.go b/pkg/api/team.go index 4eb4e18d10c..88c918a158e 100644 --- a/pkg/api/team.go +++ b/pkg/api/team.go @@ -2,6 +2,7 @@ package api import ( "errors" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -10,10 +11,15 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/teamguardian" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" ) // POST /api/teams -func (hs *HTTPServer) CreateTeam(c *models.ReqContext, cmd models.CreateTeamCommand) response.Response { +func (hs *HTTPServer) CreateTeam(c *models.ReqContext) response.Response { + cmd := models.CreateTeamCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if c.OrgRole == models.ROLE_VIEWER { return response.Error(403, "Not allowed to create team.", nil) } @@ -47,7 +53,11 @@ func (hs *HTTPServer) CreateTeam(c *models.ReqContext, cmd models.CreateTeamComm } // PUT /api/teams/:teamId -func (hs *HTTPServer) UpdateTeam(c *models.ReqContext, cmd models.UpdateTeamCommand) response.Response { +func (hs *HTTPServer) UpdateTeam(c *models.ReqContext) response.Response { + cmd := models.UpdateTeamCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.OrgId cmd.Id = c.ParamsInt64(":teamId") @@ -159,7 +169,11 @@ func (hs *HTTPServer) GetTeamPreferences(c *models.ReqContext) response.Response } // PUT /api/teams/:teamId/preferences -func (hs *HTTPServer) UpdateTeamPreferences(c *models.ReqContext, dtoCmd dtos.UpdatePrefsCmd) response.Response { +func (hs *HTTPServer) UpdateTeamPreferences(c *models.ReqContext) response.Response { + dtoCmd := dtos.UpdatePrefsCmd{} + if err := web.Bind(c.Req, &dtoCmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } teamId := c.ParamsInt64(":teamId") orgId := c.OrgId diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go index 01cd3e91e8c..28525cf49c2 100644 --- a/pkg/api/team_members.go +++ b/pkg/api/team_members.go @@ -2,6 +2,7 @@ package api import ( "errors" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -10,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/teamguardian" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" ) // GET /api/teams/:teamId/members @@ -41,7 +43,11 @@ func (hs *HTTPServer) GetTeamMembers(c *models.ReqContext) response.Response { } // POST /api/teams/:teamId/members -func (hs *HTTPServer) AddTeamMember(c *models.ReqContext, cmd models.AddTeamMemberCommand) response.Response { +func (hs *HTTPServer) AddTeamMember(c *models.ReqContext) response.Response { + cmd := models.AddTeamMemberCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.OrgId = c.OrgId cmd.TeamId = c.ParamsInt64(":teamId") @@ -68,7 +74,11 @@ func (hs *HTTPServer) AddTeamMember(c *models.ReqContext, cmd models.AddTeamMemb } // PUT /:teamId/members/:userId -func (hs *HTTPServer) UpdateTeamMember(c *models.ReqContext, cmd models.UpdateTeamMemberCommand) response.Response { +func (hs *HTTPServer) UpdateTeamMember(c *models.ReqContext) response.Response { + cmd := models.UpdateTeamMemberCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } teamId := c.ParamsInt64(":teamId") orgId := c.OrgId diff --git a/pkg/api/team_test.go b/pkg/api/team_test.go index 0ed629925cb..3a800157409 100644 --- a/pkg/api/team_test.go +++ b/pkg/api/team_test.go @@ -130,8 +130,8 @@ func TestTeamAPIEndpoint(t *testing.T) { Logger: stub, } c.OrgRole = models.ROLE_EDITOR - cmd := models.CreateTeamCommand{Name: teamName} - hs.CreateTeam(c, cmd) + c.Req.Body = mockRequestBody(models.CreateTeamCommand{Name: teamName}) + hs.CreateTeam(c) assert.Equal(t, createTeamCalled, 1) assert.Equal(t, addTeamMemberCalled, 0) assert.True(t, stub.warnCalled) @@ -146,9 +146,9 @@ func TestTeamAPIEndpoint(t *testing.T) { Logger: stub, } c.OrgRole = models.ROLE_EDITOR - cmd := models.CreateTeamCommand{Name: teamName} + c.Req.Body = mockRequestBody(models.CreateTeamCommand{Name: teamName}) createTeamCalled, addTeamMemberCalled = 0, 0 - hs.CreateTeam(c, cmd) + hs.CreateTeam(c) assert.Equal(t, createTeamCalled, 1) assert.Equal(t, addTeamMemberCalled, 1) assert.False(t, stub.warnCalled) diff --git a/pkg/api/user.go b/pkg/api/user.go index 9f6341b23a8..242d7dff02c 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -3,6 +3,7 @@ package api import ( "context" "errors" + "net/http" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -10,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" ) // GET /api/user (current authenticated user) @@ -70,7 +72,11 @@ func GetUserByLoginOrEmail(c *models.ReqContext) response.Response { } // POST /api/user -func UpdateSignedInUser(c *models.ReqContext, cmd models.UpdateUserCommand) response.Response { +func UpdateSignedInUser(c *models.ReqContext) response.Response { + cmd := models.UpdateUserCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if setting.AuthProxyEnabled { if setting.AuthProxyHeaderProperty == "email" && cmd.Email != c.Email { return response.Error(400, "Not allowed to change email when auth proxy is using email property", nil) @@ -84,7 +90,11 @@ func UpdateSignedInUser(c *models.ReqContext, cmd models.UpdateUserCommand) resp } // POST /api/users/:id -func UpdateUser(c *models.ReqContext, cmd models.UpdateUserCommand) response.Response { +func UpdateUser(c *models.ReqContext) response.Response { + cmd := models.UpdateUserCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } cmd.UserId = c.ParamsInt64(":id") return handleUpdateUser(c.Req.Context(), cmd) } @@ -217,7 +227,11 @@ func (hs *HTTPServer) ChangeActiveOrgAndRedirectToHome(c *models.ReqContext) { c.Redirect(hs.Cfg.AppSubURL + "/") } -func ChangeUserPassword(c *models.ReqContext, cmd models.ChangeUserPasswordCommand) response.Response { +func ChangeUserPassword(c *models.ReqContext) response.Response { + cmd := models.ChangeUserPasswordCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } if setting.LDAPEnabled || setting.AuthProxyEnabled { return response.Error(400, "Not allowed to change password when LDAP or Auth Proxy is enabled", nil) } diff --git a/pkg/api/user_token.go b/pkg/api/user_token.go index 1b813d5866a..5f15a0f7fac 100644 --- a/pkg/api/user_token.go +++ b/pkg/api/user_token.go @@ -3,6 +3,7 @@ package api import ( "context" "errors" + "net/http" "time" "github.com/grafana/grafana/pkg/api/dtos" @@ -10,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/web" "github.com/ua-parser/uap-go/uaparser" ) @@ -19,7 +21,11 @@ func (hs *HTTPServer) GetUserAuthTokens(c *models.ReqContext) response.Response } // POST /api/user/revoke-auth-token -func (hs *HTTPServer) RevokeUserAuthToken(c *models.ReqContext, cmd models.RevokeAuthTokenCmd) response.Response { +func (hs *HTTPServer) RevokeUserAuthToken(c *models.ReqContext) response.Response { + cmd := models.RevokeAuthTokenCmd{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } return hs.revokeUserAuthTokenInternal(c, c.UserId, cmd) } diff --git a/pkg/api/user_token_test.go b/pkg/api/user_token_test.go index 2a114b7cc74..815ba8b59f3 100644 --- a/pkg/api/user_token_test.go +++ b/pkg/api/user_token_test.go @@ -184,12 +184,13 @@ func revokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePat sc := setupScenarioContext(t, url) sc.userAuthTokenService = fakeAuthTokenService sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { + c.Req.Body = mockRequestBody(cmd) sc.context = c sc.context.UserId = userId sc.context.OrgId = testOrgID sc.context.OrgRole = models.ROLE_ADMIN - return hs.RevokeUserAuthToken(c, cmd) + return hs.RevokeUserAuthToken(c) }) sc.m.Post(routePattern, sc.defaultHandler) diff --git a/pkg/services/libraryelements/api.go b/pkg/services/libraryelements/api.go index f88e8c51391..ea9ac0994a2 100644 --- a/pkg/services/libraryelements/api.go +++ b/pkg/services/libraryelements/api.go @@ -2,8 +2,8 @@ package libraryelements import ( "errors" + "net/http" - "github.com/go-macaron/binding" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/middleware" @@ -13,18 +13,23 @@ import ( func (l *LibraryElementService) registerAPIEndpoints() { l.RouteRegister.Group("/api/library-elements", func(entities routing.RouteRegister) { - entities.Post("/", middleware.ReqSignedIn, binding.Bind(CreateLibraryElementCommand{}), routing.Wrap(l.createHandler)) + entities.Post("/", middleware.ReqSignedIn, routing.Wrap(l.createHandler)) entities.Delete("/:uid", middleware.ReqSignedIn, routing.Wrap(l.deleteHandler)) entities.Get("/", middleware.ReqSignedIn, routing.Wrap(l.getAllHandler)) entities.Get("/:uid", middleware.ReqSignedIn, routing.Wrap(l.getHandler)) entities.Get("/:uid/connections/", middleware.ReqSignedIn, routing.Wrap(l.getConnectionsHandler)) entities.Get("/name/:name", middleware.ReqSignedIn, routing.Wrap(l.getByNameHandler)) - entities.Patch("/:uid", middleware.ReqSignedIn, binding.Bind(patchLibraryElementCommand{}), routing.Wrap(l.patchHandler)) + entities.Patch("/:uid", middleware.ReqSignedIn, routing.Wrap(l.patchHandler)) }) } // createHandler handles POST /api/library-elements. -func (l *LibraryElementService) createHandler(c *models.ReqContext, cmd CreateLibraryElementCommand) response.Response { +func (l *LibraryElementService) createHandler(c *models.ReqContext) response.Response { + cmd := CreateLibraryElementCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } + element, err := l.createLibraryElement(c.Req.Context(), c.SignedInUser, cmd) if err != nil { return toLibraryElementError(err, "Failed to create library element") @@ -77,7 +82,12 @@ func (l *LibraryElementService) getAllHandler(c *models.ReqContext) response.Res } // patchHandler handles PATCH /api/library-elements/:uid -func (l *LibraryElementService) patchHandler(c *models.ReqContext, cmd patchLibraryElementCommand) response.Response { +func (l *LibraryElementService) patchHandler(c *models.ReqContext) response.Response { + cmd := patchLibraryElementCommand{} + if err := web.Bind(c.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } + element, err := l.patchLibraryElement(c.Req.Context(), c.SignedInUser, cmd, web.Params(c.Req)[":uid"]) if err != nil { return toLibraryElementError(err, "Failed to update library element") diff --git a/pkg/services/libraryelements/libraryelements_create_test.go b/pkg/services/libraryelements/libraryelements_create_test.go index 01f72f35963..fdcbd730e1d 100644 --- a/pkg/services/libraryelements/libraryelements_create_test.go +++ b/pkg/services/libraryelements/libraryelements_create_test.go @@ -14,7 +14,8 @@ func TestCreateLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to create a library panel that already exists, it should fail", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) }) @@ -64,7 +65,8 @@ func TestCreateLibraryElement(t *testing.T) { func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Nonexistent UID") command.UID = util.GenerateShortUID() - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) var expected = libraryElementResult{ Result: libraryElement{ @@ -110,7 +112,8 @@ func TestCreateLibraryElement(t *testing.T) { func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Existing UID") command.UID = sc.initialResult.Result.UID - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) }) @@ -118,7 +121,8 @@ func TestCreateLibraryElement(t *testing.T) { func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Invalid UID") command.UID = "Testing an invalid UID" - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) }) @@ -126,14 +130,16 @@ func TestCreateLibraryElement(t *testing.T) { func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Invalid UID") command.UID = "j6T00KRZzj6T00KRZzj6T00KRZzj6T00KRZzj6T00K" - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) }) testScenario(t, "When an admin tries to create a library panel where name and panel title differ, it should not update panel title", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(1, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) var expected = libraryElementResult{ Result: libraryElement{ diff --git a/pkg/services/libraryelements/libraryelements_get_all_test.go b/pkg/services/libraryelements/libraryelements_get_all_test.go index 6f4d80c94f2..e927c6fc976 100644 --- a/pkg/services/libraryelements/libraryelements_get_all_test.go +++ b/pkg/services/libraryelements/libraryelements_get_all_test.go @@ -37,7 +37,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all panel elements and both panels and variables exist, it should only return panels", func(t *testing.T, sc scenarioContext) { command := getCreateVariableCommand(sc.folder.Id, "query0") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -102,7 +103,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all variable elements and both panels and variables exist, it should only return panels", func(t *testing.T, sc scenarioContext) { command := getCreateVariableCommand(sc.folder.Id, "query0") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -166,7 +168,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist, it should succeed", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) resp = sc.service.getAllHandler(sc.reqContext) @@ -262,7 +265,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and sort desc is set, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -369,7 +373,8 @@ func TestGetAllLibraryElements(t *testing.T) { "description": "Gauge description" } `)) - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) command = getCreateCommandWithModel(sc.folder.Id, "BarGauge - Library Panel", models.PanelElement, []byte(` @@ -381,7 +386,8 @@ func TestGetAllLibraryElements(t *testing.T) { "description": "BarGauge description" } `)) - resp = sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp = sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -488,7 +494,8 @@ func TestGetAllLibraryElements(t *testing.T) { "description": "Gauge description" } `)) - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -517,7 +524,8 @@ func TestGetAllLibraryElements(t *testing.T) { func(t *testing.T, sc scenarioContext) { newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) command := getCreatePanelCommand(newFolder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) folderFilter := strconv.FormatInt(newFolder.Id, 10) @@ -583,7 +591,8 @@ func TestGetAllLibraryElements(t *testing.T) { func(t *testing.T, sc scenarioContext) { newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) command := getCreatePanelCommand(newFolder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) folderFilter := "2020,2021" @@ -612,7 +621,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and folderFilter is set to General folder, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) folderFilter := "0" @@ -712,7 +722,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and excludeUID is set, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -776,7 +787,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -840,7 +852,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 2, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -913,7 +926,8 @@ func TestGetAllLibraryElements(t *testing.T) { "description": "Some other d e s c r i p t i o n" } `)) - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -987,7 +1001,8 @@ func TestGetAllLibraryElements(t *testing.T) { "description": "A Library Panel" } `)) - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -1086,7 +1101,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 1 and searchString is panel2, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -1152,7 +1168,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 3 and searchString is panel, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() @@ -1182,7 +1199,8 @@ func TestGetAllLibraryElements(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get all library panels and two exist and perPage is 1 and page is 3 and searchString does not exist, it should succeed and the result should be correct", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel2") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) err := sc.reqContext.Req.ParseForm() diff --git a/pkg/services/libraryelements/libraryelements_patch_test.go b/pkg/services/libraryelements/libraryelements_patch_test.go index 1921c1fb33a..a20f7d499b6 100644 --- a/pkg/services/libraryelements/libraryelements_patch_test.go +++ b/pkg/services/libraryelements/libraryelements_patch_test.go @@ -14,9 +14,10 @@ import ( func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel that does not exist, it should fail", func(t *testing.T, sc scenarioContext) { - cmd := patchLibraryElementCommand{Kind: int64(models.PanelElement)} + cmd := patchLibraryElementCommand{Kind: int64(models.PanelElement), Version: 1} sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": "unknown"}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) require.Equal(t, 404, resp.Status()) }) @@ -39,7 +40,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) var result = validateAndUnMarshalResponse(t, resp) var expected = libraryElementResult{ @@ -91,7 +93,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) var result = validateAndUnMarshalResponse(t, resp) sc.initialResult.Result.FolderID = newFolder.Id @@ -113,7 +116,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) sc.initialResult.Result.Name = "New Name" sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName @@ -135,7 +139,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) sc.initialResult.Result.UID = cmd.UID sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName @@ -157,7 +162,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) }) @@ -170,7 +176,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) }) @@ -178,7 +185,8 @@ func TestPatchLibraryElement(t *testing.T) { func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Existing UID") command.UID = util.GenerateShortUID() - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) cmd := patchLibraryElementCommand{ FolderID: -1, @@ -187,7 +195,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp = sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp = sc.service.patchHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) }) @@ -200,7 +209,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) sc.initialResult.Result.Type = "graph" sc.initialResult.Result.Description = "New description" @@ -228,7 +238,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) sc.initialResult.Result.Type = "text" sc.initialResult.Result.Description = "New description" @@ -254,7 +265,8 @@ func TestPatchLibraryElement(t *testing.T) { Version: 1, } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) sc.initialResult.Result.Type = "graph" sc.initialResult.Result.Description = "A description" @@ -276,7 +288,8 @@ func TestPatchLibraryElement(t *testing.T) { cmd := patchLibraryElementCommand{FolderID: -1, Version: 1, Kind: int64(models.PanelElement)} sc.reqContext.UserId = 2 sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) sc.initialResult.Result.Meta.UpdatedBy.ID = int64(2) sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName @@ -291,7 +304,8 @@ func TestPatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel with a name that already exists, it should fail", func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Another Panel") - resp := sc.service.createHandler(sc.reqContext, command) + sc.ctx.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) cmd := patchLibraryElementCommand{ Name: "Text - Library Panel", @@ -299,7 +313,8 @@ func TestPatchLibraryElement(t *testing.T) { Kind: int64(models.PanelElement), } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) - resp = sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp = sc.service.patchHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) }) @@ -307,7 +322,8 @@ func TestPatchLibraryElement(t *testing.T) { func(t *testing.T, sc scenarioContext) { newFolder := createFolderWithACL(t, sc.sqlStore, "NewFolder", sc.user, []folderACLItem{}) command := getCreatePanelCommand(newFolder.Id, "Text - Library Panel") - resp := sc.service.createHandler(sc.reqContext, command) + sc.ctx.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) var result = validateAndUnMarshalResponse(t, resp) cmd := patchLibraryElementCommand{ FolderID: 1, @@ -315,7 +331,8 @@ func TestPatchLibraryElement(t *testing.T) { Kind: int64(models.PanelElement), } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) - resp = sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp = sc.service.patchHandler(sc.reqContext) require.Equal(t, 400, resp.Status()) }) @@ -328,7 +345,8 @@ func TestPatchLibraryElement(t *testing.T) { } sc.reqContext.OrgId = 2 sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) require.Equal(t, 404, resp.Status()) }) @@ -340,9 +358,11 @@ func TestPatchLibraryElement(t *testing.T) { Kind: int64(models.PanelElement), } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) - resp = sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp = sc.service.patchHandler(sc.reqContext) require.Equal(t, 412, resp.Status()) }) @@ -354,7 +374,8 @@ func TestPatchLibraryElement(t *testing.T) { Kind: int64(models.VariableElement), } sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID}) - resp := sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp := sc.service.patchHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) var result = validateAndUnMarshalResponse(t, resp) sc.initialResult.Result.Type = "text" diff --git a/pkg/services/libraryelements/libraryelements_permissions_test.go b/pkg/services/libraryelements/libraryelements_permissions_test.go index cea74b5e7de..ce959d5df9a 100644 --- a/pkg/services/libraryelements/libraryelements_permissions_test.go +++ b/pkg/services/libraryelements/libraryelements_permissions_test.go @@ -71,7 +71,8 @@ func TestLibraryElementPermissions(t *testing.T) { sc.reqContext.SignedInUser.OrgRole = testCase.role command := getCreatePanelCommand(folder.Id, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, testCase.status, resp.Status()) }) @@ -79,14 +80,16 @@ func TestLibraryElementPermissions(t *testing.T) { func(t *testing.T, sc scenarioContext) { fromFolder := createFolderWithACL(t, sc.sqlStore, "Everyone", sc.user, everyonePermissions) command := getCreatePanelCommand(fromFolder.Id, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items) sc.reqContext.SignedInUser.OrgRole = testCase.role cmd := patchLibraryElementCommand{FolderID: toFolder.Id, Version: 1, Kind: int64(models.PanelElement)} sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) - resp = sc.service.patchHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp = sc.service.patchHandler(sc.reqContext) require.Equal(t, testCase.status, resp.Status()) }) @@ -94,14 +97,16 @@ func TestLibraryElementPermissions(t *testing.T) { func(t *testing.T, sc scenarioContext) { fromFolder := createFolderWithACL(t, sc.sqlStore, "Everyone", sc.user, testCase.items) command := getCreatePanelCommand(fromFolder.Id, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) toFolder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions) sc.reqContext.SignedInUser.OrgRole = testCase.role cmd := patchLibraryElementCommand{FolderID: toFolder.Id, Version: 1, Kind: int64(models.PanelElement)} sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) - resp = sc.service.patchHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp = sc.service.patchHandler(sc.reqContext) require.Equal(t, testCase.status, resp.Status()) }) @@ -109,7 +114,8 @@ func TestLibraryElementPermissions(t *testing.T) { func(t *testing.T, sc scenarioContext) { folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, testCase.items) cmd := getCreatePanelCommand(folder.Id, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) sc.reqContext.SignedInUser.OrgRole = testCase.role @@ -134,7 +140,8 @@ func TestLibraryElementPermissions(t *testing.T) { sc.reqContext.SignedInUser.OrgRole = testCase.role command := getCreatePanelCommand(0, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, testCase.status, resp.Status()) }) @@ -142,13 +149,15 @@ func TestLibraryElementPermissions(t *testing.T) { func(t *testing.T, sc scenarioContext) { folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions) command := getCreatePanelCommand(folder.Id, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) sc.reqContext.SignedInUser.OrgRole = testCase.role cmd := patchLibraryElementCommand{FolderID: 0, Version: 1, Kind: int64(models.PanelElement)} sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) - resp = sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp = sc.service.patchHandler(sc.reqContext) require.Equal(t, testCase.status, resp.Status()) }) @@ -156,20 +165,23 @@ func TestLibraryElementPermissions(t *testing.T) { func(t *testing.T, sc scenarioContext) { folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions) command := getCreatePanelCommand(0, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) sc.reqContext.SignedInUser.OrgRole = testCase.role cmd := patchLibraryElementCommand{FolderID: folder.Id, Version: 1, Kind: int64(models.PanelElement)} sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) - resp = sc.service.patchHandler(sc.reqContext, cmd) + sc.ctx.Req.Body = mockRequestBody(cmd) + resp = sc.service.patchHandler(sc.reqContext) require.Equal(t, testCase.status, resp.Status()) }) testScenario(t, fmt.Sprintf("When %s tries to delete a library panel in the General folder, it should return correct status", testCase.role), func(t *testing.T, sc scenarioContext) { cmd := getCreatePanelCommand(0, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) sc.reqContext.SignedInUser.OrgRole = testCase.role @@ -193,7 +205,8 @@ func TestLibraryElementPermissions(t *testing.T) { sc.reqContext.SignedInUser.OrgRole = testCase.role command := getCreatePanelCommand(-100, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 404, resp.Status()) }) @@ -201,13 +214,15 @@ func TestLibraryElementPermissions(t *testing.T) { func(t *testing.T, sc scenarioContext) { folder := createFolderWithACL(t, sc.sqlStore, "Folder", sc.user, everyonePermissions) command := getCreatePanelCommand(folder.Id, "Library Panel Name") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) sc.reqContext.SignedInUser.OrgRole = testCase.role cmd := patchLibraryElementCommand{FolderID: -100, Version: 1, Kind: int64(models.PanelElement)} sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": result.Result.UID}) - resp = sc.service.patchHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp = sc.service.patchHandler(sc.reqContext) require.Equal(t, 404, resp.Status()) }) } @@ -228,7 +243,8 @@ func TestLibraryElementPermissions(t *testing.T) { for i, folderCase := range folderCases { folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase) cmd := getCreatePanelCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i)) - resp := sc.service.createHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) result.Result.Meta.CreatedBy.Name = userInDbName result.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar @@ -250,7 +266,8 @@ func TestLibraryElementPermissions(t *testing.T) { testScenario(t, fmt.Sprintf("When %s tries to get a library panel from General folder, it should return correct response", testCase.role), func(t *testing.T, sc scenarioContext) { cmd := getCreatePanelCommand(0, "Library Panel in General Folder") - resp := sc.service.createHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) result.Result.Meta.CreatedBy.Name = userInDbName result.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar @@ -289,7 +306,8 @@ func TestLibraryElementPermissions(t *testing.T) { for i, folderCase := range folderCases { folder := createFolderWithACL(t, sc.sqlStore, fmt.Sprintf("Folder%v", i), sc.user, folderCase) cmd := getCreatePanelCommand(folder.Id, fmt.Sprintf("Library Panel in Folder%v", i)) - resp := sc.service.createHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) result.Result.Meta.CreatedBy.Name = userInDbName result.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar @@ -340,7 +358,8 @@ func TestLibraryElementPermissions(t *testing.T) { testScenario(t, fmt.Sprintf("When %s tries to get all library panels from General folder, it should return correct response", testCase.role), func(t *testing.T, sc scenarioContext) { cmd := getCreatePanelCommand(0, "Library Panel in General Folder") - resp := sc.service.createHandler(sc.reqContext, cmd) + sc.reqContext.Req.Body = mockRequestBody(cmd) + resp := sc.service.createHandler(sc.reqContext) result := validateAndUnMarshalResponse(t, resp) result.Result.Meta.CreatedBy.Name = userInDbName result.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 682ee206f30..132a1457f7f 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -1,8 +1,10 @@ package libraryelements import ( + "bytes" "context" "encoding/json" + "io" "net/http" "testing" "time" @@ -68,7 +70,8 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) { scenarioWithPanel(t, "When an admin tries to delete a folder that contains disconnected elements, it should delete all disconnected elements too", func(t *testing.T, sc scenarioContext) { command := getCreateVariableCommand(sc.folder.Id, "query0") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) resp = sc.service.getAllHandler(sc.reqContext) @@ -266,7 +269,8 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena testScenario(t, desc, func(t *testing.T, sc scenarioContext) { command := getCreatePanelCommand(sc.folder.Id, "Text - Library Panel") - resp := sc.service.createHandler(sc.reqContext, command) + sc.reqContext.Req.Body = mockRequestBody(command) + resp := sc.service.createHandler(sc.reqContext) sc.initialResult = validateAndUnMarshalResponse(t, resp) fn(t, sc) @@ -333,3 +337,8 @@ func getCompareOptions() []cmp.Option { }), } } + +func mockRequestBody(v interface{}) io.ReadCloser { + b, _ := json.Marshal(v) + return io.NopCloser(bytes.NewReader(b)) +} diff --git a/pkg/services/libraryelements/models.go b/pkg/services/libraryelements/models.go index b9f40aa52f6..a5ae10fac99 100644 --- a/pkg/services/libraryelements/models.go +++ b/pkg/services/libraryelements/models.go @@ -170,7 +170,7 @@ type CreateLibraryElementCommand struct { type patchLibraryElementCommand struct { FolderID int64 `json:"folderId" binding:"Default(-1)"` Name string `json:"name"` - Model json.RawMessage `json:"model"` + Model json.RawMessage `json:"model,omitempty"` Kind int64 `json:"kind" binding:"Required"` Version int64 `json:"version" binding:"Required"` UID string `json:"uid"` diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index b94940adeea..26d21f72776 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -893,7 +893,11 @@ func (g *GrafanaLive) ClientCount(orgID int64, channel string) (int, error) { return len(p.Presence), nil } -func (g *GrafanaLive) HandleHTTPPublish(ctx *models.ReqContext, cmd dtos.LivePublishCmd) response.Response { +func (g *GrafanaLive) HandleHTTPPublish(ctx *models.ReqContext) response.Response { + cmd := dtos.LivePublishCmd{} + if err := web.Bind(ctx.Req, &cmd); err != nil { + return response.Error(http.StatusBadRequest, "bad request data", err) + } addr, err := live.ParseChannel(cmd.Channel) if err != nil { return response.Error(http.StatusBadRequest, "invalid channel ID", nil)