From 6a19278f2337c6668b6edd9f970bcc24a55f53aa Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Wed, 28 Aug 2024 12:20:36 +0100 Subject: [PATCH] API Keys: Removal & Redirect of Create endpoint (#92144) * API keys: redirecting of create endpont * update naming and using admonition * fmt * Apply suggestions from code review Co-authored-by: Ieva --------- Co-authored-by: Ieva --- docs/sources/developers/http_api/auth.md | 22 +++++++- pkg/api/apikey.go | 66 +++--------------------- pkg/api/swagger_responses.go | 5 ++ public/api-merged.json | 35 +++---------- public/openapi3.json | 40 +++++--------- 5 files changed, 52 insertions(+), 116 deletions(-) diff --git a/docs/sources/developers/http_api/auth.md b/docs/sources/developers/http_api/auth.md index 14f3a930767..4d5ce4d8740 100644 --- a/docs/sources/developers/http_api/auth.md +++ b/docs/sources/developers/http_api/auth.md @@ -29,6 +29,11 @@ If you use Grafana v9.1 or newer, use service accounts instead of API keys. For ## List API keys +{{% admonition type="warning" %}} +This endpoint is deprecated. + +{{% /admonition %}} + `GET /api/auth/keys` **Required permissions** @@ -75,6 +80,13 @@ Content-Type: application/json ## Create API Key +{{% admonition type="warning" %}} +This endpoint has been made obsolete in Grafana 11.3.0. + +{{% /admonition %}} + +Endpoint is obsolete and has been moved to [Grafana service account API]({{< relref "./serviceaccount/" >}}). For more information, refer to [Migrate to Grafana service account API]({{< relref "../../administration/api-keys/#migrate-api-keys-to-grafana-service-accounts-using-the-api" >}}). + `POST /api/auth/keys` **Required permissions** @@ -114,14 +126,20 @@ Error statuses: **Example Response**: ```http -HTTP/1.1 200 +HTTP/1.1 301 Content-Type: application/json -{"name":"mykey","key":"eyJrIjoiWHZiSWd3NzdCYUZnNUtibE9obUpESmE3bzJYNDRIc0UiLCJuIjoibXlrZXkiLCJpZCI6MX1=","id":1} +"" ``` ## Delete API Key +{{% admonition type="warning" %}} + +### DEPRECATED + +{{% /admonition %}} + `DELETE /api/auth/keys/:id` **Required permissions** diff --git a/pkg/api/apikey.go b/pkg/api/apikey.go index fafaddecb68..baa4308252f 100644 --- a/pkg/api/apikey.go +++ b/pkg/api/apikey.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/services/apikey" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/web" @@ -114,59 +113,15 @@ func (hs *HTTPServer) DeleteAPIKey(c *contextmodel.ReqContext) response.Response // see: https://grafana.com/docs/grafana/next/administration/api-keys/#migrate-api-keys-to-grafana-service-accounts-using-the-api. // // Responses: -// 200: postAPIkeyResponse -// 400: badRequestError -// 401: unauthorisedError -// 403: forbiddenError -// 409: conflictError -// 500: internalServerError +// 301: statusMovedPermanently func (hs *HTTPServer) AddAPIKey(c *contextmodel.ReqContext) response.Response { - cmd := apikey.AddCommand{} - 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(http.StatusBadRequest, "Invalid role specified", nil) - } - if !c.SignedInUser.GetOrgRole().Includes(cmd.Role) { - return response.Error(http.StatusForbidden, "Cannot assign a role higher than user's role", nil) - } + // Set the Location header to the new URL + hs.log.Warn("Obsolete and Permanently moved API endpoint called", "path", c.Req.URL.Path) + c.Context.Resp.Header().Set("Location", "/api/serviceaccounts/tokens") - if hs.Cfg.ApiKeyMaxSecondsToLive != -1 { - if cmd.SecondsToLive == 0 { - return response.Error(http.StatusBadRequest, "Number of seconds before expiration should be set", nil) - } - if cmd.SecondsToLive > hs.Cfg.ApiKeyMaxSecondsToLive { - return response.Error(http.StatusBadRequest, "Number of seconds before expiration is greater than the global limit", nil) - } - } - - cmd.OrgID = c.SignedInUser.GetOrgID() - - newKeyInfo, err := apikeygen.New(cmd.OrgID, cmd.Name) - if err != nil { - return response.Error(http.StatusInternalServerError, "Generating API key failed", err) - } - - cmd.Key = newKeyInfo.HashedKey - key, err := hs.apiKeyService.AddAPIKey(c.Req.Context(), &cmd) - if err != nil { - if errors.Is(err, apikey.ErrInvalidExpiration) { - return response.Error(http.StatusBadRequest, err.Error(), nil) - } - if errors.Is(err, apikey.ErrDuplicate) { - return response.Error(http.StatusConflict, err.Error(), nil) - } - return response.Error(http.StatusInternalServerError, "Failed to add API Key", err) - } - - result := &dtos.NewApiKeyResult{ - ID: key.ID, - Name: key.Name, - Key: newKeyInfo.ClientSecret, - } - - return response.JSON(http.StatusOK, result) + // Respond with a 301 Moved Permanently status code + // the Location header is enough for clients to know where to go next. + return response.JSON(http.StatusMovedPermanently, nil) } // swagger:parameters getAPIkeys @@ -178,13 +133,6 @@ type GetAPIkeysParams struct { IncludeExpired bool `json:"includeExpired"` } -// swagger:parameters addAPIkey -type AddAPIkeyParams struct { - // in:body - // required:true - Body apikey.AddCommand -} - // swagger:parameters deleteAPIkey type DeleteAPIkeyParams struct { // in:path diff --git a/pkg/api/swagger_responses.go b/pkg/api/swagger_responses.go index 715aa1e6c38..5f422b90eff 100644 --- a/pkg/api/swagger_responses.go +++ b/pkg/api/swagger_responses.go @@ -82,6 +82,11 @@ type UnauthorizedError GenericError // swagger:response acceptedResponse type AcceptedResponse GenericError +// StatusMovedPermanently +// +// swagger:response statusMovedPermanently +type StatusMovedPermanentlyRedirect GenericError + // documentation for PublicError defined in errutil.Error // swagger:response publicErrorResponse diff --git a/public/api-merged.json b/public/api-merged.json index 50d08dee9ff..9fc0bee0c67 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -2225,34 +2225,9 @@ "summary": "Creates an API key.", "operationId": "addAPIkey", "deprecated": true, - "parameters": [ - { - "name": "Body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/AddAPIKeyCommand" - } - } - ], "responses": { - "200": { - "$ref": "#/responses/postAPIkeyResponse" - }, - "400": { - "$ref": "#/responses/badRequestError" - }, - "401": { - "$ref": "#/responses/unauthorisedError" - }, - "403": { - "$ref": "#/responses/forbiddenError" - }, - "409": { - "$ref": "#/responses/conflictError" - }, - "500": { - "$ref": "#/responses/internalServerError" + "301": { + "$ref": "#/responses/statusMovedPermanently" } } } @@ -24190,6 +24165,12 @@ "$ref": "#/definitions/SnapshotListResponseDTO" } }, + "statusMovedPermanently": { + "description": "StatusMovedPermanently", + "schema": { + "$ref": "#/definitions/ErrorResponseBody" + } + }, "unauthorisedError": { "description": "UnauthorizedError is returned when the request is not authenticated.", "schema": { diff --git a/public/openapi3.json b/public/openapi3.json index 3eccd8eda54..dba946b9470 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -1942,6 +1942,16 @@ }, "description": "(empty)" }, + "statusMovedPermanently": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponseBody" + } + } + }, + "description": "StatusMovedPermanently" + }, "unauthorisedError": { "content": { "application/json": { @@ -15304,35 +15314,9 @@ "deprecated": true, "description": "Will return details of the created API key.", "operationId": "addAPIkey", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AddAPIKeyCommand" - } - } - }, - "required": true, - "x-originalParamName": "Body" - }, "responses": { - "200": { - "$ref": "#/components/responses/postAPIkeyResponse" - }, - "400": { - "$ref": "#/components/responses/badRequestError" - }, - "401": { - "$ref": "#/components/responses/unauthorisedError" - }, - "403": { - "$ref": "#/components/responses/forbiddenError" - }, - "409": { - "$ref": "#/components/responses/conflictError" - }, - "500": { - "$ref": "#/components/responses/internalServerError" + "301": { + "$ref": "#/components/responses/statusMovedPermanently" } }, "summary": "Creates an API key.",