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/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 func GetAPIKeys(c *models.ReqContext) response.Response { query := models.GetApiKeysQuery{OrgId: c.OrgId, IncludeExpired: c.QueryBool("includeExpired")} if err := bus.Dispatch(c.Req.Context(), &query); err != nil { return response.Error(500, "Failed to list api keys", err) } result := make([]*models.ApiKeyDTO, len(query.Result)) for i, t := range query.Result { var expiration *time.Time = nil if t.Expires != nil { v := time.Unix(*t.Expires, 0) expiration = &v } result[i] = &models.ApiKeyDTO{ Id: t.Id, Name: t.Name, Role: t.Role, Expiration: expiration, } } return response.JSON(200, result) } // DeleteAPIKey deletes an API key func DeleteAPIKey(c *models.ReqContext) response.Response { id := c.ParamsInt64(":id") cmd := &models.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId} err := bus.Dispatch(c.Req.Context(), cmd) if err != nil { var status int if errors.Is(err, models.ErrApiKeyNotFound) { status = 404 } else { status = 500 } return response.Error(status, "Failed to delete API key", err) } return response.Success("API key deleted") } // AddAPIKey adds an API key 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) } if hs.Cfg.ApiKeyMaxSecondsToLive != -1 { if cmd.SecondsToLive == 0 { return response.Error(400, "Number of seconds before expiration should be set", nil) } if cmd.SecondsToLive > hs.Cfg.ApiKeyMaxSecondsToLive { return response.Error(400, "Number of seconds before expiration is greater than the global limit", nil) } } cmd.OrgId = c.OrgId var err error if hs.Cfg.FeatureToggles["service-accounts"] { //Every new API key must have an associated service account if cmd.CreateNewServiceAccount { //Create a new service account for the new API key serviceAccount, err := hs.SQLStore.CloneUserToServiceAccount(c.Req.Context(), c.SignedInUser) if err != nil { hs.log.Warn("Unable to clone user to service account", "err", err) return response.Error(500, "Unable to clone user to service account", err) } cmd.ServiceAccountId = serviceAccount.Id } else { //Link the new API key to an existing service account //Check if user and service account are in the same org query := models.GetUserByIdQuery{Id: cmd.ServiceAccountId} err = bus.Dispatch(c.Req.Context(), &query) if err != nil { hs.log.Warn("Unable to link new API key to existing service account", "err", err, "query", query) return response.Error(500, "Unable to link new API key to existing service account", err) } serviceAccountDetails := query.Result if serviceAccountDetails.OrgId != c.OrgId || serviceAccountDetails.OrgId != cmd.OrgId { hs.log.Warn("Target service is not in the same organisation as requesting user or api key", "err", err, "reqOrg", cmd.OrgId, "serviceAccId", serviceAccountDetails.OrgId, "userOrgId", c.OrgId) return response.Error(403, "Target service is not in the same organisation as requesting user or api key", err) } } } else { if cmd.CreateNewServiceAccount { return response.Error(400, "Service accounts disabled. Retry create api request without service account flag.", err) } } newKeyInfo, err := apikeygen.New(cmd.OrgId, cmd.Name) if err != nil { return response.Error(500, "Generating API key failed", err) } cmd.Key = newKeyInfo.HashedKey if err := bus.Dispatch(c.Req.Context(), &cmd); err != nil { if errors.Is(err, models.ErrInvalidApiKeyExpiration) { return response.Error(400, err.Error(), nil) } if errors.Is(err, models.ErrDuplicateApiKey) { return response.Error(409, err.Error(), nil) } return response.Error(500, "Failed to add API Key", err) } result := &dtos.NewApiKeyResult{ ID: cmd.Result.Id, Name: cmd.Result.Name, Key: newKeyInfo.ClientSecret, } return response.JSON(200, result) } // AddAPIKey adds an additional API key to a service account 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")) } if cmd.CreateNewServiceAccount { return response.Error(500, "Can't create service account while adding additional API key", nil) } return hs.AddAPIKey(c) }