Auth: Remove api key endpoints (#106019)
* remove api key endpoints * generate openapi specs * remove methods from mock service * remove ApiKeyDTO * generate openapi specs * remove apikey migration endpoints * remove unused function
This commit is contained in:
@@ -40,7 +40,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ssoutils"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/cloudmigration"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
@@ -396,14 +395,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
// orgs (admin routes)
|
||||
apiRoute.Get("/orgs/name/:name/", authorizeInOrg(ac.UseGlobalOrg, ac.EvalPermission(ac.ActionOrgsRead)), routing.Wrap(hs.GetOrgByName))
|
||||
|
||||
// auth api keys
|
||||
apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) {
|
||||
apikeyIDScope := ac.Scope("apikeys", "id", ac.Parameter(":id"))
|
||||
keysRoute.Get("/", authorize(ac.EvalPermission(ac.ActionAPIKeyRead)), routing.Wrap(hs.GetAPIKeys))
|
||||
keysRoute.Post("/", authorize(ac.EvalPermission(ac.ActionAPIKeyCreate)), quota(string(apikey.QuotaTargetSrv)), routing.Wrap(hs.AddAPIKey))
|
||||
keysRoute.Delete("/:id", authorize(ac.EvalPermission(ac.ActionAPIKeyDelete, apikeyIDScope)), routing.Wrap(hs.DeleteAPIKey))
|
||||
}, requestmeta.SetOwner(requestmeta.TeamAuth))
|
||||
|
||||
// Preferences
|
||||
apiRoute.Group("/preferences", func(prefRoute routing.RouteRegister) {
|
||||
prefRoute.Post("/set-home-dash", routing.Wrap(hs.SetHomeDashboard))
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
// swagger:route GET /auth/keys api_keys getAPIkeys
|
||||
//
|
||||
// Get auth keys.
|
||||
//
|
||||
// Will return auth keys.
|
||||
//
|
||||
// Deprecated: true.
|
||||
//
|
||||
// Deprecated. Please use GET /api/serviceaccounts and GET /api/serviceaccounts/{id}/tokens instead
|
||||
// see https://grafana.com/docs/grafana/next/administration/service-accounts/migrate-api-keys/.
|
||||
//
|
||||
// Responses:
|
||||
// 200: getAPIkeyResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) GetAPIKeys(c *contextmodel.ReqContext) response.Response {
|
||||
query := apikey.GetApiKeysQuery{OrgID: c.GetOrgID(), User: c.SignedInUser, IncludeExpired: c.QueryBool("includeExpired")}
|
||||
|
||||
keys, err := hs.apiKeyService.GetAPIKeys(c.Req.Context(), &query)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to list api keys", err)
|
||||
}
|
||||
|
||||
ids := map[string]bool{}
|
||||
result := make([]*dtos.ApiKeyDTO, len(keys))
|
||||
for i, t := range keys {
|
||||
ids[strconv.FormatInt(t.ID, 10)] = true
|
||||
var expiration *time.Time = nil
|
||||
if t.Expires != nil {
|
||||
v := time.Unix(*t.Expires, 0)
|
||||
expiration = &v
|
||||
}
|
||||
result[i] = &dtos.ApiKeyDTO{
|
||||
ID: t.ID,
|
||||
Name: t.Name,
|
||||
Role: t.Role,
|
||||
Expiration: expiration,
|
||||
LastUsedAt: t.LastUsedAt,
|
||||
}
|
||||
}
|
||||
|
||||
metadata := getMultiAccessControlMetadata(c, "apikeys:id", ids)
|
||||
if len(metadata) > 0 {
|
||||
for _, key := range result {
|
||||
key.AccessControl = metadata[strconv.FormatInt(key.ID, 10)]
|
||||
}
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// swagger:route DELETE /auth/keys/{id} api_keys deleteAPIkey
|
||||
//
|
||||
// Delete API key.
|
||||
//
|
||||
// Deletes an API key.
|
||||
// Deprecated. See: https://grafana.com/docs/grafana/next/administration/service-accounts/migrate-api-keys/.
|
||||
//
|
||||
// Deprecated: true
|
||||
// Responses:
|
||||
// 200: okResponse
|
||||
// 401: unauthorisedError
|
||||
// 403: forbiddenError
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (hs *HTTPServer) DeleteAPIKey(c *contextmodel.ReqContext) response.Response {
|
||||
id, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "id is invalid", err)
|
||||
}
|
||||
|
||||
cmd := &apikey.DeleteCommand{ID: id, OrgID: c.GetOrgID()}
|
||||
err = hs.apiKeyService.DeleteApiKey(c.Req.Context(), cmd)
|
||||
if err != nil {
|
||||
var status int
|
||||
if errors.Is(err, apikey.ErrNotFound) {
|
||||
status = http.StatusNotFound
|
||||
} else {
|
||||
status = http.StatusInternalServerError
|
||||
}
|
||||
return response.Error(status, "Failed to delete API key", err)
|
||||
}
|
||||
|
||||
return response.Success("API key deleted")
|
||||
}
|
||||
|
||||
// swagger:route POST /auth/keys api_keys addAPIkey
|
||||
//
|
||||
// Creates an API key.
|
||||
//
|
||||
// Will return details of the created API key.
|
||||
//
|
||||
// Deprecated: true
|
||||
// Deprecated. Please use POST /api/serviceaccounts and POST /api/serviceaccounts/{id}/tokens
|
||||
//
|
||||
// see: https://grafana.com/docs/grafana/next/administration/service-accounts/migrate-api-keys/.
|
||||
//
|
||||
// Responses:
|
||||
// 410: goneError
|
||||
func (hs *HTTPServer) AddAPIKey(c *contextmodel.ReqContext) response.Response {
|
||||
hs.log.Warn("Obsolete and Permanently moved API endpoint called", "path", c.Req.URL.Path)
|
||||
|
||||
// Respond with a 410 Gone status code
|
||||
return response.Error(
|
||||
http.StatusGone,
|
||||
"this endpoint has been removed, please use POST /api/serviceaccounts and POST /api/serviceaccounts/{id}/tokens instead",
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
// swagger:parameters getAPIkeys
|
||||
type GetAPIkeysParams struct {
|
||||
// Show expired keys
|
||||
// in:query
|
||||
// required:false
|
||||
// default:false
|
||||
IncludeExpired bool `json:"includeExpired"`
|
||||
}
|
||||
|
||||
// swagger:parameters deleteAPIkey
|
||||
type DeleteAPIkeyParams struct {
|
||||
// in:path
|
||||
// required:true
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
// swagger:response getAPIkeyResponse
|
||||
type GetAPIkeyResponse struct {
|
||||
// The response message
|
||||
// in: body
|
||||
Body []*dtos.ApiKeyDTO `json:"body"`
|
||||
}
|
||||
@@ -1,12 +1,5 @@
|
||||
package dtos
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
)
|
||||
|
||||
// swagger:model
|
||||
type NewApiKeyResult struct {
|
||||
// example: 1
|
||||
@@ -16,12 +9,3 @@ type NewApiKeyResult struct {
|
||||
// example: glsa_yscW25imSKJIuav8zF37RZmnbiDvB05G_fcaaf58a
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type ApiKeyDTO struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Role org.RoleType `json:"role"`
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
LastUsedAt *time.Time `json:"lastUsedAt,omitempty"`
|
||||
AccessControl accesscontrol.Metadata `json:"accessControl,omitempty"`
|
||||
}
|
||||
|
||||
@@ -5,13 +5,9 @@ import (
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
GetAPIKeys(ctx context.Context, query *GetApiKeysQuery) (res []*APIKey, err error)
|
||||
GetAllAPIKeys(ctx context.Context, orgID int64) ([]*APIKey, error)
|
||||
DeleteApiKey(ctx context.Context, cmd *DeleteCommand) error
|
||||
AddAPIKey(ctx context.Context, cmd *AddCommand) (res *APIKey, err error)
|
||||
GetApiKeyByName(ctx context.Context, query *GetByNameQuery) (res *APIKey, err error)
|
||||
GetAPIKeyByHash(ctx context.Context, hash string) (*APIKey, error)
|
||||
UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error
|
||||
// IsDisabled returns true if the API key is not available for use.
|
||||
IsDisabled(ctx context.Context, orgID int64) (bool, error)
|
||||
}
|
||||
|
||||
@@ -37,9 +37,6 @@ func (s *Service) Usage(ctx context.Context, scopeParams *quota.ScopeParameters)
|
||||
return s.store.Count(ctx, scopeParams)
|
||||
}
|
||||
|
||||
func (s *Service) GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) ([]*apikey.APIKey, error) {
|
||||
return s.store.GetAPIKeys(ctx, query)
|
||||
}
|
||||
func (s *Service) GetAllAPIKeys(ctx context.Context, orgID int64) ([]*apikey.APIKey, error) {
|
||||
return s.store.GetAllAPIKeys(ctx, orgID)
|
||||
}
|
||||
@@ -49,9 +46,6 @@ func (s *Service) GetApiKeyByName(ctx context.Context, query *apikey.GetByNameQu
|
||||
func (s *Service) GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error) {
|
||||
return s.store.GetAPIKeyByHash(ctx, hash)
|
||||
}
|
||||
func (s *Service) DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error {
|
||||
return s.store.DeleteApiKey(ctx, cmd)
|
||||
}
|
||||
func (s *Service) AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) (res *apikey.APIKey, err error) {
|
||||
return s.store.AddAPIKey(ctx, cmd)
|
||||
}
|
||||
@@ -59,16 +53,6 @@ func (s *Service) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) e
|
||||
return s.store.UpdateAPIKeyLastUsedDate(ctx, tokenID)
|
||||
}
|
||||
|
||||
// IsDisabled returns true if the apikey service is disabled for the given org.
|
||||
// This is the case if the org has no apikeys.
|
||||
func (s *Service) IsDisabled(ctx context.Context, orgID int64) (bool, error) {
|
||||
apikeys, err := s.store.CountAPIKeys(ctx, orgID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return apikeys == 0, nil
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ import (
|
||||
)
|
||||
|
||||
type store interface {
|
||||
GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) (res []*apikey.APIKey, err error)
|
||||
GetAllAPIKeys(ctx context.Context, orgID int64) ([]*apikey.APIKey, error)
|
||||
CountAPIKeys(ctx context.Context, orgID int64) (int64, error)
|
||||
DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error
|
||||
AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) (res *apikey.APIKey, err error)
|
||||
GetApiKeyByName(ctx context.Context, query *apikey.GetByNameQuery) (res *apikey.APIKey, err error)
|
||||
GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||
@@ -26,7 +25,6 @@ type getStore func(db.DB) store
|
||||
type getApiKeysTestCase struct {
|
||||
desc string
|
||||
user identity.Requester
|
||||
expectedNumKeys int
|
||||
expectedAllNumKeys int
|
||||
}
|
||||
|
||||
@@ -86,12 +84,6 @@ func testIntegrationApiKeyDataAccess(t *testing.T, fn getStore) {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, key)
|
||||
})
|
||||
t.Run("Should be able to delete key by id", func(t *testing.T) {
|
||||
key, err := ss.GetAPIKeyByHash(context.Background(), cmd.Key)
|
||||
assert.NoError(t, err)
|
||||
err = ss.DeleteApiKey(context.Background(), &apikey.DeleteCommand{ID: key.ID, OrgID: key.OrgID})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Add non expiring key", func(t *testing.T) {
|
||||
@@ -171,34 +163,6 @@ func testIntegrationApiKeyDataAccess(t *testing.T, fn getStore) {
|
||||
|
||||
// advance mocked getTime by 1s
|
||||
timeNow()
|
||||
|
||||
testUser := &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAPIKeyRead: []string{accesscontrol.ScopeAPIKeysAll}},
|
||||
},
|
||||
}
|
||||
query := apikey.GetApiKeysQuery{OrgID: 1, IncludeExpired: false, User: testUser}
|
||||
keys, err := ss.GetAPIKeys(context.Background(), &query)
|
||||
assert.Nil(t, err)
|
||||
|
||||
for _, k := range keys {
|
||||
if k.Name == "key2" {
|
||||
t.Fatalf("key2 should not be there")
|
||||
}
|
||||
}
|
||||
|
||||
query = apikey.GetApiKeysQuery{OrgID: 1, IncludeExpired: true, User: testUser}
|
||||
keys, err = ss.GetAPIKeys(context.Background(), &query)
|
||||
assert.Nil(t, err)
|
||||
|
||||
found := false
|
||||
for _, k := range keys {
|
||||
if k.Name == "key2" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -206,13 +170,6 @@ func testIntegrationApiKeyDataAccess(t *testing.T, fn getStore) {
|
||||
db := db.InitTestDB(t)
|
||||
ss := fn(db)
|
||||
|
||||
t.Run("Delete non-existing key should return error", func(t *testing.T) {
|
||||
cmd := apikey.DeleteCommand{ID: 1}
|
||||
err := ss.DeleteApiKey(context.Background(), &cmd)
|
||||
|
||||
assert.EqualError(t, err, apikey.ErrNotFound.Error())
|
||||
})
|
||||
|
||||
t.Run("Testing API Duplicate Key Errors", func(t *testing.T) {
|
||||
t.Run("Given saved api key", func(t *testing.T) {
|
||||
cmd := apikey.AddCommand{OrgID: 0, Name: "duplicate", Key: "asd"}
|
||||
@@ -235,7 +192,6 @@ func testIntegrationApiKeyDataAccess(t *testing.T, fn getStore) {
|
||||
user: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
1: {"apikeys:read": {"apikeys:*"}},
|
||||
}},
|
||||
expectedNumKeys: 10,
|
||||
expectedAllNumKeys: 10,
|
||||
},
|
||||
{
|
||||
@@ -243,7 +199,6 @@ func testIntegrationApiKeyDataAccess(t *testing.T, fn getStore) {
|
||||
user: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
1: {"apikeys:read": {"apikeys:id:1", "apikeys:id:3"}},
|
||||
}},
|
||||
expectedNumKeys: 2,
|
||||
expectedAllNumKeys: 10,
|
||||
},
|
||||
{
|
||||
@@ -251,7 +206,6 @@ func testIntegrationApiKeyDataAccess(t *testing.T, fn getStore) {
|
||||
user: &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
1: {"apikeys:read": {}},
|
||||
}},
|
||||
expectedNumKeys: 0,
|
||||
expectedAllNumKeys: 10,
|
||||
},
|
||||
}
|
||||
@@ -262,11 +216,6 @@ func testIntegrationApiKeyDataAccess(t *testing.T, fn getStore) {
|
||||
store := fn(db)
|
||||
seedApiKeys(t, store, 10)
|
||||
|
||||
query := &apikey.GetApiKeysQuery{OrgID: 1, User: tt.user}
|
||||
keys, err := store.GetAPIKeys(context.Background(), query)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, keys, tt.expectedNumKeys)
|
||||
|
||||
res, err := store.GetAllAPIKeys(context.Background(), 1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedAllNumKeys, len(res))
|
||||
|
||||
@@ -5,10 +5,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util/xorm"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/apikey"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
@@ -21,34 +18,6 @@ type sqlStore struct {
|
||||
// timeNow makes it possible to test usage of time
|
||||
var timeNow = time.Now
|
||||
|
||||
func (ss *sqlStore) GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) (res []*apikey.APIKey, err error) {
|
||||
err = ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
var sess *xorm.Session
|
||||
|
||||
if query.IncludeExpired {
|
||||
sess = dbSession.Limit(100, 0).
|
||||
Where("org_id=?", query.OrgID).
|
||||
Asc("name")
|
||||
} else {
|
||||
sess = dbSession.Limit(100, 0).
|
||||
Where("org_id=? and ( expires IS NULL or expires >= ?)", query.OrgID, timeNow().Unix()).
|
||||
Asc("name")
|
||||
}
|
||||
|
||||
sess = sess.Where("service_account_id IS NULL")
|
||||
|
||||
filter, err := accesscontrol.Filter(query.User, "id", "apikeys:id:", accesscontrol.ActionAPIKeyRead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sess.And(filter.Where, filter.Args...)
|
||||
|
||||
res = make([]*apikey.APIKey, 0)
|
||||
return sess.Find(&res)
|
||||
})
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetAllAPIKeys(ctx context.Context, orgID int64) ([]*apikey.APIKey, error) {
|
||||
result := make([]*apikey.APIKey, 0)
|
||||
err := ss.db.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
@@ -61,42 +30,6 @@ func (ss *sqlStore) GetAllAPIKeys(ctx context.Context, orgID int64) ([]*apikey.A
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) CountAPIKeys(ctx context.Context, orgID int64) (int64, error) {
|
||||
type result struct {
|
||||
Count int64
|
||||
}
|
||||
|
||||
r := result{}
|
||||
err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
rawSQL := "SELECT COUNT(*) AS count FROM api_key WHERE org_id = ? and service_account_id IS NULL"
|
||||
if _, err := sess.SQL(rawSQL, orgID).Get(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return r.Count, err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
rawSQL := "DELETE FROM api_key WHERE id=? and org_id=? and service_account_id IS NULL"
|
||||
result, err := sess.Exec(rawSQL, cmd.ID, cmd.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
} else if n == 0 {
|
||||
return apikey.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) (res *apikey.APIKey, err error) {
|
||||
err = ss.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
key := apikey.APIKey{OrgID: cmd.OrgID, Name: cmd.Name}
|
||||
|
||||
@@ -8,14 +8,10 @@ import (
|
||||
|
||||
type Service struct {
|
||||
ExpectedError error
|
||||
ExpectedBool bool
|
||||
ExpectedAPIKeys []*apikey.APIKey
|
||||
ExpectedAPIKey *apikey.APIKey
|
||||
}
|
||||
|
||||
func (s *Service) GetAPIKeys(ctx context.Context, query *apikey.GetApiKeysQuery) ([]*apikey.APIKey, error) {
|
||||
return s.ExpectedAPIKeys, s.ExpectedError
|
||||
}
|
||||
func (s *Service) GetAllAPIKeys(ctx context.Context, orgID int64) ([]*apikey.APIKey, error) {
|
||||
return s.ExpectedAPIKeys, s.ExpectedError
|
||||
}
|
||||
@@ -25,15 +21,9 @@ func (s *Service) GetApiKeyByName(ctx context.Context, query *apikey.GetByNameQu
|
||||
func (s *Service) GetAPIKeyByHash(ctx context.Context, hash string) (*apikey.APIKey, error) {
|
||||
return s.ExpectedAPIKey, s.ExpectedError
|
||||
}
|
||||
func (s *Service) DeleteApiKey(ctx context.Context, cmd *apikey.DeleteCommand) error {
|
||||
return s.ExpectedError
|
||||
}
|
||||
func (s *Service) AddAPIKey(ctx context.Context, cmd *apikey.AddCommand) (*apikey.APIKey, error) {
|
||||
return s.ExpectedAPIKey, s.ExpectedError
|
||||
}
|
||||
func (s *Service) UpdateAPIKeyLastUsedDate(ctx context.Context, tokenID int64) error {
|
||||
return s.ExpectedError
|
||||
}
|
||||
func (s *Service) IsDisabled(ctx context.Context, orgID int64) (bool, error) {
|
||||
return s.ExpectedBool, s.ExpectedError
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
)
|
||||
@@ -42,16 +41,6 @@ type AddCommand struct {
|
||||
ServiceAccountID *int64 `json:"-"`
|
||||
}
|
||||
|
||||
type DeleteCommand struct {
|
||||
ID int64 `json:"id"`
|
||||
OrgID int64 `json:"-"`
|
||||
}
|
||||
|
||||
type GetApiKeysQuery struct {
|
||||
OrgID int64
|
||||
IncludeExpired bool
|
||||
User identity.Requester
|
||||
}
|
||||
type GetByNameQuery struct {
|
||||
KeyName string
|
||||
OrgID int64
|
||||
|
||||
@@ -154,19 +154,6 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
Url: s.cfg.AppSubURL + "/org/serviceaccounts",
|
||||
})
|
||||
}
|
||||
disabled, err := s.apiKeyService.IsDisabled(ctx, c.GetOrgID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasAccess(ac.ApiKeyAccessEvaluator) && !disabled {
|
||||
accessNodeLinks = append(accessNodeLinks, &navtree.NavLink{
|
||||
Text: "API keys",
|
||||
Id: "apikeys",
|
||||
SubTitle: "Manage and create API keys that are used to interact with Grafana HTTP APIs",
|
||||
Icon: "key-skeleton-alt",
|
||||
Url: s.cfg.AppSubURL + "/org/apikeys",
|
||||
})
|
||||
}
|
||||
|
||||
if s.license.FeatureEnabled("groupsync") &&
|
||||
s.features.IsEnabled(ctx, featuremgmt.FlagGroupAttributeSync) &&
|
||||
|
||||
@@ -65,8 +65,6 @@ func (api *ServiceAccountsAPI) RegisterAPIEndpoints() {
|
||||
serviceAccountsRoute.Get("/:serviceAccountId/tokens", saUIDResolver, auth(accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.ListTokens))
|
||||
serviceAccountsRoute.Post("/:serviceAccountId/tokens", saUIDResolver, auth(accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.CreateToken))
|
||||
serviceAccountsRoute.Delete("/:serviceAccountId/tokens/:tokenId", saUIDResolver, auth(accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteToken))
|
||||
serviceAccountsRoute.Post("/migrate", auth(accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.MigrateApiKeysToServiceAccounts))
|
||||
serviceAccountsRoute.Post("/migrate/:keyId", auth(accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.ConvertToServiceAccount))
|
||||
}, requestmeta.SetOwner(requestmeta.TeamAuth))
|
||||
}
|
||||
|
||||
@@ -302,30 +300,6 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *contextmode
|
||||
return response.JSON(http.StatusOK, serviceAccountSearch)
|
||||
}
|
||||
|
||||
// POST /api/serviceaccounts/migrate
|
||||
func (api *ServiceAccountsAPI) MigrateApiKeysToServiceAccounts(ctx *contextmodel.ReqContext) response.Response {
|
||||
results, err := api.service.MigrateApiKeysToServiceAccounts(ctx.Req.Context(), ctx.GetOrgID())
|
||||
if err != nil {
|
||||
return response.JSON(http.StatusInternalServerError, results)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusOK, results)
|
||||
}
|
||||
|
||||
// POST /api/serviceaccounts/migrate/:keyId
|
||||
func (api *ServiceAccountsAPI) ConvertToServiceAccount(ctx *contextmodel.ReqContext) response.Response {
|
||||
keyId, err := strconv.ParseInt(web.Params(ctx.Req)[":keyId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Key ID is invalid", err)
|
||||
}
|
||||
|
||||
if err := api.service.MigrateApiKey(ctx.Req.Context(), ctx.GetOrgID(), keyId); err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Error converting API key", err)
|
||||
}
|
||||
|
||||
return response.Success("Service accounts migrated")
|
||||
}
|
||||
|
||||
func (api *ServiceAccountsAPI) getAccessControlMetadata(c *contextmodel.ReqContext, saIDs map[string]bool) map[string]accesscontrol.Metadata {
|
||||
if !c.QueryBool("accesscontrol") {
|
||||
return map[string]accesscontrol.Metadata{}
|
||||
|
||||
@@ -2,7 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -239,66 +238,6 @@ func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceAccountsAPI_MigrateApiKeysToServiceAccounts(t *testing.T) {
|
||||
type TestCase struct {
|
||||
desc string
|
||||
orgId int64
|
||||
basicRole org.RoleType
|
||||
permissions []accesscontrol.Permission
|
||||
expectedMigrationResult *serviceaccounts.MigrationResult
|
||||
expectedCode int
|
||||
}
|
||||
|
||||
tests := []TestCase{
|
||||
{
|
||||
desc: "should be able to migrate API keys to service accounts with correct permissions",
|
||||
orgId: 1,
|
||||
basicRole: org.RoleAdmin,
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: serviceaccounts.ActionCreate, Scope: serviceaccounts.ScopeAll},
|
||||
},
|
||||
expectedMigrationResult: &serviceaccounts.MigrationResult{
|
||||
Total: 5,
|
||||
Migrated: 4,
|
||||
Failed: 1,
|
||||
FailedDetails: []string{"API key name: failedKey - Error: migration error"},
|
||||
},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "should not be able to migrate API keys to service accounts with wrong permissions",
|
||||
orgId: 2,
|
||||
basicRole: org.RoleAdmin,
|
||||
permissions: []accesscontrol.Permission{
|
||||
{Action: serviceaccounts.ActionCreate, Scope: serviceaccounts.ScopeAll},
|
||||
},
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
server := setupTests(t, func(a *ServiceAccountsAPI) {
|
||||
a.service = &satests.FakeServiceAccountService{ExpectedMigrationResult: tt.expectedMigrationResult}
|
||||
})
|
||||
|
||||
req := server.NewRequest(http.MethodPost, "/api/serviceaccounts/migrate", nil)
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{OrgRole: tt.basicRole, OrgID: tt.orgId, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions)}})
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.expectedCode, res.StatusCode)
|
||||
if tt.expectedCode == http.StatusOK {
|
||||
var result serviceaccounts.MigrationResult
|
||||
err := json.NewDecoder(res.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedMigrationResult, &result)
|
||||
}
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupTests(t *testing.T, opts ...func(a *ServiceAccountsAPI)) *webtest.Server {
|
||||
t.Helper()
|
||||
cfg := setting.NewCfg()
|
||||
|
||||
@@ -480,26 +480,6 @@ func (s *ServiceAccountsStoreImpl) MigrateApiKeysToServiceAccounts(ctx context.C
|
||||
return migrationResult, nil
|
||||
}
|
||||
|
||||
func (s *ServiceAccountsStoreImpl) MigrateApiKey(ctx context.Context, orgId int64, keyId int64) error {
|
||||
basicKeys, err := s.apiKeyService.GetAllAPIKeys(ctx, orgId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(basicKeys) == 0 {
|
||||
return fmt.Errorf("no API keys to convert found")
|
||||
}
|
||||
for _, key := range basicKeys {
|
||||
if keyId == key.ID {
|
||||
err := s.CreateServiceAccountFromApikey(ctx, key)
|
||||
if err != nil {
|
||||
s.log.Error("Converting to service account failed with error", "keyId", keyId, "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServiceAccountsStoreImpl) CreateServiceAccountFromApikey(ctx context.Context, key *apikey.APIKey) error {
|
||||
prefix := "sa-autogen"
|
||||
cmd := user.CreateUserCommand{
|
||||
|
||||
@@ -2,7 +2,6 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -320,129 +319,6 @@ func TestIntegrationStore_RetrieveServiceAccount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationStore_MigrateApiKeys(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
cases := []struct {
|
||||
desc string
|
||||
serviceAccounts []user.CreateUserCommand
|
||||
key tests.TestApiKey
|
||||
expectedLogin string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
desc: "api key should be migrated to service account token",
|
||||
serviceAccounts: []user.CreateUserCommand{},
|
||||
key: tests.TestApiKey{Name: "test1", Role: org.RoleEditor, OrgId: 1},
|
||||
expectedLogin: "sa-AuToGeN-1-test1", // Using mixed-case to test case-insensitive search.
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "api key should be migrated to service account token on second attempt",
|
||||
serviceAccounts: []user.CreateUserCommand{
|
||||
{Login: "sa-autogen-1-test2"},
|
||||
},
|
||||
key: tests.TestApiKey{Name: "test2", Role: org.RoleEditor, OrgId: 1},
|
||||
expectedLogin: "sa-AuToGeN-1-test2-001", // Using mixed-case to test case-insensitive search.
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "api key should be migrated to service account token on last attempt (the 10th)",
|
||||
serviceAccounts: []user.CreateUserCommand{
|
||||
{Login: "sa-autogen-1-test3"},
|
||||
{Login: "sa-autogen-1-test3-001"},
|
||||
{Login: "sa-autogen-1-test3-002"},
|
||||
{Login: "sa-autogen-1-test3-003"},
|
||||
{Login: "sa-autogen-1-test3-004"},
|
||||
{Login: "sa-autogen-1-test3-005"},
|
||||
{Login: "sa-autogen-1-test3-006"},
|
||||
{Login: "sa-autogen-1-test3-007"},
|
||||
{Login: "sa-autogen-1-test3-008"},
|
||||
{Login: "sa-autogen-1-test3-009"},
|
||||
},
|
||||
key: tests.TestApiKey{Name: "test3", Role: org.RoleEditor, OrgId: 1},
|
||||
expectedLogin: "sa-AuToGeN-1-test3-010", // Using mixed-case to test case-insensitive search.
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "api key should not be migrated to service account token because all attempts failed",
|
||||
serviceAccounts: []user.CreateUserCommand{
|
||||
{Login: "sa-autogen-1-test4"},
|
||||
{Login: "sa-autogen-1-test4-001"},
|
||||
{Login: "sa-autogen-1-test4-002"},
|
||||
{Login: "sa-autogen-1-test4-003"},
|
||||
{Login: "sa-autogen-1-test4-004"},
|
||||
{Login: "sa-autogen-1-test4-005"},
|
||||
{Login: "sa-autogen-1-test4-006"},
|
||||
{Login: "sa-autogen-1-test4-007"},
|
||||
{Login: "sa-autogen-1-test4-008"},
|
||||
{Login: "sa-autogen-1-test4-009"},
|
||||
{Login: "sa-autogen-1-test4-010"},
|
||||
},
|
||||
key: tests.TestApiKey{Name: "test4", Role: org.RoleEditor, OrgId: 1},
|
||||
expectedErr: serviceaccounts.ErrServiceAccountAlreadyExists,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
db, store := setupTestDatabase(t)
|
||||
|
||||
store.cfg.AutoAssignOrg = true
|
||||
store.cfg.AutoAssignOrgId = 1
|
||||
store.cfg.AutoAssignOrgRole = "Viewer"
|
||||
_, err := store.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "main"})
|
||||
require.NoError(t, err)
|
||||
|
||||
key := tests.SetupApiKey(t, db, store.cfg, c.key)
|
||||
|
||||
for _, sa := range c.serviceAccounts {
|
||||
sa.IsServiceAccount = true
|
||||
sa.OrgID = key.OrgID
|
||||
_, err := store.userService.CreateServiceAccount(context.Background(), &sa)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = store.MigrateApiKey(context.Background(), key.OrgID, key.ID)
|
||||
if c.expectedErr != nil {
|
||||
require.ErrorIs(t, err, c.expectedErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
|
||||
q := serviceaccounts.SearchOrgServiceAccountsQuery{
|
||||
OrgID: key.OrgID,
|
||||
Query: c.expectedLogin,
|
||||
Page: 1,
|
||||
Limit: 50,
|
||||
SignedInUser: &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
key.OrgID: {
|
||||
"serviceaccounts:read": {"serviceaccounts:id:*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
serviceAccounts, err := store.SearchOrgServiceAccounts(context.Background(), &q)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), serviceAccounts.TotalCount)
|
||||
saMigrated := serviceAccounts.ServiceAccounts[0]
|
||||
require.Equal(t, string(key.Role), saMigrated.Role)
|
||||
require.Equal(t, strings.ToLower(c.expectedLogin), saMigrated.Login)
|
||||
|
||||
tokens, err := store.ListTokens(context.Background(), &serviceaccounts.GetSATokensQuery{
|
||||
OrgID: &key.OrgID,
|
||||
ServiceAccountID: &saMigrated.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, tokens, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationStore_MigrateAllApiKeys(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
|
||||
@@ -301,15 +301,6 @@ func (sa *ServiceAccountsService) DeleteServiceAccountToken(ctx context.Context,
|
||||
return sa.store.DeleteServiceAccountToken(ctx, orgID, serviceAccountID, tokenID)
|
||||
}
|
||||
|
||||
func (sa *ServiceAccountsService) MigrateApiKey(ctx context.Context, orgID, keyID int64) error {
|
||||
if err := validOrgID(orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validAPIKeyID(keyID); err != nil {
|
||||
return err
|
||||
}
|
||||
return sa.store.MigrateApiKey(ctx, orgID, keyID)
|
||||
}
|
||||
func (sa *ServiceAccountsService) MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) (*serviceaccounts.MigrationResult, error) {
|
||||
if err := validOrgID(orgID); err != nil {
|
||||
return nil, err
|
||||
@@ -389,9 +380,3 @@ func validServiceAccountTokenID(tokenID int64) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func validAPIKeyID(apiKeyID int64) error {
|
||||
if apiKeyID == 0 {
|
||||
return serviceaccounts.ErrServiceAccountInvalidAPIKeyID.Errorf("invalid API key ID 0 has been specified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -75,11 +75,6 @@ func (f *FakeServiceAccountStore) MigrateApiKeysToServiceAccounts(ctx context.Co
|
||||
return f.expectedMigratedResults, f.ExpectedError
|
||||
}
|
||||
|
||||
// MigrateApiKey is a fake migrating an api key to a service account.
|
||||
func (f *FakeServiceAccountStore) MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error {
|
||||
return f.ExpectedError
|
||||
}
|
||||
|
||||
// RevertApiKey is a fake reverting an api key to a service account.
|
||||
func (f *FakeServiceAccountStore) RevertApiKey(ctx context.Context, saId int64, keyId int64) error {
|
||||
return f.ExpectedError
|
||||
|
||||
@@ -15,7 +15,6 @@ type store interface {
|
||||
EnableServiceAccount(ctx context.Context, orgID, serviceAccountID int64, enable bool) error
|
||||
GetUsageMetrics(ctx context.Context) (*serviceaccounts.Stats, error)
|
||||
ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error)
|
||||
MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error
|
||||
MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) (*serviceaccounts.MigrationResult, error)
|
||||
RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error)
|
||||
RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error)
|
||||
|
||||
@@ -123,10 +123,6 @@ func (s *ServiceAccountsProxy) ListTokens(ctx context.Context, query *serviceacc
|
||||
return s.proxiedService.ListTokens(ctx, query)
|
||||
}
|
||||
|
||||
func (s *ServiceAccountsProxy) MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error {
|
||||
return s.proxiedService.MigrateApiKey(ctx, orgID, keyId)
|
||||
}
|
||||
|
||||
func (s *ServiceAccountsProxy) MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) (*serviceaccounts.MigrationResult, error) {
|
||||
return s.proxiedService.MigrateApiKeysToServiceAccounts(ctx, orgID)
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@ type Service interface {
|
||||
DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error
|
||||
ListTokens(ctx context.Context, query *GetSATokensQuery) ([]apikey.APIKey, error)
|
||||
|
||||
// API specific functions
|
||||
MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error
|
||||
MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) (*MigrationResult, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -52,10 +52,6 @@ func (f *FakeServiceAccountService) ListTokens(ctx context.Context, query *servi
|
||||
return f.ExpectedServiceAccountTokens, f.ExpectedErr
|
||||
}
|
||||
|
||||
func (f *FakeServiceAccountService) MigrateApiKey(ctx context.Context, orgID, keyID int64) error {
|
||||
return f.ExpectedErr
|
||||
}
|
||||
|
||||
func (f *FakeServiceAccountService) MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) (*serviceaccounts.MigrationResult, error) {
|
||||
return f.ExpectedMigrationResult, f.ExpectedErr
|
||||
}
|
||||
|
||||
@@ -137,20 +137,6 @@ func (_m *MockServiceAccountService) ListTokens(ctx context.Context, query *serv
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MigrateApiKey provides a mock function with given fields: ctx, orgID, keyId
|
||||
func (_m *MockServiceAccountService) MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error {
|
||||
ret := _m.Called(ctx, orgID, keyId)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) error); ok {
|
||||
r0 = rf(ctx, orgID, keyId)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MigrateApiKeysToServiceAccounts provides a mock function with given fields: ctx, orgID
|
||||
func (_m *MockServiceAccountService) MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) (*serviceaccounts.MigrationResult, error) {
|
||||
ret := _m.Called(ctx, orgID)
|
||||
|
||||
@@ -3027,38 +3027,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiKeyDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accessControl": {
|
||||
"$ref": "#/definitions/Metadata"
|
||||
},
|
||||
"expiration": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"lastUsedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"None",
|
||||
"Viewer",
|
||||
"Editor",
|
||||
"Admin"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Assignments": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9570,15 +9538,6 @@
|
||||
"$ref": "#/definitions/ErrorResponseBody"
|
||||
}
|
||||
},
|
||||
"getAPIkeyResponse": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ApiKeyDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"getAccessControlStatusResponse": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
|
||||
@@ -2188,93 +2188,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/keys": {
|
||||
"get": {
|
||||
"description": "Will return auth keys.\n\nDeprecated: true.\n\nDeprecated. Please use GET /api/serviceaccounts and GET /api/serviceaccounts/{id}/tokens instead\nsee https://grafana.com/docs/grafana/next/administration/service-accounts/migrate-api-keys/.",
|
||||
"tags": [
|
||||
"api_keys"
|
||||
],
|
||||
"summary": "Get auth keys.",
|
||||
"operationId": "getAPIkeys",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Show expired keys",
|
||||
"name": "includeExpired",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/getAPIkeyResponse"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbiddenError"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFoundError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/internalServerError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Will return details of the created API key.",
|
||||
"tags": [
|
||||
"api_keys"
|
||||
],
|
||||
"summary": "Creates an API key.",
|
||||
"operationId": "addAPIkey",
|
||||
"deprecated": true,
|
||||
"responses": {
|
||||
"410": {
|
||||
"$ref": "#/responses/goneError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/keys/{id}": {
|
||||
"delete": {
|
||||
"description": "Deletes an API key.\nDeprecated. See: https://grafana.com/docs/grafana/next/administration/service-accounts/migrate-api-keys/.",
|
||||
"tags": [
|
||||
"api_keys"
|
||||
],
|
||||
"summary": "Delete API key.",
|
||||
"operationId": "deleteAPIkey",
|
||||
"deprecated": true,
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/okResponse"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/responses/forbiddenError"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/responses/notFoundError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/responses/internalServerError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -13308,38 +13221,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiKeyDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accessControl": {
|
||||
"$ref": "#/definitions/Metadata"
|
||||
},
|
||||
"expiration": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"lastUsedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"None",
|
||||
"Viewer",
|
||||
"Editor",
|
||||
"Admin"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiRuleNode": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -24160,15 +24041,6 @@
|
||||
"$ref": "#/definitions/ErrorResponseBody"
|
||||
}
|
||||
},
|
||||
"getAPIkeyResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ApiKeyDTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"getAccessControlStatusResponse": {
|
||||
"description": "(empty)",
|
||||
"schema": {
|
||||
|
||||
@@ -628,19 +628,6 @@
|
||||
},
|
||||
"description": "A GenericError is the default error message that is generated.\nFor certain status codes there are more appropriate error structures."
|
||||
},
|
||||
"getAPIkeyResponse": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ApiKeyDTO"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "(empty)"
|
||||
},
|
||||
"getAccessControlStatusResponse": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
@@ -3284,38 +3271,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiKeyDTO": {
|
||||
"properties": {
|
||||
"accessControl": {
|
||||
"$ref": "#/components/schemas/Metadata"
|
||||
},
|
||||
"expiration": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"lastUsedAt": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"enum": [
|
||||
"None",
|
||||
"Viewer",
|
||||
"Editor",
|
||||
"Admin"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiRuleNode": {
|
||||
"properties": {
|
||||
"alert": {
|
||||
@@ -16055,97 +16010,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/keys": {
|
||||
"get": {
|
||||
"description": "Will return auth keys.\n\nDeprecated: true.\n\nDeprecated. Please use GET /api/serviceaccounts and GET /api/serviceaccounts/{id}/tokens instead\nsee https://grafana.com/docs/grafana/next/administration/service-accounts/migrate-api-keys/.",
|
||||
"operationId": "getAPIkeys",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Show expired keys",
|
||||
"in": "query",
|
||||
"name": "includeExpired",
|
||||
"schema": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/getAPIkeyResponse"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbiddenError"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/notFoundError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/components/responses/internalServerError"
|
||||
}
|
||||
},
|
||||
"summary": "Get auth keys.",
|
||||
"tags": [
|
||||
"api_keys"
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"deprecated": true,
|
||||
"description": "Will return details of the created API key.",
|
||||
"operationId": "addAPIkey",
|
||||
"responses": {
|
||||
"410": {
|
||||
"$ref": "#/components/responses/goneError"
|
||||
}
|
||||
},
|
||||
"summary": "Creates an API key.",
|
||||
"tags": [
|
||||
"api_keys"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/auth/keys/{id}": {
|
||||
"delete": {
|
||||
"deprecated": true,
|
||||
"description": "Deletes an API key.\nDeprecated. See: https://grafana.com/docs/grafana/next/administration/service-accounts/migrate-api-keys/.",
|
||||
"operationId": "deleteAPIkey",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/okResponse"
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/unauthorisedError"
|
||||
},
|
||||
"403": {
|
||||
"$ref": "#/components/responses/forbiddenError"
|
||||
},
|
||||
"404": {
|
||||
"$ref": "#/components/responses/notFoundError"
|
||||
},
|
||||
"500": {
|
||||
"$ref": "#/components/responses/internalServerError"
|
||||
}
|
||||
},
|
||||
"summary": "Delete API key.",
|
||||
"tags": [
|
||||
"api_keys"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/cloudmigration/migration": {
|
||||
"get": {
|
||||
"operationId": "getSessionList",
|
||||
|
||||
Reference in New Issue
Block a user