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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user