ServiceAccounts: Allow use of UIDs for Service Account routes (#95401)
This commit is contained in:
@@ -51,7 +51,10 @@ func ProvideServiceAccountPermissions(
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = serviceAccountRetrieverService.RetrieveServiceAccount(ctx, orgID, id)
|
||||
_, err = serviceAccountRetrieverService.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: orgID,
|
||||
ID: id,
|
||||
})
|
||||
return err
|
||||
},
|
||||
Assignments: resourcepermissions.Assignments{
|
||||
|
||||
@@ -31,6 +31,30 @@ type ServiceAccountsAPI struct {
|
||||
isExternalSAEnabled bool
|
||||
}
|
||||
|
||||
func MiddlewareServiceAccountUIDResolver(saService serviceaccounts.Service, paramName string) web.Handler {
|
||||
return func(c *contextmodel.ReqContext) {
|
||||
// Get service account id from request
|
||||
saUID := web.Params(c.Req)[paramName]
|
||||
// if saID is empty or is an integer, we assume it's a service account id and we don't need to resolve it
|
||||
_, err := strconv.ParseInt(saUID, 10, 64)
|
||||
if saUID == "" || err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
serviceAccount, err := saService.RetrieveServiceAccount(c.Req.Context(), &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: c.SignedInUser.GetOrgID(),
|
||||
UID: saUID,
|
||||
})
|
||||
if err == nil {
|
||||
gotParams := web.Params(c.Req)
|
||||
gotParams[paramName] = strconv.FormatInt(serviceAccount.Id, 10)
|
||||
web.SetURLParams(c.Req, gotParams)
|
||||
} else {
|
||||
c.JsonApiErr(http.StatusNotFound, "Not found", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewServiceAccountsAPI(
|
||||
cfg *setting.Cfg,
|
||||
service serviceaccounts.Service,
|
||||
@@ -55,15 +79,16 @@ func NewServiceAccountsAPI(
|
||||
|
||||
func (api *ServiceAccountsAPI) RegisterAPIEndpoints() {
|
||||
auth := accesscontrol.Middleware(api.accesscontrol)
|
||||
saUIDResolver := MiddlewareServiceAccountUIDResolver(api.service, ":serviceAccountId")
|
||||
api.RouterRegister.Group("/api/serviceaccounts", func(serviceAccountsRoute routing.RouteRegister) {
|
||||
serviceAccountsRoute.Get("/search", auth(accesscontrol.EvalPermission(serviceaccounts.ActionRead)), routing.Wrap(api.SearchOrgServiceAccountsWithPaging))
|
||||
serviceAccountsRoute.Post("/", auth(accesscontrol.EvalPermission(serviceaccounts.ActionCreate)), routing.Wrap(api.CreateServiceAccount))
|
||||
serviceAccountsRoute.Get("/:serviceAccountId", auth(accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.RetrieveServiceAccount))
|
||||
serviceAccountsRoute.Patch("/:serviceAccountId", auth(accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.UpdateServiceAccount))
|
||||
serviceAccountsRoute.Delete("/:serviceAccountId", auth(accesscontrol.EvalPermission(serviceaccounts.ActionDelete, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteServiceAccount))
|
||||
serviceAccountsRoute.Get("/:serviceAccountId/tokens", auth(accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.ListTokens))
|
||||
serviceAccountsRoute.Post("/:serviceAccountId/tokens", auth(accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.CreateToken))
|
||||
serviceAccountsRoute.Delete("/:serviceAccountId/tokens/:tokenId", auth(accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteToken))
|
||||
serviceAccountsRoute.Get("/:serviceAccountId", saUIDResolver, auth(accesscontrol.EvalPermission(serviceaccounts.ActionRead, serviceaccounts.ScopeID)), routing.Wrap(api.RetrieveServiceAccount))
|
||||
serviceAccountsRoute.Patch("/:serviceAccountId", saUIDResolver, auth(accesscontrol.EvalPermission(serviceaccounts.ActionWrite, serviceaccounts.ScopeID)), routing.Wrap(api.UpdateServiceAccount))
|
||||
serviceAccountsRoute.Delete("/:serviceAccountId", saUIDResolver, auth(accesscontrol.EvalPermission(serviceaccounts.ActionDelete, serviceaccounts.ScopeID)), routing.Wrap(api.DeleteServiceAccount))
|
||||
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))
|
||||
@@ -125,12 +150,15 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *contextmodel.ReqContext)
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (api *ServiceAccountsAPI) RetrieveServiceAccount(ctx *contextmodel.ReqContext) response.Response {
|
||||
scopeID, err := strconv.ParseInt(web.Params(ctx.Req)[":serviceAccountId"], 10, 64)
|
||||
saID, err := strconv.ParseInt(web.Params(ctx.Req)[":serviceAccountId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Service Account ID is invalid", err)
|
||||
}
|
||||
|
||||
serviceAccount, err := api.service.RetrieveServiceAccount(ctx.Req.Context(), ctx.SignedInUser.GetOrgID(), scopeID)
|
||||
serviceAccount, err := api.service.RetrieveServiceAccount(ctx.Req.Context(), &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: ctx.SignedInUser.GetOrgID(),
|
||||
ID: saID,
|
||||
})
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to retrieve service account", err)
|
||||
}
|
||||
@@ -167,7 +195,7 @@ func (api *ServiceAccountsAPI) RetrieveServiceAccount(ctx *contextmodel.ReqConte
|
||||
// 404: notFoundError
|
||||
// 500: internalServerError
|
||||
func (api *ServiceAccountsAPI) UpdateServiceAccount(c *contextmodel.ReqContext) response.Response {
|
||||
scopeID, err := strconv.ParseInt(web.Params(c.Req)[":serviceAccountId"], 10, 64)
|
||||
saID, err := strconv.ParseInt(web.Params(c.Req)[":serviceAccountId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Service Account ID is invalid", err)
|
||||
}
|
||||
@@ -181,7 +209,7 @@ func (api *ServiceAccountsAPI) UpdateServiceAccount(c *contextmodel.ReqContext)
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to update service account", err)
|
||||
}
|
||||
|
||||
resp, err := api.service.UpdateServiceAccount(c.Req.Context(), c.SignedInUser.GetOrgID(), scopeID, &cmd)
|
||||
resp, err := api.service.UpdateServiceAccount(c.Req.Context(), c.SignedInUser.GetOrgID(), saID, &cmd)
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed update service account", err)
|
||||
}
|
||||
@@ -223,11 +251,11 @@ func (api *ServiceAccountsAPI) validateRole(r *org.RoleType, orgRole org.RoleTyp
|
||||
// 403: forbiddenError
|
||||
// 500: internalServerError
|
||||
func (api *ServiceAccountsAPI) DeleteServiceAccount(ctx *contextmodel.ReqContext) response.Response {
|
||||
scopeID, err := strconv.ParseInt(web.Params(ctx.Req)[":serviceAccountId"], 10, 64)
|
||||
saID, err := strconv.ParseInt(web.Params(ctx.Req)[":serviceAccountId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Service account ID is invalid", err)
|
||||
}
|
||||
err = api.service.DeleteServiceAccount(ctx.Req.Context(), ctx.SignedInUser.GetOrgID(), scopeID)
|
||||
err = api.service.DeleteServiceAccount(ctx.Req.Context(), ctx.SignedInUser.GetOrgID(), saID)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Service account deletion error", err)
|
||||
}
|
||||
|
||||
@@ -132,7 +132,10 @@ func (api *ServiceAccountsAPI) CreateToken(c *contextmodel.ReqContext) response.
|
||||
}
|
||||
|
||||
// confirm service account exists
|
||||
if _, err = api.service.RetrieveServiceAccount(c.Req.Context(), c.SignedInUser.GetOrgID(), saID); err != nil {
|
||||
if _, err = api.service.RetrieveServiceAccount(c.Req.Context(), &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: c.SignedInUser.GetOrgID(),
|
||||
ID: saID,
|
||||
}); err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to retrieve service account", err)
|
||||
}
|
||||
|
||||
@@ -205,7 +208,10 @@ func (api *ServiceAccountsAPI) DeleteToken(c *contextmodel.ReqContext) response.
|
||||
}
|
||||
|
||||
// confirm service account exists
|
||||
if _, err := api.service.RetrieveServiceAccount(c.Req.Context(), c.SignedInUser.GetOrgID(), saID); err != nil {
|
||||
if _, err := api.service.RetrieveServiceAccount(c.Req.Context(), &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: c.SignedInUser.GetOrgID(),
|
||||
ID: saID,
|
||||
}); err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to retrieve service account", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ import (
|
||||
)
|
||||
|
||||
func TestIntegrationStore_UsageStats(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
|
||||
saToCreate := tests.TestUser{Login: "servicetestwithTeam@admin", IsServiceAccount: true}
|
||||
db, store := setupTestDatabase(t)
|
||||
sa := tests.SetupUserServiceAccount(t, db, store.cfg, saToCreate)
|
||||
|
||||
@@ -3,6 +3,7 @@ package database
|
||||
//nolint:goimports
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -81,6 +82,7 @@ func (s *ServiceAccountsStoreImpl) CreateServiceAccount(ctx context.Context, org
|
||||
|
||||
return &serviceaccounts.ServiceAccountDTO{
|
||||
Id: newSA.ID,
|
||||
UID: newSA.UID,
|
||||
Name: newSA.Name,
|
||||
Login: newSA.Login,
|
||||
OrgId: newSA.OrgID,
|
||||
@@ -100,7 +102,7 @@ func (s *ServiceAccountsStoreImpl) UpdateServiceAccount(
|
||||
|
||||
err := s.sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
var err error
|
||||
updatedUser, err = s.RetrieveServiceAccount(ctx, orgId, serviceAccountId)
|
||||
updatedUser, err = s.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{OrgID: orgId, ID: serviceAccountId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -192,8 +194,15 @@ func (s *ServiceAccountsStoreImpl) EnableServiceAccount(ctx context.Context, org
|
||||
})
|
||||
}
|
||||
|
||||
// RetrieveServiceAccount returns a service account by its ID
|
||||
func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, orgId, serviceAccountId int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
// RetrieveServiceAccount returns a service account by its ID or UID
|
||||
func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
if query.ID == 0 && query.UID == "" {
|
||||
return nil, errors.New("either ID or UID must be provided")
|
||||
}
|
||||
if query.OrgID == 0 {
|
||||
return nil, errors.New("OrgID must be provided")
|
||||
}
|
||||
|
||||
serviceAccount := &serviceaccounts.ServiceAccountProfileDTO{}
|
||||
|
||||
err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
|
||||
@@ -205,10 +214,17 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, o
|
||||
whereParams := make([]any, 0)
|
||||
|
||||
whereConditions = append(whereConditions, "org_user.org_id = ?")
|
||||
whereParams = append(whereParams, orgId)
|
||||
whereParams = append(whereParams, query.OrgID)
|
||||
|
||||
whereConditions = append(whereConditions, "org_user.user_id = ?")
|
||||
whereParams = append(whereParams, serviceAccountId)
|
||||
if query.ID != 0 {
|
||||
whereConditions = append(whereConditions, "org_user.user_id = ?")
|
||||
whereParams = append(whereParams, query.ID)
|
||||
}
|
||||
|
||||
if query.UID != "" {
|
||||
whereConditions = append(whereConditions, "user.uid = ?")
|
||||
whereParams = append(whereParams, query.UID)
|
||||
}
|
||||
|
||||
whereConditions = append(whereConditions,
|
||||
fmt.Sprintf("%s.is_service_account = %s",
|
||||
@@ -223,6 +239,7 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, o
|
||||
"org_user.role",
|
||||
"user.email",
|
||||
"user.name",
|
||||
"user.uid",
|
||||
"user.login",
|
||||
"user.created",
|
||||
"user.updated",
|
||||
@@ -232,7 +249,7 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, o
|
||||
if ok, err := sess.Get(serviceAccount); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return serviceaccounts.ErrServiceAccountNotFound.Errorf("service account with id %d not found", serviceAccountId)
|
||||
return serviceaccounts.ErrServiceAccountNotFound.Errorf("service account with id %d or uid %s not found", query.ID, query.UID)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -377,6 +394,7 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context,
|
||||
"user.email",
|
||||
"user.name",
|
||||
"user.login",
|
||||
"user.uid",
|
||||
"user.last_seen_at",
|
||||
"user.is_disabled",
|
||||
)
|
||||
|
||||
@@ -28,7 +28,11 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
// Service Account should not create an org on its own
|
||||
func TestStore_CreateServiceAccountOrgNonExistant(t *testing.T) {
|
||||
func TestIntegrationStore_CreateServiceAccountOrgNonExistant(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
|
||||
_, store := setupTestDatabase(t)
|
||||
serviceAccountName := "new Service Account"
|
||||
t.Run("create service account", func(t *testing.T) {
|
||||
@@ -67,7 +71,10 @@ func TestStore_CreateServiceAccount(t *testing.T) {
|
||||
assert.Equal(t, serviceAccountName, saDTO.Name)
|
||||
assert.Equal(t, 0, int(saDTO.Tokens))
|
||||
|
||||
retrieved, err := store.RetrieveServiceAccount(context.Background(), serviceAccountOrgId, saDTO.Id)
|
||||
retrieved, err := store.RetrieveServiceAccount(context.Background(), &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: serviceAccountOrgId,
|
||||
ID: saDTO.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, serviceAccountName, retrieved.Name)
|
||||
assert.Equal(t, serviceAccountOrgId, retrieved.OrgId)
|
||||
@@ -98,7 +105,10 @@ func TestStore_CreateServiceAccount(t *testing.T) {
|
||||
assert.Equal(t, serviceAccountName, saDTO.Name)
|
||||
assert.Equal(t, 0, int(saDTO.Tokens))
|
||||
|
||||
retrieved, err := store.RetrieveServiceAccount(context.Background(), serviceAccountOrgId, saDTO.Id)
|
||||
retrieved, err := store.RetrieveServiceAccount(context.Background(), &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: serviceAccountOrgId,
|
||||
ID: saDTO.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, serviceAccountName, retrieved.Name)
|
||||
assert.Equal(t, serviceAccountOrgId, retrieved.OrgId)
|
||||
@@ -133,7 +143,10 @@ func TestStore_CreateServiceAccount(t *testing.T) {
|
||||
assert.Equal(t, serviceAccountName, saDTO.Name)
|
||||
assert.Equal(t, 0, int(saDTO.Tokens))
|
||||
|
||||
retrieved, err := store.RetrieveServiceAccount(context.Background(), serviceAccountOrgId, saDTO.Id)
|
||||
retrieved, err := store.RetrieveServiceAccount(context.Background(), &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: serviceAccountOrgId,
|
||||
ID: saDTO.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, serviceAccountName, retrieved.Name)
|
||||
assert.Equal(t, serviceAccountOrgId, retrieved.OrgId)
|
||||
@@ -156,7 +169,11 @@ func TestStore_CreateServiceAccount(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStore_CreateServiceAccountRoleNone(t *testing.T) {
|
||||
func TestIntegrationStore_CreateServiceAccountRoleNone(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
|
||||
_, store := setupTestDatabase(t)
|
||||
orgQuery := &org.CreateOrgCommand{Name: orgimpl.MainOrgName}
|
||||
orgResult, err := store.orgService.CreateWithMember(context.Background(), orgQuery)
|
||||
@@ -176,7 +193,10 @@ func TestStore_CreateServiceAccountRoleNone(t *testing.T) {
|
||||
assert.Equal(t, serviceAccountName, saDTO.Name)
|
||||
assert.Equal(t, 0, int(saDTO.Tokens))
|
||||
|
||||
retrieved, err := store.RetrieveServiceAccount(context.Background(), serviceAccountOrgId, saDTO.Id)
|
||||
retrieved, err := store.RetrieveServiceAccount(context.Background(), &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: serviceAccountOrgId,
|
||||
ID: saDTO.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, serviceAccountName, retrieved.Name)
|
||||
assert.Equal(t, serviceAccountOrgId, retrieved.OrgId)
|
||||
@@ -188,7 +208,10 @@ func TestStore_CreateServiceAccountRoleNone(t *testing.T) {
|
||||
assert.Equal(t, saDTO.Role, string(org.RoleNone))
|
||||
}
|
||||
|
||||
func TestStore_DeleteServiceAccount(t *testing.T) {
|
||||
func TestIntegrationStore_DeleteServiceAccount(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
cases := []struct {
|
||||
desc string
|
||||
user tests.TestUser
|
||||
@@ -237,7 +260,10 @@ func setupTestDatabase(t *testing.T) (db.DB, *ServiceAccountsStoreImpl) {
|
||||
return db, ProvideServiceAccountsStore(cfg, db, apiKeyService, kvStore, userSvc, orgService)
|
||||
}
|
||||
|
||||
func TestStore_RetrieveServiceAccount(t *testing.T) {
|
||||
func TestIntegrationStore_RetrieveServiceAccount(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
cases := []struct {
|
||||
desc string
|
||||
user tests.TestUser
|
||||
@@ -259,7 +285,10 @@ func TestStore_RetrieveServiceAccount(t *testing.T) {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
db, store := setupTestDatabase(t)
|
||||
user := tests.SetupUserServiceAccount(t, db, store.cfg, c.user)
|
||||
dto, err := store.RetrieveServiceAccount(context.Background(), user.OrgID, user.ID)
|
||||
dto, err := store.RetrieveServiceAccount(context.Background(), &serviceaccounts.GetServiceAccountQuery{
|
||||
OrgID: user.OrgID,
|
||||
ID: user.ID,
|
||||
})
|
||||
if c.expectedErr != nil {
|
||||
require.ErrorIs(t, err, c.expectedErr)
|
||||
} else {
|
||||
@@ -271,7 +300,10 @@ func TestStore_RetrieveServiceAccount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_MigrateApiKeys(t *testing.T) {
|
||||
func TestIntegrationStore_MigrateApiKeys(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
cases := []struct {
|
||||
desc string
|
||||
key tests.TestApiKey
|
||||
@@ -331,7 +363,10 @@ func TestStore_MigrateApiKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_MigrateAllApiKeys(t *testing.T) {
|
||||
func TestIntegrationStore_MigrateAllApiKeys(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
cases := []struct {
|
||||
desc string
|
||||
keys []tests.TestApiKey
|
||||
@@ -448,7 +483,11 @@ func TestStore_MigrateAllApiKeys(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestServiceAccountsStoreImpl_SearchOrgServiceAccounts(t *testing.T) {
|
||||
func TestIntegrationServiceAccountsStoreImpl_SearchOrgServiceAccounts(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
|
||||
initUsers := []tests.TestUser{
|
||||
{Name: "satest-1", Role: string(org.RoleViewer), Login: "sa-1-satest-1", IsServiceAccount: true},
|
||||
{Name: "usertest-2", Role: string(org.RoleEditor), Login: "usertest-2", IsServiceAccount: false},
|
||||
@@ -575,7 +614,11 @@ func TestServiceAccountsStoreImpl_SearchOrgServiceAccounts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceAccountsStoreImpl_EnableServiceAccounts(t *testing.T) {
|
||||
func TestIntegrationServiceAccountsStoreImpl_EnableServiceAccounts(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
initUsers := []tests.TestUser{
|
||||
@@ -588,9 +631,9 @@ func TestServiceAccountsStoreImpl_EnableServiceAccounts(t *testing.T) {
|
||||
orgID := tests.SetupUsersServiceAccounts(t, db, store.cfg, initUsers)
|
||||
|
||||
fetchStates := func() map[int64]bool {
|
||||
sa1, err := store.RetrieveServiceAccount(ctx, orgID, 1)
|
||||
sa1, err := store.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{OrgID: orgID, ID: 1})
|
||||
require.NoError(t, err)
|
||||
sa2, err := store.RetrieveServiceAccount(ctx, orgID, 2)
|
||||
sa2, err := store.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{OrgID: orgID, ID: 2})
|
||||
require.NoError(t, err)
|
||||
user, err := store.userService.GetByID(ctx, &user.GetUserByIDQuery{ID: 3})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -45,7 +45,7 @@ func (s *ServiceAccountsStoreImpl) AddServiceAccountToken(ctx context.Context, s
|
||||
var apiKey *apikey.APIKey
|
||||
|
||||
return apiKey, s.sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
|
||||
if _, err := s.RetrieveServiceAccount(ctx, cmd.OrgId, serviceAccountId); err != nil {
|
||||
if _, err := s.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{OrgID: cmd.OrgId, ID: serviceAccountId}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ func (esa *ExtSvcAccountsService) RetrieveExtSvcAccount(ctx context.Context, org
|
||||
ctx, span := esa.tracer.Start(ctx, "ExtSvcAccountsService.RetrieveExtSvcAccount")
|
||||
defer span.End()
|
||||
|
||||
svcAcc, err := esa.saSvc.RetrieveServiceAccount(ctx, orgID, saID)
|
||||
svcAcc, err := esa.saSvc.RetrieveServiceAccount(ctx, &sa.GetServiceAccountQuery{OrgID: orgID, ID: saID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -193,14 +193,16 @@ func (sa *ServiceAccountsService) CreateServiceAccount(ctx context.Context, orgI
|
||||
return serviceAccount, nil
|
||||
}
|
||||
|
||||
func (sa *ServiceAccountsService) RetrieveServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
if err := validOrgID(orgID); err != nil {
|
||||
func (sa *ServiceAccountsService) RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
if err := validOrgID(query.OrgID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validServiceAccountID(serviceAccountID); err != nil {
|
||||
return nil, err
|
||||
if err := validServiceAccountID(query.ID); err != nil {
|
||||
if err := validServiceAccountUID(query.UID); err != nil {
|
||||
return nil, fmt.Errorf("invalid service account ID %d and UID %s has been specified", query.ID, query.UID)
|
||||
}
|
||||
}
|
||||
return sa.store.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
|
||||
return sa.store.RetrieveServiceAccount(ctx, query)
|
||||
}
|
||||
|
||||
func (sa *ServiceAccountsService) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) {
|
||||
@@ -303,12 +305,21 @@ func validOrgID(orgID int64) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validServiceAccountID(serviceaccountID int64) error {
|
||||
if serviceaccountID == 0 {
|
||||
return serviceaccounts.ErrServiceAccountInvalidID.Errorf("invalid service account ID 0 has been specified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validServiceAccountUID(saUID string) error {
|
||||
if saUID == "" {
|
||||
return serviceaccounts.ErrServiceAccountInvalidID.Errorf("invalid service account UID has been specified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validServiceAccountTokenID(tokenID int64) error {
|
||||
if tokenID == 0 {
|
||||
return serviceaccounts.ErrServiceAccountInvalidTokenID.Errorf("invalid service account token ID 0 has been specified")
|
||||
|
||||
@@ -35,7 +35,7 @@ func newServiceAccountStoreFake() *FakeServiceAccountStore {
|
||||
}
|
||||
|
||||
// CreateServiceAccount is a fake creating a service account.
|
||||
func (f *FakeServiceAccountStore) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
func (f *FakeServiceAccountStore) RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
return f.ExpectedServiceAccountProfileDTO, f.ExpectedError
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ type store interface {
|
||||
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, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error)
|
||||
RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error)
|
||||
RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error)
|
||||
RevokeServiceAccountToken(ctx context.Context, orgId, serviceAccountId, tokenId int64) error
|
||||
SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error)
|
||||
|
||||
@@ -73,6 +73,8 @@ type UpdateServiceAccountForm struct {
|
||||
// swagger: model
|
||||
type ServiceAccountDTO struct {
|
||||
Id int64 `json:"id" xorm:"user_id"`
|
||||
// example: fe1xejlha91xce
|
||||
UID string `json:"uid" xorm:"uid"`
|
||||
// example: grafana
|
||||
Name string `json:"name" xorm:"name"`
|
||||
// example: sa-grafana
|
||||
@@ -98,6 +100,12 @@ type GetSATokensQuery struct {
|
||||
ServiceAccountID *int64 // optional filtering by service account ID
|
||||
}
|
||||
|
||||
type GetServiceAccountQuery struct {
|
||||
OrgID int64 `json:"orgId"`
|
||||
ID int64 `json:"id"`
|
||||
UID string `json:"uid"`
|
||||
}
|
||||
|
||||
type AddServiceAccountTokenCommand struct {
|
||||
Name string `json:"name" binding:"Required"`
|
||||
OrgId int64 `json:"-"`
|
||||
@@ -135,6 +143,8 @@ type SearchOrgServiceAccountsResult struct {
|
||||
type ServiceAccountProfileDTO struct {
|
||||
// example: 2
|
||||
Id int64 `json:"id" xorm:"user_id"`
|
||||
// example: fe1xejlha91xce
|
||||
UID string `json:"uid" xorm:"uid"`
|
||||
// example: test
|
||||
Name string `json:"name" xorm:"name"`
|
||||
// example: sa-grafana
|
||||
|
||||
@@ -51,7 +51,7 @@ var _ serviceaccounts.Service = (*ServiceAccountsProxy)(nil)
|
||||
|
||||
func (s *ServiceAccountsProxy) AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) (*apikey.APIKey, error) {
|
||||
if s.isProxyEnabled {
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, cmd.OrgId, serviceAccountID)
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{ID: serviceAccountID, OrgID: cmd.OrgId})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func (s *ServiceAccountsProxy) CreateServiceAccount(ctx context.Context, orgID i
|
||||
|
||||
func (s *ServiceAccountsProxy) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
|
||||
if s.isProxyEnabled {
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{ID: serviceAccountID, OrgID: orgID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func (s *ServiceAccountsProxy) DeleteServiceAccount(ctx context.Context, orgID,
|
||||
|
||||
func (s *ServiceAccountsProxy) DeleteServiceAccountToken(ctx context.Context, orgID int64, serviceAccountID int64, tokenID int64) error {
|
||||
if s.isProxyEnabled {
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{OrgID: orgID, ID: serviceAccountID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (s *ServiceAccountsProxy) DeleteServiceAccountToken(ctx context.Context, or
|
||||
|
||||
func (s *ServiceAccountsProxy) EnableServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64, enable bool) error {
|
||||
if s.isProxyEnabled {
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{OrgID: orgID, ID: serviceAccountID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -131,8 +131,8 @@ func (s *ServiceAccountsProxy) MigrateApiKeysToServiceAccounts(ctx context.Conte
|
||||
return s.proxiedService.MigrateApiKeysToServiceAccounts(ctx, orgID)
|
||||
}
|
||||
|
||||
func (s *ServiceAccountsProxy) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
|
||||
func (s *ServiceAccountsProxy) RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -155,7 +155,7 @@ func (s *ServiceAccountsProxy) UpdateServiceAccount(ctx context.Context, orgID,
|
||||
s.log.Error("Invalid service account name", "name", *saForm.Name)
|
||||
return nil, extsvcaccounts.ErrInvalidName
|
||||
}
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
|
||||
sa, err := s.proxiedService.RetrieveServiceAccount(ctx, &serviceaccounts.GetServiceAccountQuery{OrgID: orgID, ID: serviceAccountID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ func TestProvideServiceAccount_crudServiceAccount(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
serviceMock.ExpectedServiceAccountProfile = tc.expectedServiceAccount
|
||||
sa, err := svc.RetrieveServiceAccount(context.Background(), autoAssignOrgID, testServiceAccountId)
|
||||
sa, err := svc.RetrieveServiceAccount(context.Background(), &sa.GetServiceAccountQuery{OrgID: autoAssignOrgID, ID: testServiceAccountId})
|
||||
assert.NoError(t, err, tc.description)
|
||||
assert.Equal(t, tc.expectedIsExternal, sa.IsExternal, tc.description)
|
||||
})
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// At the time of writing, this service is only used by the service accounts permissions service
|
||||
// to avoid cyclic dependency between the ServiceAccountService and the ServiceAccountPermissionsService
|
||||
type ServiceAccountRetriever interface {
|
||||
RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error)
|
||||
RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error)
|
||||
}
|
||||
|
||||
// ServiceAccountRetriever is the service that manages service accounts.
|
||||
@@ -47,6 +47,6 @@ func ProvideService(
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
return s.store.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
|
||||
func (s *Service) RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
return s.store.RetrieveServiceAccount(ctx, query)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ do not have a password.
|
||||
type Service interface {
|
||||
CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error)
|
||||
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
|
||||
RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*ServiceAccountProfileDTO, error)
|
||||
RetrieveServiceAccount(ctx context.Context, query *GetServiceAccountQuery) (*ServiceAccountProfileDTO, error)
|
||||
RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error)
|
||||
SearchOrgServiceAccounts(ctx context.Context, query *SearchOrgServiceAccountsQuery) (*SearchOrgServiceAccountsResult, error)
|
||||
EnableServiceAccount(ctx context.Context, orgID, serviceAccountID int64, enable bool) error
|
||||
|
||||
@@ -37,7 +37,7 @@ func (f *FakeServiceAccountService) EnableServiceAccount(ctx context.Context, or
|
||||
return f.ExpectedErr
|
||||
}
|
||||
|
||||
func (f *FakeServiceAccountService) RetrieveServiceAccount(ctx context.Context, orgID, id int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
func (f *FakeServiceAccountService) RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
return f.ExpectedServiceAccountProfile, f.ExpectedErr
|
||||
}
|
||||
|
||||
|
||||
@@ -178,24 +178,24 @@ func (_m *MockServiceAccountService) MigrateApiKeysToServiceAccounts(ctx context
|
||||
}
|
||||
|
||||
// RetrieveServiceAccount provides a mock function with given fields: ctx, orgID, serviceAccountID
|
||||
func (_m *MockServiceAccountService) RetrieveServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
ret := _m.Called(ctx, orgID, serviceAccountID)
|
||||
func (_m *MockServiceAccountService) RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 *serviceaccounts.ServiceAccountProfileDTO
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) (*serviceaccounts.ServiceAccountProfileDTO, error)); ok {
|
||||
return rf(ctx, orgID, serviceAccountID)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error)); ok {
|
||||
return rf(ctx, query)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, int64) *serviceaccounts.ServiceAccountProfileDTO); ok {
|
||||
r0 = rf(ctx, orgID, serviceAccountID)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *serviceaccounts.GetServiceAccountQuery) *serviceaccounts.ServiceAccountProfileDTO); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*serviceaccounts.ServiceAccountProfileDTO)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, int64) error); ok {
|
||||
r1 = rf(ctx, orgID, serviceAccountID)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *serviceaccounts.GetServiceAccountQuery) error); ok {
|
||||
r1 = rf(ctx, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
@@ -7421,6 +7421,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 0
|
||||
},
|
||||
"uid": {
|
||||
"type": "string",
|
||||
"example": "fe1xejlha91xce"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7487,6 +7491,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string",
|
||||
"example": "fe1xejlha91xce"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
|
||||
@@ -20434,6 +20434,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 0
|
||||
},
|
||||
"uid": {
|
||||
"type": "string",
|
||||
"example": "fe1xejlha91xce"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -20500,6 +20504,10 @@
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string",
|
||||
"example": "fe1xejlha91xce"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
|
||||
@@ -22,11 +22,12 @@ const createServiceAccount = async (sa: ServiceAccountDTO) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const updateServiceAccount = async (id: number, sa: ServiceAccountDTO) =>
|
||||
getBackendSrv().patch(`/api/serviceaccounts/${id}`, sa);
|
||||
const updateServiceAccount = async (uid: string, sa: ServiceAccountDTO) =>
|
||||
getBackendSrv().patch(`/api/serviceaccounts/${uid}`, sa);
|
||||
|
||||
const defaultServiceAccount = {
|
||||
id: 0,
|
||||
uid: '',
|
||||
orgId: contextSrv.user.orgId,
|
||||
role: contextSrv.licensedAccessControlEnabled() ? OrgRole.None : OrgRole.Viewer,
|
||||
tokens: 0,
|
||||
@@ -81,6 +82,7 @@ export const ServiceAccountCreatePage = ({}: Props): JSX.Element => {
|
||||
const newAccount: ServiceAccountCreateApiResponse = {
|
||||
avatarUrl: response.avatarUrl,
|
||||
id: response.id,
|
||||
uid: response.uid,
|
||||
isDisabled: response.isDisabled,
|
||||
login: response.login,
|
||||
name: response.name,
|
||||
@@ -88,7 +90,7 @@ export const ServiceAccountCreatePage = ({}: Props): JSX.Element => {
|
||||
role: response.role,
|
||||
tokens: response.tokens,
|
||||
};
|
||||
await updateServiceAccount(response.id, data);
|
||||
await updateServiceAccount(newAccount.uid, data);
|
||||
if (
|
||||
contextSrv.licensedAccessControlEnabled() &&
|
||||
contextSrv.hasPermission(AccessControlAction.ActionUserRolesAdd) &&
|
||||
@@ -99,7 +101,7 @@ export const ServiceAccountCreatePage = ({}: Props): JSX.Element => {
|
||||
} catch (e) {
|
||||
console.error(e); // TODO: handle error
|
||||
}
|
||||
locationService.push(`/org/serviceaccounts/${response.id}`);
|
||||
locationService.push(`/org/serviceaccounts/${response.uid}`);
|
||||
},
|
||||
[serviceAccount.role, pendingRoles]
|
||||
);
|
||||
|
||||
@@ -61,6 +61,7 @@ const setup = (propOverrides: Partial<Props>) => {
|
||||
|
||||
const getDefaultServiceAccount = (): ServiceAccountDTO => ({
|
||||
id: 42,
|
||||
uid: 'aaaaa',
|
||||
name: 'Data source scavenger',
|
||||
login: 'sa-data-source-scavenger',
|
||||
orgId: 1,
|
||||
@@ -164,6 +165,6 @@ describe('ServiceAccountPage tests', () => {
|
||||
await userEvent.click(screen.getByLabelText(/Delete service account token/));
|
||||
await user.click(screen.getByRole('button', { name: /^Delete$/ }));
|
||||
|
||||
expect(deleteServiceAccountTokenMock).toHaveBeenCalledWith(42, 142);
|
||||
expect(deleteServiceAccountTokenMock).toHaveBeenCalledWith('aaaaa', 142);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -68,7 +68,6 @@ export const ServiceAccountPageUnconnected = ({
|
||||
const [isDisableModalOpen, setIsDisableModalOpen] = useState(false);
|
||||
const { id = '' } = useParams();
|
||||
|
||||
const serviceAccountId = parseInt(id, 10);
|
||||
const tokenActionsDisabled =
|
||||
serviceAccount.isDisabled ||
|
||||
serviceAccount.isExternal ||
|
||||
@@ -87,12 +86,12 @@ export const ServiceAccountPageUnconnected = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadServiceAccount(serviceAccountId);
|
||||
loadServiceAccountTokens(serviceAccountId);
|
||||
loadServiceAccount(id);
|
||||
loadServiceAccountTokens(id);
|
||||
if (contextSrv.licensedAccessControlEnabled()) {
|
||||
fetchACOptions();
|
||||
}
|
||||
}, [loadServiceAccount, loadServiceAccountTokens, serviceAccountId]);
|
||||
}, [loadServiceAccount, loadServiceAccountTokens, id]);
|
||||
|
||||
const onProfileChange = (serviceAccount: ServiceAccountDTO) => {
|
||||
updateServiceAccount(serviceAccount);
|
||||
@@ -107,7 +106,7 @@ export const ServiceAccountPageUnconnected = ({
|
||||
};
|
||||
|
||||
const handleServiceAccountDelete = () => {
|
||||
deleteServiceAccount(serviceAccount.id);
|
||||
deleteServiceAccount(serviceAccount.uid);
|
||||
};
|
||||
|
||||
const handleServiceAccountDisable = () => {
|
||||
@@ -120,11 +119,11 @@ export const ServiceAccountPageUnconnected = ({
|
||||
};
|
||||
|
||||
const onDeleteServiceAccountToken = (key: ApiKey) => {
|
||||
deleteServiceAccountToken(serviceAccount?.id, key.id!);
|
||||
deleteServiceAccountToken(serviceAccount?.uid, key.id!);
|
||||
};
|
||||
|
||||
const onCreateToken = (token: ServiceAccountToken) => {
|
||||
createServiceAccountToken(serviceAccount?.id, token, setNewToken);
|
||||
createServiceAccountToken(serviceAccount?.uid, token, setNewToken);
|
||||
};
|
||||
|
||||
const onTokenModalClose = () => {
|
||||
|
||||
@@ -122,7 +122,7 @@ const getCellContent = (
|
||||
if (isLoading) {
|
||||
return columnName === 'avatarUrl' ? <Skeleton circle width={24} height={24} /> : <Skeleton width={100} />;
|
||||
}
|
||||
const href = `/org/serviceaccounts/${original.id}`;
|
||||
const href = `/org/serviceaccounts/${original.uid}`;
|
||||
const ariaLabel = `Edit service account's ${name} details`;
|
||||
switch (columnName) {
|
||||
case 'avatarUrl':
|
||||
|
||||
@@ -65,6 +65,7 @@ const setup = (propOverrides: Partial<Props>) => {
|
||||
|
||||
const getDefaultServiceAccount: () => ServiceAccountDTO = () => ({
|
||||
id: 42,
|
||||
uid: 'aaaaa',
|
||||
name: 'Data source scavenger',
|
||||
login: 'sa-data-source-scavenger',
|
||||
orgId: 1,
|
||||
@@ -155,6 +156,6 @@ describe('ServiceAccountsListPage tests', () => {
|
||||
await user.click(screen.getByLabelText(`Delete service account ${getDefaultServiceAccount().name}`));
|
||||
await user.click(screen.getByRole('button', { name: 'Delete' }));
|
||||
|
||||
expect(deleteServiceAccountMock).toHaveBeenCalledWith(42);
|
||||
expect(deleteServiceAccountMock).toHaveBeenCalledWith('aaaaa');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,7 +112,7 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
|
||||
const onServiceAccountRemove = async () => {
|
||||
if (currentServiceAccount) {
|
||||
deleteServiceAccount(currentServiceAccount.id);
|
||||
deleteServiceAccount(currentServiceAccount.uid);
|
||||
}
|
||||
onRemoveModalClose();
|
||||
};
|
||||
@@ -140,7 +140,7 @@ export const ServiceAccountsListPageUnconnected = ({
|
||||
|
||||
const onTokenCreate = async (token: ServiceAccountToken) => {
|
||||
if (currentServiceAccount) {
|
||||
createServiceAccountToken(currentServiceAccount.id, token, setNewToken);
|
||||
createServiceAccountToken(currentServiceAccount.uid, token, setNewToken);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -84,27 +84,27 @@ const fetchServiceAccountsWithDebounce = debounce((dispatch) => dispatch(fetchSe
|
||||
|
||||
export function updateServiceAccount(serviceAccount: ServiceAccountDTO): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().patch(`${BASE_URL}/${serviceAccount.id}?accesscontrol=true`, {
|
||||
await getBackendSrv().patch(`${BASE_URL}/${serviceAccount.uid}?accesscontrol=true`, {
|
||||
...serviceAccount,
|
||||
});
|
||||
dispatch(fetchServiceAccounts());
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteServiceAccount(serviceAccountId: number): ThunkResult<void> {
|
||||
export function deleteServiceAccount(serviceAccountUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().delete(`${BASE_URL}/${serviceAccountId}`);
|
||||
await getBackendSrv().delete(`${BASE_URL}/${serviceAccountUid}`);
|
||||
dispatch(fetchServiceAccounts());
|
||||
};
|
||||
}
|
||||
|
||||
export function createServiceAccountToken(
|
||||
saID: number,
|
||||
saUid: string,
|
||||
token: ServiceAccountToken,
|
||||
onTokenCreated: (key: string) => void
|
||||
): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
const result = await getBackendSrv().post(`${BASE_URL}/${saID}/tokens`, token);
|
||||
const result = await getBackendSrv().post(`${BASE_URL}/${saUid}/tokens`, token);
|
||||
onTokenCreated(result.key);
|
||||
dispatch(fetchServiceAccounts());
|
||||
};
|
||||
|
||||
@@ -13,11 +13,11 @@ import {
|
||||
|
||||
const BASE_URL = `/api/serviceaccounts`;
|
||||
|
||||
export function loadServiceAccount(saID: number): ThunkResult<void> {
|
||||
export function loadServiceAccount(saUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
dispatch(serviceAccountFetchBegin());
|
||||
try {
|
||||
const response = await getBackendSrv().get(`${BASE_URL}/${saID}`, accessControlQueryParam());
|
||||
const response = await getBackendSrv().get(`${BASE_URL}/${saUid}`, accessControlQueryParam());
|
||||
dispatch(serviceAccountLoaded(response));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -29,43 +29,43 @@ export function loadServiceAccount(saID: number): ThunkResult<void> {
|
||||
|
||||
export function updateServiceAccount(serviceAccount: ServiceAccountDTO): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().patch(`${BASE_URL}/${serviceAccount.id}?accesscontrol=true`, {
|
||||
await getBackendSrv().patch(`${BASE_URL}/${serviceAccount.uid}?accesscontrol=true`, {
|
||||
...serviceAccount,
|
||||
});
|
||||
dispatch(loadServiceAccount(serviceAccount.id));
|
||||
dispatch(loadServiceAccount(serviceAccount.uid));
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteServiceAccount(serviceAccountId: number): ThunkResult<void> {
|
||||
export function deleteServiceAccount(serviceAccountUid: string): ThunkResult<void> {
|
||||
return async () => {
|
||||
await getBackendSrv().delete(`${BASE_URL}/${serviceAccountId}`);
|
||||
await getBackendSrv().delete(`${BASE_URL}/${serviceAccountUid}`);
|
||||
locationService.push('/org/serviceaccounts');
|
||||
};
|
||||
}
|
||||
|
||||
export function createServiceAccountToken(
|
||||
saID: number,
|
||||
saUid: string,
|
||||
token: ServiceAccountToken,
|
||||
onTokenCreated: (key: string) => void
|
||||
): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
const result = await getBackendSrv().post(`${BASE_URL}/${saID}/tokens`, token);
|
||||
const result = await getBackendSrv().post(`${BASE_URL}/${saUid}/tokens`, token);
|
||||
onTokenCreated(result.key);
|
||||
dispatch(loadServiceAccountTokens(saID));
|
||||
dispatch(loadServiceAccountTokens(saUid));
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteServiceAccountToken(saID: number, id: number): ThunkResult<void> {
|
||||
export function deleteServiceAccountToken(saUid: string, id: number): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
await getBackendSrv().delete(`${BASE_URL}/${saID}/tokens/${id}`);
|
||||
dispatch(loadServiceAccountTokens(saID));
|
||||
await getBackendSrv().delete(`${BASE_URL}/${saUid}/tokens/${id}`);
|
||||
dispatch(loadServiceAccountTokens(saUid));
|
||||
};
|
||||
}
|
||||
|
||||
export function loadServiceAccountTokens(saID: number): ThunkResult<void> {
|
||||
export function loadServiceAccountTokens(saUid: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
const response = await getBackendSrv().get(`${BASE_URL}/${saID}/tokens`);
|
||||
const response = await getBackendSrv().get(`${BASE_URL}/${saUid}/tokens`);
|
||||
dispatch(serviceAccountTokensLoaded(response));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ServiceAccount {
|
||||
|
||||
export interface ServiceAccountDTO extends WithAccessControlMetadata {
|
||||
id: number;
|
||||
uid: string;
|
||||
orgId: number;
|
||||
tokens: number;
|
||||
name: string;
|
||||
@@ -44,6 +45,7 @@ export interface ServiceAccountDTO extends WithAccessControlMetadata {
|
||||
export interface ServiceAccountCreateApiResponse {
|
||||
avatarUrl?: string;
|
||||
id: number;
|
||||
uid: string;
|
||||
isDisabled: boolean;
|
||||
login: string;
|
||||
name: string;
|
||||
|
||||
@@ -10394,6 +10394,10 @@
|
||||
"example": 0,
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"uid": {
|
||||
"example": "fe1xejlha91xce",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -10460,6 +10464,10 @@
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"uid": {
|
||||
"example": "fe1xejlha91xce",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"example": "2022-03-21T14:35:33Z",
|
||||
"format": "date-time",
|
||||
|
||||
Reference in New Issue
Block a user