Auth: Service account store refactor (#58961)

* refactor: renaming of files from database to store

* refactor: make service account store private

- moves store interface to manager package
- adds an interface to the ProvideAPI constructor
- refactors tests to use the store when necessary
- adds mocks for the new interface implementations in the tests package

* wip

* refactor: make fakestore in service

* wip

* wip

* wip

* working tests

* trailing whitespaces

* Update pkg/services/serviceaccounts/api/api.go

* Update pkg/services/serviceaccounts/tests/common.go

* Update pkg/services/serviceaccounts/tests/common.go

* refactor: doc string for retriever

* fix import unused

* remove: serviceaccount from featuretoggle

* added: back legacy serviceaccounts feature toggle

* added: docs

* refactor: make query for the SearchQuery

* add: validation of service input fields

* add validation
This commit is contained in:
Eric Leijonmarck
2022-12-13 14:56:10 +01:00
committed by GitHub
parent 13adaddfaa
commit 371d7850a5
19 changed files with 692 additions and 267 deletions

View File

@@ -101,7 +101,6 @@ import (
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
secretsMigrator "github.com/grafana/grafana/pkg/services/secrets/migrator" secretsMigrator "github.com/grafana/grafana/pkg/services/secrets/migrator"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
serviceaccountsmanager "github.com/grafana/grafana/pkg/services/serviceaccounts/manager" serviceaccountsmanager "github.com/grafana/grafana/pkg/services/serviceaccounts/manager"
"github.com/grafana/grafana/pkg/services/shorturls" "github.com/grafana/grafana/pkg/services/shorturls"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
@@ -243,8 +242,6 @@ var wireSet = wire.NewSet(
pluginSettings.ProvideService, pluginSettings.ProvideService,
wire.Bind(new(pluginsettings.Service), new(*pluginSettings.Service)), wire.Bind(new(pluginsettings.Service), new(*pluginSettings.Service)),
alerting.ProvideService, alerting.ProvideService,
database.ProvideServiceAccountsStore,
wire.Bind(new(serviceaccounts.Store), new(*database.ServiceAccountsStoreImpl)),
ossaccesscontrol.ProvideServiceAccountPermissions, ossaccesscontrol.ProvideServiceAccountPermissions,
wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)),
serviceaccountsmanager.ProvideServiceAccountsService, serviceaccountsmanager.ProvideServiceAccountsService,

View File

@@ -111,8 +111,8 @@ import (
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
secretsMigrator "github.com/grafana/grafana/pkg/services/secrets/migrator" secretsMigrator "github.com/grafana/grafana/pkg/services/secrets/migrator"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
serviceaccountsmanager "github.com/grafana/grafana/pkg/services/serviceaccounts/manager" serviceaccountsmanager "github.com/grafana/grafana/pkg/services/serviceaccounts/manager"
serviceaccountsretriever "github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
"github.com/grafana/grafana/pkg/services/shorturls" "github.com/grafana/grafana/pkg/services/shorturls"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
@@ -271,8 +271,8 @@ var wireBasicSet = wire.NewSet(
pluginSettings.ProvideService, pluginSettings.ProvideService,
wire.Bind(new(pluginsettings.Service), new(*pluginSettings.Service)), wire.Bind(new(pluginsettings.Service), new(*pluginSettings.Service)),
alerting.ProvideService, alerting.ProvideService,
database.ProvideServiceAccountsStore, serviceaccountsretriever.ProvideService,
wire.Bind(new(serviceaccounts.Store), new(*database.ServiceAccountsStoreImpl)), wire.Bind(new(serviceaccountsretriever.ServiceAccountRetriever), new(*serviceaccountsretriever.Service)),
ossaccesscontrol.ProvideServiceAccountPermissions, ossaccesscontrol.ProvideServiceAccountPermissions,
wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)),
serviceaccountsmanager.ProvideServiceAccountsService, serviceaccountsmanager.ProvideServiceAccountsService,

View File

@@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
"github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/team/teamimpl" "github.com/grafana/grafana/pkg/services/team/teamimpl"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
@@ -283,7 +284,7 @@ type ServiceAccountPermissionsService struct {
func ProvideServiceAccountPermissions( func ProvideServiceAccountPermissions(
cfg *setting.Cfg, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl, cfg *setting.Cfg, router routing.RouteRegister, sql db.DB, ac accesscontrol.AccessControl,
license models.Licensing, serviceAccountStore serviceaccounts.Store, service accesscontrol.Service, license models.Licensing, serviceAccountRetrieverService *retriever.Service, service accesscontrol.Service,
teamService team.Service, userService user.Service, teamService team.Service, userService user.Service,
) (*ServiceAccountPermissionsService, error) { ) (*ServiceAccountPermissionsService, error) {
options := resourcepermissions.Options{ options := resourcepermissions.Options{
@@ -294,7 +295,7 @@ func ProvideServiceAccountPermissions(
if err != nil { if err != nil {
return err return err
} }
_, err = serviceAccountStore.RetrieveServiceAccount(ctx, orgID, id) _, err = serviceAccountRetrieverService.RetrieveServiceAccount(ctx, orgID, id)
return err return err
}, },
Assignments: resourcepermissions.Assignments{ Assignments: resourcepermissions.Assignments{

View File

@@ -1,6 +1,7 @@
package api package api
import ( import (
"context"
"errors" "errors"
"net/http" "net/http"
"strconv" "strconv"
@@ -12,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/database" "github.com/grafana/grafana/pkg/services/serviceaccounts/database"
@@ -22,22 +24,39 @@ import (
type ServiceAccountsAPI struct { type ServiceAccountsAPI struct {
cfg *setting.Cfg cfg *setting.Cfg
service serviceaccounts.Service service service
accesscontrol accesscontrol.AccessControl accesscontrol accesscontrol.AccessControl
accesscontrolService accesscontrol.Service accesscontrolService accesscontrol.Service
RouterRegister routing.RouteRegister RouterRegister routing.RouteRegister
store serviceaccounts.Store
log log.Logger log log.Logger
permissionService accesscontrol.ServiceAccountPermissionsService permissionService accesscontrol.ServiceAccountPermissionsService
} }
// Service implements the API exposed methods for service accounts.
type service interface {
CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error)
RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error)
UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error)
SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error)
ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error)
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*serviceaccounts.APIKeysMigrationStatus, error)
HideApiKeysTab(ctx context.Context, orgID int64) error
MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error
MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error
RevertApiKey(ctx context.Context, saId int64, keyId int64) error
// Service account tokens
AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) error
DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error
}
func NewServiceAccountsAPI( func NewServiceAccountsAPI(
cfg *setting.Cfg, cfg *setting.Cfg,
service serviceaccounts.Service, service service,
accesscontrol accesscontrol.AccessControl, accesscontrol accesscontrol.AccessControl,
accesscontrolService accesscontrol.Service, accesscontrolService accesscontrol.Service,
routerRegister routing.RouteRegister, routerRegister routing.RouteRegister,
store serviceaccounts.Store,
permissionService accesscontrol.ServiceAccountPermissionsService, permissionService accesscontrol.ServiceAccountPermissionsService,
) *ServiceAccountsAPI { ) *ServiceAccountsAPI {
return &ServiceAccountsAPI{ return &ServiceAccountsAPI{
@@ -46,7 +65,6 @@ func NewServiceAccountsAPI(
accesscontrol: accesscontrol, accesscontrol: accesscontrol,
accesscontrolService: accesscontrolService, accesscontrolService: accesscontrolService,
RouterRegister: routerRegister, RouterRegister: routerRegister,
store: store,
log: log.New("serviceaccounts.api"), log: log.New("serviceaccounts.api"),
permissionService: permissionService, permissionService: permissionService,
} }
@@ -116,7 +134,7 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *models.ReqContext) respon
} }
} }
serviceAccount, err := api.store.CreateServiceAccount(c.Req.Context(), c.OrgID, &cmd) serviceAccount, err := api.service.CreateServiceAccount(c.Req.Context(), c.OrgID, &cmd)
switch { switch {
case errors.Is(err, database.ErrServiceAccountAlreadyExists): case errors.Is(err, database.ErrServiceAccountAlreadyExists):
return response.Error(http.StatusBadRequest, "Failed to create service account", err) return response.Error(http.StatusBadRequest, "Failed to create service account", err)
@@ -159,7 +177,7 @@ func (api *ServiceAccountsAPI) RetrieveServiceAccount(ctx *models.ReqContext) re
return response.Error(http.StatusBadRequest, "Service Account ID is invalid", err) return response.Error(http.StatusBadRequest, "Service Account ID is invalid", err)
} }
serviceAccount, err := api.store.RetrieveServiceAccount(ctx.Req.Context(), ctx.OrgID, scopeID) serviceAccount, err := api.service.RetrieveServiceAccount(ctx.Req.Context(), ctx.OrgID, scopeID)
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound): case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound):
@@ -174,7 +192,7 @@ func (api *ServiceAccountsAPI) RetrieveServiceAccount(ctx *models.ReqContext) re
serviceAccount.AvatarUrl = dtos.GetGravatarUrlWithDefault("", serviceAccount.Name) serviceAccount.AvatarUrl = dtos.GetGravatarUrlWithDefault("", serviceAccount.Name)
serviceAccount.AccessControl = metadata[saIDString] serviceAccount.AccessControl = metadata[saIDString]
tokens, err := api.store.ListTokens(ctx.Req.Context(), &serviceaccounts.GetSATokensQuery{ tokens, err := api.service.ListTokens(ctx.Req.Context(), &serviceaccounts.GetSATokensQuery{
OrgID: &serviceAccount.OrgId, OrgID: &serviceAccount.OrgId,
ServiceAccountID: &serviceAccount.Id, ServiceAccountID: &serviceAccount.Id,
}) })
@@ -222,7 +240,7 @@ func (api *ServiceAccountsAPI) UpdateServiceAccount(c *models.ReqContext) respon
} }
} }
resp, err := api.store.UpdateServiceAccount(c.Req.Context(), c.OrgID, scopeID, &cmd) resp, err := api.service.UpdateServiceAccount(c.Req.Context(), c.OrgID, scopeID, &cmd)
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound): case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound):
@@ -312,7 +330,15 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *models.ReqC
if onlyDisabled { if onlyDisabled {
filter = serviceaccounts.FilterOnlyDisabled filter = serviceaccounts.FilterOnlyDisabled
} }
serviceAccountSearch, err := api.store.SearchOrgServiceAccounts(ctx, c.OrgID, c.Query("query"), filter, page, perPage, c.SignedInUser) q := serviceaccounts.SearchOrgServiceAccountsQuery{
OrgID: c.OrgID,
Query: c.Query("query"),
Page: page,
Limit: perPage,
Filter: filter,
SignedInUser: c.SignedInUser,
}
serviceAccountSearch, err := api.service.SearchOrgServiceAccounts(ctx, &q)
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to get service accounts for current organization", err) return response.Error(http.StatusInternalServerError, "Failed to get service accounts for current organization", err)
} }
@@ -326,7 +352,7 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *models.ReqC
saIDs[saIDString] = true saIDs[saIDString] = true
metadata := api.getAccessControlMetadata(c, map[string]bool{saIDString: true}) metadata := api.getAccessControlMetadata(c, map[string]bool{saIDString: true})
sa.AccessControl = metadata[strconv.FormatInt(sa.Id, 10)] sa.AccessControl = metadata[strconv.FormatInt(sa.Id, 10)]
tokens, err := api.store.ListTokens(ctx, &serviceaccounts.GetSATokensQuery{ tokens, err := api.service.ListTokens(ctx, &serviceaccounts.GetSATokensQuery{
OrgID: &sa.OrgId, ServiceAccountID: &sa.Id, OrgID: &sa.OrgId, ServiceAccountID: &sa.Id,
}) })
if err != nil { if err != nil {
@@ -340,7 +366,7 @@ func (api *ServiceAccountsAPI) SearchOrgServiceAccountsWithPaging(c *models.ReqC
// GET /api/serviceaccounts/migrationstatus // GET /api/serviceaccounts/migrationstatus
func (api *ServiceAccountsAPI) GetAPIKeysMigrationStatus(ctx *models.ReqContext) response.Response { func (api *ServiceAccountsAPI) GetAPIKeysMigrationStatus(ctx *models.ReqContext) response.Response {
upgradeStatus, err := api.store.GetAPIKeysMigrationStatus(ctx.Req.Context(), ctx.OrgID) upgradeStatus, err := api.service.GetAPIKeysMigrationStatus(ctx.Req.Context(), ctx.OrgID)
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "Internal server error", err) return response.Error(http.StatusInternalServerError, "Internal server error", err)
} }
@@ -349,7 +375,7 @@ func (api *ServiceAccountsAPI) GetAPIKeysMigrationStatus(ctx *models.ReqContext)
// POST /api/serviceaccounts/hideapikeys // POST /api/serviceaccounts/hideapikeys
func (api *ServiceAccountsAPI) HideApiKeysTab(ctx *models.ReqContext) response.Response { func (api *ServiceAccountsAPI) HideApiKeysTab(ctx *models.ReqContext) response.Response {
if err := api.store.HideApiKeysTab(ctx.Req.Context(), ctx.OrgID); err != nil { if err := api.service.HideApiKeysTab(ctx.Req.Context(), ctx.OrgID); err != nil {
return response.Error(http.StatusInternalServerError, "Internal server error", err) return response.Error(http.StatusInternalServerError, "Internal server error", err)
} }
return response.Success("API keys hidden") return response.Success("API keys hidden")
@@ -357,7 +383,7 @@ func (api *ServiceAccountsAPI) HideApiKeysTab(ctx *models.ReqContext) response.R
// POST /api/serviceaccounts/migrate // POST /api/serviceaccounts/migrate
func (api *ServiceAccountsAPI) MigrateApiKeysToServiceAccounts(ctx *models.ReqContext) response.Response { func (api *ServiceAccountsAPI) MigrateApiKeysToServiceAccounts(ctx *models.ReqContext) response.Response {
if err := api.store.MigrateApiKeysToServiceAccounts(ctx.Req.Context(), ctx.OrgID); err != nil { if err := api.service.MigrateApiKeysToServiceAccounts(ctx.Req.Context(), ctx.OrgID); err != nil {
return response.Error(http.StatusInternalServerError, "Internal server error", err) return response.Error(http.StatusInternalServerError, "Internal server error", err)
} }
@@ -371,7 +397,7 @@ func (api *ServiceAccountsAPI) ConvertToServiceAccount(ctx *models.ReqContext) r
return response.Error(http.StatusBadRequest, "Key ID is invalid", err) return response.Error(http.StatusBadRequest, "Key ID is invalid", err)
} }
if err := api.store.MigrateApiKey(ctx.Req.Context(), ctx.OrgID, keyId); err != nil { if err := api.service.MigrateApiKey(ctx.Req.Context(), ctx.OrgID, keyId); err != nil {
return response.Error(http.StatusInternalServerError, "Error converting API key", err) return response.Error(http.StatusInternalServerError, "Error converting API key", err)
} }
@@ -389,7 +415,7 @@ func (api *ServiceAccountsAPI) RevertApiKey(ctx *models.ReqContext) response.Res
return response.Error(http.StatusBadRequest, "service account ID is invalid", err) return response.Error(http.StatusBadRequest, "service account ID is invalid", err)
} }
if err := api.store.RevertApiKey(ctx.Req.Context(), serviceAccountId, keyId); err != nil { if err := api.service.RevertApiKey(ctx.Req.Context(), serviceAccountId, keyId); err != nil {
return response.Error(http.StatusInternalServerError, "error reverting to API key", err) return response.Error(http.StatusInternalServerError, "error reverting to API key", err)
} }
return response.Success("reverted service account to API key") return response.Success("reverted service account to API key")
@@ -464,7 +490,7 @@ type DeleteServiceAccountParams struct {
// swagger:response searchOrgServiceAccountsWithPagingResponse // swagger:response searchOrgServiceAccountsWithPagingResponse
type SearchOrgServiceAccountsWithPagingResponse struct { type SearchOrgServiceAccountsWithPagingResponse struct {
// in:body // in:body
Body *serviceaccounts.SearchServiceAccountsResult Body *serviceaccounts.SearchOrgServiceAccountsResult
} }
// swagger:response createServiceAccountResponse // swagger:response createServiceAccountResponse

View File

@@ -32,6 +32,7 @@ import (
"github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/database" "github.com/grafana/grafana/pkg/services/serviceaccounts/database"
"github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests" "github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/team/teamimpl" "github.com/grafana/grafana/pkg/services/team/teamimpl"
@@ -46,6 +47,13 @@ var (
serviceAccountIDPath = serviceAccountPath + "%v" serviceAccountIDPath = serviceAccountPath + "%v"
) )
// TODO:
// refactor this set of tests to make use of fakes for the ServiceAccountService
// all of the API tests are calling with all of the db store injections
// which is not ideal
// this is a bit of a hack to get the tests to pass until we refactor the tests
// to use fakes as in the user service tests
func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) { func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
store := db.InitTestDB(t) store := db.InitTestDB(t)
services := setupTestServices(t, store) services := setupTestServices(t, store)
@@ -162,7 +170,7 @@ func TestServiceAccountsAPI_CreateServiceAccount(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
serviceAccountRequestScenario(t, http.MethodPost, serviceAccountPath, testUser, func(httpmethod string, endpoint string, usr *tests.TestUser) { serviceAccountRequestScenario(t, http.MethodPost, serviceAccountPath, testUser, func(httpmethod string, endpoint string, usr *tests.TestUser) {
server, api := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store, services.SAStore) server, api := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store)
marshalled, err := json.Marshal(tc.body) marshalled, err := json.Marshal(tc.body)
require.NoError(t, err) require.NoError(t, err)
@@ -239,7 +247,7 @@ func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) {
} }
serviceAccountRequestScenario(t, http.MethodDelete, serviceAccountIDPath, &testcase.user, func(httpmethod string, endpoint string, user *tests.TestUser) { serviceAccountRequestScenario(t, http.MethodDelete, serviceAccountIDPath, &testcase.user, func(httpmethod string, endpoint string, user *tests.TestUser) {
createduser := tests.SetupUserServiceAccount(t, store, testcase.user) createduser := tests.SetupUserServiceAccount(t, store, testcase.user)
server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), testcase.acmock, store, services.SAStore) server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), testcase.acmock, store)
actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, fmt.Sprint(createduser.ID))).Code actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, fmt.Sprint(createduser.ID))).Code
require.Equal(t, testcase.expectedCode, actual) require.Equal(t, testcase.expectedCode, actual)
}) })
@@ -263,7 +271,7 @@ func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) {
} }
serviceAccountRequestScenario(t, http.MethodDelete, serviceAccountIDPath, &testcase.user, func(httpmethod string, endpoint string, user *tests.TestUser) { serviceAccountRequestScenario(t, http.MethodDelete, serviceAccountIDPath, &testcase.user, func(httpmethod string, endpoint string, user *tests.TestUser) {
createduser := tests.SetupUserServiceAccount(t, store, testcase.user) createduser := tests.SetupUserServiceAccount(t, store, testcase.user)
server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), testcase.acmock, store, services.SAStore) server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), testcase.acmock, store)
actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, createduser.ID)).Code actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, createduser.ID)).Code
require.Equal(t, testcase.expectedCode, actual) require.Equal(t, testcase.expectedCode, actual)
}) })
@@ -275,21 +283,29 @@ func serviceAccountRequestScenario(t *testing.T, httpMethod string, endpoint str
fn(httpMethod, endpoint, user) fn(httpMethod, endpoint, user)
} }
func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock, func setupTestServer(
t *testing.T,
svc *tests.ServiceAccountMock,
routerRegister routing.RouteRegister, routerRegister routing.RouteRegister,
acmock *accesscontrolmock.Mock, acmock *accesscontrolmock.Mock,
sqlStore db.DB, saStore serviceaccounts.Store) (*web.Mux, *ServiceAccountsAPI) { sqlStore db.DB,
) (*web.Mux, *ServiceAccountsAPI) {
cfg := setting.NewCfg() cfg := setting.NewCfg()
teamSvc := teamimpl.ProvideService(sqlStore, cfg) teamSvc := teamimpl.ProvideService(sqlStore, cfg)
orgSvc, err := orgimpl.ProvideService(sqlStore, cfg, quotatest.New(false, nil))
userSvc, err := userimpl.ProvideService(sqlStore, nil, cfg, teamimpl.ProvideService(sqlStore, cfg), nil, quotatest.New(false, nil))
require.NoError(t, err) require.NoError(t, err)
userSvc, err := userimpl.ProvideService(sqlStore, orgSvc, cfg, teamimpl.ProvideService(sqlStore, cfg), nil, quotatest.New(false, nil))
require.NoError(t, err)
// TODO: create fake for retriever to pass into the permissionservice
retrieverSvc := retriever.ProvideService(sqlStore, nil, nil, nil, nil)
saPermissionService, err := ossaccesscontrol.ProvideServiceAccountPermissions( saPermissionService, err := ossaccesscontrol.ProvideServiceAccountPermissions(
cfg, routing.NewRouteRegister(), sqlStore, acmock, &licensing.OSSLicensingService{}, saStore, acmock, teamSvc, userSvc) cfg, routing.NewRouteRegister(), sqlStore, acmock, &licensing.OSSLicensingService{}, retrieverSvc, acmock, teamSvc, userSvc)
require.NoError(t, err) require.NoError(t, err)
acService := actest.FakeService{} acService := actest.FakeService{}
a := NewServiceAccountsAPI(cfg, svc, acmock, acService, routerRegister, saStore, saPermissionService) a := NewServiceAccountsAPI(cfg, svc, acmock, acService, routerRegister, saPermissionService)
a.RegisterAPIEndpoints() a.RegisterAPIEndpoints()
a.cfg.ApiKeyMaxSecondsToLive = -1 // disable api key expiration a.cfg.ApiKeyMaxSecondsToLive = -1 // disable api key expiration
@@ -317,6 +333,7 @@ func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock,
func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) { func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) {
store := db.InitTestDB(t) store := db.InitTestDB(t)
services := setupTestServices(t, store) services := setupTestServices(t, store)
type testRetrieveSATestCase struct { type testRetrieveSATestCase struct {
desc string desc string
user *tests.TestUser user *tests.TestUser
@@ -380,7 +397,7 @@ func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) {
createdUser := tests.SetupUserServiceAccount(t, store, *tc.user) createdUser := tests.SetupUserServiceAccount(t, store, *tc.user)
scopeID = int(createdUser.ID) scopeID = int(createdUser.ID)
} }
server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store, services.SAStore) server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store)
actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, scopeID)) actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, scopeID))
@@ -406,6 +423,7 @@ func newString(s string) *string {
func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) { func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) {
store := db.InitTestDB(t) store := db.InitTestDB(t)
services := setupTestServices(t, store) services := setupTestServices(t, store)
type testUpdateSATestCase struct { type testUpdateSATestCase struct {
desc string desc string
user *tests.TestUser user *tests.TestUser
@@ -498,7 +516,7 @@ func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
server, saAPI := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store, services.SAStore) server, saAPI := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store)
scopeID := tc.Id scopeID := tc.Id
if tc.user != nil { if tc.user != nil {
createdUser := tests.SetupUserServiceAccount(t, store, *tc.user) createdUser := tests.SetupUserServiceAccount(t, store, *tc.user)
@@ -528,7 +546,7 @@ func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) {
assert.Equal(t, tc.user.Login, serviceAccountData["login"].(string)) assert.Equal(t, tc.user.Login, serviceAccountData["login"].(string))
// Ensure the user was updated in DB // Ensure the user was updated in DB
sa, err := saAPI.store.RetrieveServiceAccount(context.Background(), 1, int64(scopeID)) sa, err := saAPI.service.RetrieveServiceAccount(context.Background(), 1, int64(scopeID))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, *tc.body.Name, sa.Name) require.Equal(t, *tc.body.Name, sa.Name)
require.Equal(t, string(*tc.body.Role), sa.Role) require.Equal(t, string(*tc.body.Role), sa.Role)
@@ -540,7 +558,6 @@ func TestServiceAccountsAPI_UpdateServiceAccount(t *testing.T) {
type services struct { type services struct {
OrgService org.Service OrgService org.Service
UserService user.Service UserService user.Service
SAStore serviceaccounts.Store
SAService tests.ServiceAccountMock SAService tests.ServiceAccountMock
APIKeyService apikey.Service APIKeyService apikey.Service
} }
@@ -555,13 +572,13 @@ func setupTestServices(t *testing.T, db *sqlstore.SQLStore) services {
require.NoError(t, err) require.NoError(t, err)
userSvc, err := userimpl.ProvideService(db, orgService, db.Cfg, nil, nil, quotaService) userSvc, err := userimpl.ProvideService(db, orgService, db.Cfg, nil, nil, quotaService)
require.NoError(t, err) require.NoError(t, err)
saStore := database.ProvideServiceAccountsStore(db, apiKeyService, kvStore, userSvc, orgService)
svcmock := tests.ServiceAccountMock{} saStore := database.ProvideServiceAccountsStore(nil, db, apiKeyService, kvStore, userSvc, orgService)
svcmock := tests.ServiceAccountMock{Store: saStore, Calls: tests.Calls{}, Stats: nil, SecretScanEnabled: false}
return services{ return services{
OrgService: orgService, OrgService: orgService,
UserService: userSvc, UserService: userSvc,
SAStore: saStore,
SAService: svcmock, SAService: svcmock,
APIKeyService: apiKeyService, APIKeyService: apiKeyService,
} }

View File

@@ -72,7 +72,7 @@ func (api *ServiceAccountsAPI) ListTokens(ctx *models.ReqContext) response.Respo
return response.Error(http.StatusBadRequest, "Service Account ID is invalid", err) return response.Error(http.StatusBadRequest, "Service Account ID is invalid", err)
} }
saTokens, err := api.store.ListTokens(ctx.Req.Context(), &serviceaccounts.GetSATokensQuery{ saTokens, err := api.service.ListTokens(ctx.Req.Context(), &serviceaccounts.GetSATokensQuery{
OrgID: &ctx.OrgID, OrgID: &ctx.OrgID,
ServiceAccountID: &saID, ServiceAccountID: &saID,
}) })
@@ -134,7 +134,7 @@ func (api *ServiceAccountsAPI) CreateToken(c *models.ReqContext) response.Respon
} }
// confirm service account exists // confirm service account exists
if _, err := api.store.RetrieveServiceAccount(c.Req.Context(), c.OrgID, saID); err != nil { if _, err := api.service.RetrieveServiceAccount(c.Req.Context(), c.OrgID, saID); err != nil {
switch { switch {
case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound): case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound):
return response.Error(http.StatusNotFound, "Failed to retrieve service account", err) return response.Error(http.StatusNotFound, "Failed to retrieve service account", err)
@@ -175,7 +175,7 @@ func (api *ServiceAccountsAPI) CreateToken(c *models.ReqContext) response.Respon
cmd.Key = newKeyInfo.HashedKey cmd.Key = newKeyInfo.HashedKey
if err := api.store.AddServiceAccountToken(c.Req.Context(), saID, &cmd); err != nil { if err := api.service.AddServiceAccountToken(c.Req.Context(), saID, &cmd); err != nil {
if errors.Is(err, database.ErrInvalidTokenExpiration) { if errors.Is(err, database.ErrInvalidTokenExpiration) {
return response.Error(http.StatusBadRequest, err.Error(), nil) return response.Error(http.StatusBadRequest, err.Error(), nil)
} }
@@ -217,7 +217,7 @@ func (api *ServiceAccountsAPI) DeleteToken(c *models.ReqContext) response.Respon
} }
// confirm service account exists // confirm service account exists
if _, err := api.store.RetrieveServiceAccount(c.Req.Context(), c.OrgID, saID); err != nil { if _, err := api.service.RetrieveServiceAccount(c.Req.Context(), c.OrgID, saID); err != nil {
switch { switch {
case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound): case errors.Is(err, serviceaccounts.ErrServiceAccountNotFound):
return response.Error(http.StatusNotFound, "Failed to retrieve service account", err) return response.Error(http.StatusNotFound, "Failed to retrieve service account", err)
@@ -231,7 +231,7 @@ func (api *ServiceAccountsAPI) DeleteToken(c *models.ReqContext) response.Respon
return response.Error(http.StatusBadRequest, "Token ID is invalid", err) return response.Error(http.StatusBadRequest, "Token ID is invalid", err)
} }
if err = api.store.DeleteServiceAccountToken(c.Req.Context(), c.OrgID, saID, tokenID); err != nil { if err = api.service.DeleteServiceAccountToken(c.Req.Context(), c.OrgID, saID, tokenID); err != nil {
status := http.StatusNotFound status := http.StatusNotFound
if err != nil && !errors.Is(err, apikey.ErrNotFound) { if err != nil && !errors.Is(err, apikey.ErrNotFound) {
status = http.StatusInternalServerError status = http.StatusInternalServerError

View File

@@ -32,7 +32,7 @@ const (
serviceaccountIDTokensDetailPath = "/api/serviceaccounts/%v/tokens/%v" // #nosec G101 serviceaccountIDTokensDetailPath = "/api/serviceaccounts/%v/tokens/%v" // #nosec G101
) )
func createTokenforSA(t *testing.T, store serviceaccounts.Store, keyName string, orgID int64, saID int64, secondsToLive int64) *apikey.APIKey { func createTokenforSA(t *testing.T, service serviceaccounts.Service, keyName string, orgID int64, saID int64, secondsToLive int64) *apikey.APIKey {
key, err := apikeygen.New(orgID, keyName) key, err := apikeygen.New(orgID, keyName)
require.NoError(t, err) require.NoError(t, err)
@@ -44,7 +44,7 @@ func createTokenforSA(t *testing.T, store serviceaccounts.Store, keyName string,
Result: &apikey.APIKey{}, Result: &apikey.APIKey{},
} }
err = store.AddServiceAccountToken(context.Background(), saID, &cmd) err = service.AddServiceAccountToken(context.Background(), saID, &cmd)
require.NoError(t, err) require.NoError(t, err)
return cmd.Result return cmd.Result
} }
@@ -131,7 +131,7 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) {
bodyString = string(b) bodyString = string(b)
} }
server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store, services.SAStore) server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store)
actual := requestResponse(server, http.MethodPost, endpoint, strings.NewReader(bodyString)) actual := requestResponse(server, http.MethodPost, endpoint, strings.NewReader(bodyString))
actualCode := actual.Code actualCode := actual.Code
@@ -166,6 +166,7 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) {
func TestServiceAccountsAPI_DeleteToken(t *testing.T) { func TestServiceAccountsAPI_DeleteToken(t *testing.T) {
store := db.InitTestDB(t) store := db.InitTestDB(t)
services := setupTestServices(t, store) services := setupTestServices(t, store)
sa := tests.SetupUserServiceAccount(t, store, tests.TestUser{Login: "sa", IsServiceAccount: true}) sa := tests.SetupUserServiceAccount(t, store, tests.TestUser{Login: "sa", IsServiceAccount: true})
type testCreateSAToken struct { type testCreateSAToken struct {
@@ -225,11 +226,11 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
token := createTokenforSA(t, services.SAStore, tc.keyName, sa.OrgID, sa.ID, 1) token := createTokenforSA(t, &services.SAService, tc.keyName, sa.OrgID, sa.ID, 1)
endpoint := fmt.Sprintf(serviceaccountIDTokensDetailPath, sa.ID, token.Id) endpoint := fmt.Sprintf(serviceaccountIDTokensDetailPath, sa.ID, token.Id)
bodyString := "" bodyString := ""
server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store, services.SAStore) server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store)
actual := requestResponse(server, http.MethodDelete, endpoint, strings.NewReader(bodyString)) actual := requestResponse(server, http.MethodDelete, endpoint, strings.NewReader(bodyString))
actualCode := actual.Code actualCode := actual.Code
@@ -249,20 +250,11 @@ func TestServiceAccountsAPI_DeleteToken(t *testing.T) {
} }
} }
type saStoreMockTokens struct {
serviceaccounts.Store
saAPIKeys []apikey.APIKey
}
func (s *saStoreMockTokens) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) {
return s.saAPIKeys, nil
}
func TestServiceAccountsAPI_ListTokens(t *testing.T) { func TestServiceAccountsAPI_ListTokens(t *testing.T) {
store := db.InitTestDB(t) store := db.InitTestDB(t)
svcmock := tests.ServiceAccountMock{} services := setupTestServices(t, store)
sa := tests.SetupUserServiceAccount(t, store, tests.TestUser{Login: "sa", IsServiceAccount: true})
sa := tests.SetupUserServiceAccount(t, store, tests.TestUser{Login: "sa", IsServiceAccount: true})
type testCreateSAToken struct { type testCreateSAToken struct {
desc string desc string
tokens []apikey.APIKey tokens []apikey.APIKey
@@ -351,7 +343,8 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) { t.Run(tc.desc, func(t *testing.T) {
endpoint := fmt.Sprintf(serviceAccountIDPath+"/tokens", sa.ID) endpoint := fmt.Sprintf(serviceAccountIDPath+"/tokens", sa.ID)
server, _ := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store, &saStoreMockTokens{saAPIKeys: tc.tokens}) services.SAService.ExpectedTokens = tc.tokens
server, _ := setupTestServer(t, &services.SAService, routing.NewRouteRegister(), tc.acmock, store)
actual := requestResponse(server, http.MethodGet, endpoint, http.NoBody) actual := requestResponse(server, http.MethodGet, endpoint, http.NoBody)
actualCode := actual.Code actualCode := actual.Code

View File

@@ -18,20 +18,23 @@ import (
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
) )
type ServiceAccountsStoreImpl struct { type ServiceAccountsStoreImpl struct {
sqlStore *sqlstore.SQLStore cfg *setting.Cfg
sqlStore db.DB
apiKeyService apikey.Service apiKeyService apikey.Service
kvStore kvstore.KVStore kvStore kvstore.KVStore
log log.Logger log log.Logger
userService user.Service
orgService org.Service orgService org.Service
userService user.Service
} }
func ProvideServiceAccountsStore(store *sqlstore.SQLStore, apiKeyService apikey.Service, func ProvideServiceAccountsStore(cfg *setting.Cfg, store db.DB, apiKeyService apikey.Service,
kvStore kvstore.KVStore, userService user.Service, orgService org.Service) *ServiceAccountsStoreImpl { kvStore kvstore.KVStore, userService user.Service, orgService org.Service) *ServiceAccountsStoreImpl {
return &ServiceAccountsStoreImpl{ return &ServiceAccountsStoreImpl{
cfg: cfg,
sqlStore: store, sqlStore: store,
apiKeyService: apiKeyService, apiKeyService: apiKeyService,
kvStore: kvStore, kvStore: kvStore,
@@ -101,9 +104,11 @@ func (s *ServiceAccountsStoreImpl) CreateServiceAccount(ctx context.Context, org
} }
// UpdateServiceAccount updates service account // UpdateServiceAccount updates service account
func (s *ServiceAccountsStoreImpl) UpdateServiceAccount(ctx context.Context, func (s *ServiceAccountsStoreImpl) UpdateServiceAccount(
ctx context.Context,
orgId, serviceAccountId int64, orgId, serviceAccountId int64,
saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) { saForm *serviceaccounts.UpdateServiceAccountForm,
) (*serviceaccounts.ServiceAccountProfileDTO, error) {
updatedUser := &serviceaccounts.ServiceAccountProfileDTO{} updatedUser := &serviceaccounts.ServiceAccountProfileDTO{}
err := s.sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error { err := s.sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
@@ -175,7 +180,7 @@ func (s *ServiceAccountsStoreImpl) DeleteServiceAccount(ctx context.Context, org
func (s *ServiceAccountsStoreImpl) deleteServiceAccount(sess *db.Session, orgId, serviceAccountId int64) error { func (s *ServiceAccountsStoreImpl) deleteServiceAccount(sess *db.Session, orgId, serviceAccountId int64) error {
user := user.User{} user := user.User{}
has, err := sess.Where(`org_id = ? and id = ? and is_service_account = ?`, has, err := sess.Where(`org_id = ? and id = ? and is_service_account = ?`,
orgId, serviceAccountId, s.sqlStore.Dialect.BooleanStr(true)).Get(&user) orgId, serviceAccountId, s.sqlStore.GetDialect().BooleanStr(true)).Get(&user)
if err != nil { if err != nil {
return err return err
} }
@@ -197,8 +202,8 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, o
err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error { err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
sess := dbSession.Table("org_user") sess := dbSession.Table("org_user")
sess.Join("INNER", s.sqlStore.Dialect.Quote("user"), sess.Join("INNER", s.sqlStore.GetDialect().Quote("user"),
fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.Dialect.Quote("user"))) fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.GetDialect().Quote("user")))
whereConditions := make([]string, 0, 3) whereConditions := make([]string, 0, 3)
whereParams := make([]interface{}, 0) whereParams := make([]interface{}, 0)
@@ -211,8 +216,8 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccount(ctx context.Context, o
whereConditions = append(whereConditions, whereConditions = append(whereConditions,
fmt.Sprintf("%s.is_service_account = %s", fmt.Sprintf("%s.is_service_account = %s",
s.sqlStore.Dialect.Quote("user"), s.sqlStore.GetDialect().Quote("user"),
s.sqlStore.Dialect.BooleanStr(true))) s.sqlStore.GetDialect().BooleanStr(true)))
sess.Where(strings.Join(whereConditions, " AND "), whereParams...) sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
@@ -250,12 +255,12 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccountIdByName(ctx context.Co
whereConditions := []string{ whereConditions := []string{
fmt.Sprintf("%s.name = ?", fmt.Sprintf("%s.name = ?",
s.sqlStore.Dialect.Quote("user")), s.sqlStore.GetDialect().Quote("user")),
fmt.Sprintf("%s.org_id = ?", fmt.Sprintf("%s.org_id = ?",
s.sqlStore.Dialect.Quote("user")), s.sqlStore.GetDialect().Quote("user")),
fmt.Sprintf("%s.is_service_account = %s", fmt.Sprintf("%s.is_service_account = %s",
s.sqlStore.Dialect.Quote("user"), s.sqlStore.GetDialect().Quote("user"),
s.sqlStore.Dialect.BooleanStr(true)), s.sqlStore.GetDialect().BooleanStr(true)),
} }
whereParams := []interface{}{name, orgId} whereParams := []interface{}{name, orgId}
@@ -281,34 +286,31 @@ func (s *ServiceAccountsStoreImpl) RetrieveServiceAccountIdByName(ctx context.Co
return serviceAccount.Id, nil return serviceAccount.Id, nil
} }
func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts( func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) {
ctx context.Context, orgId int64, query string, filter serviceaccounts.ServiceAccountFilter, page int, limit int, searchResult := &serviceaccounts.SearchOrgServiceAccountsResult{
signedInUser *user.SignedInUser,
) (*serviceaccounts.SearchServiceAccountsResult, error) {
searchResult := &serviceaccounts.SearchServiceAccountsResult{
TotalCount: 0, TotalCount: 0,
ServiceAccounts: make([]*serviceaccounts.ServiceAccountDTO, 0), ServiceAccounts: make([]*serviceaccounts.ServiceAccountDTO, 0),
Page: page, Page: query.Page,
PerPage: limit, PerPage: query.Limit,
} }
err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error { err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
sess := dbSession.Table("org_user") sess := dbSession.Table("org_user")
sess.Join("INNER", s.sqlStore.Dialect.Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.Dialect.Quote("user"))) sess.Join("INNER", s.sqlStore.GetDialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.GetDialect().Quote("user")))
whereConditions := make([]string, 0) whereConditions := make([]string, 0)
whereParams := make([]interface{}, 0) whereParams := make([]interface{}, 0)
whereConditions = append(whereConditions, "org_user.org_id = ?") whereConditions = append(whereConditions, "org_user.org_id = ?")
whereParams = append(whereParams, orgId) whereParams = append(whereParams, query.OrgID)
whereConditions = append(whereConditions, whereConditions = append(whereConditions,
fmt.Sprintf("%s.is_service_account = %s", fmt.Sprintf("%s.is_service_account = %s",
s.sqlStore.Dialect.Quote("user"), s.sqlStore.GetDialect().Quote("user"),
s.sqlStore.Dialect.BooleanStr(true))) s.sqlStore.GetDialect().BooleanStr(true)))
if !accesscontrol.IsDisabled(s.sqlStore.Cfg) { if !accesscontrol.IsDisabled(s.cfg) {
acFilter, err := accesscontrol.Filter(signedInUser, "org_user.user_id", "serviceaccounts:id:", serviceaccounts.ActionRead) acFilter, err := accesscontrol.Filter(query.SignedInUser, "org_user.user_id", "serviceaccounts:id:", serviceaccounts.ActionRead)
if err != nil { if err != nil {
return err return err
} }
@@ -316,13 +318,13 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(
whereParams = append(whereParams, acFilter.Args...) whereParams = append(whereParams, acFilter.Args...)
} }
if query != "" { if query.Query != "" {
queryWithWildcards := "%" + query + "%" queryWithWildcards := "%" + query.Query + "%"
whereConditions = append(whereConditions, "(email "+s.sqlStore.Dialect.LikeStr()+" ? OR name "+s.sqlStore.Dialect.LikeStr()+" ? OR login "+s.sqlStore.Dialect.LikeStr()+" ?)") whereConditions = append(whereConditions, "(email "+s.sqlStore.GetDialect().LikeStr()+" ? OR name "+s.sqlStore.GetDialect().LikeStr()+" ? OR login "+s.sqlStore.GetDialect().LikeStr()+" ?)")
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards) whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
} }
switch filter { switch query.Filter {
case serviceaccounts.FilterIncludeAll: case serviceaccounts.FilterIncludeAll:
// pass // pass
case serviceaccounts.FilterOnlyExpiredTokens: case serviceaccounts.FilterOnlyExpiredTokens:
@@ -336,17 +338,17 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(
whereConditions = append( whereConditions = append(
whereConditions, whereConditions,
"is_disabled = ?") "is_disabled = ?")
whereParams = append(whereParams, s.sqlStore.Dialect.BooleanStr(true)) whereParams = append(whereParams, s.sqlStore.GetDialect().BooleanStr(true))
default: default:
s.log.Warn("invalid filter user for service account filtering", "service account search filtering", filter) s.log.Warn("invalid filter user for service account filtering", "service account search filtering", query.Filter)
} }
if len(whereConditions) > 0 { if len(whereConditions) > 0 {
sess.Where(strings.Join(whereConditions, " AND "), whereParams...) sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
} }
if limit > 0 { if query.Limit > 0 {
offset := limit * (page - 1) offset := query.Limit * (query.Page - 1)
sess.Limit(limit, offset) sess.Limit(query.Limit, offset)
} }
sess.Cols( sess.Cols(
@@ -367,7 +369,7 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(
// get total // get total
serviceaccount := serviceaccounts.ServiceAccountDTO{} serviceaccount := serviceaccounts.ServiceAccountDTO{}
countSess := dbSession.Table("org_user") countSess := dbSession.Table("org_user")
sess.Join("INNER", s.sqlStore.Dialect.Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.Dialect.Quote("user"))) sess.Join("INNER", s.sqlStore.GetDialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.GetDialect().Quote("user")))
if len(whereConditions) > 0 { if len(whereConditions) > 0 {
countSess.Where(strings.Join(whereConditions, " AND "), whereParams...) countSess.Where(strings.Join(whereConditions, " AND "), whereParams...)
@@ -468,9 +470,6 @@ func (s *ServiceAccountsStoreImpl) CreateServiceAccountFromApikey(ctx context.Co
} }
if err := s.assignApiKeyToServiceAccount(sess, key.Id, newSA.ID); err != nil { if err := s.assignApiKeyToServiceAccount(sess, key.Id, newSA.ID); err != nil {
if err := s.userService.Delete(ctx, &user.DeleteUserCommand{UserID: newSA.ID}); err != nil {
s.log.Error("Error deleting service account", "error", err)
}
return fmt.Errorf("failed to migrate API key to service account token: %w", err) return fmt.Errorf("failed to migrate API key to service account token: %w", err)
} }
@@ -508,7 +507,7 @@ func (s *ServiceAccountsStoreImpl) RevertApiKey(ctx context.Context, saId int64,
err = s.sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error { err = s.sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
user := user.User{} user := user.User{}
has, err := sess.Where(`org_id = ? and id = ? and is_service_account = ?`, has, err := sess.Where(`org_id = ? and id = ? and is_service_account = ?`,
key.OrgId, *key.ServiceAccountId, s.sqlStore.Dialect.BooleanStr(true)).Get(&user) key.OrgId, *key.ServiceAccountId, s.sqlStore.GetDialect().BooleanStr(true)).Get(&user)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -121,7 +121,7 @@ func setupTestDatabase(t *testing.T) (*sqlstore.SQLStore, *ServiceAccountsStoreI
require.NoError(t, err) require.NoError(t, err)
userSvc, err := userimpl.ProvideService(db, orgService, db.Cfg, nil, nil, quotaService) userSvc, err := userimpl.ProvideService(db, orgService, db.Cfg, nil, nil, quotaService)
require.NoError(t, err) require.NoError(t, err)
return db, ProvideServiceAccountsStore(db, apiKeyService, kvStore, userSvc, orgService) return db, ProvideServiceAccountsStore(db.Cfg, db, apiKeyService, kvStore, userSvc, orgService)
} }
func TestStore_RetrieveServiceAccount(t *testing.T) { func TestStore_RetrieveServiceAccount(t *testing.T) {
@@ -174,9 +174,9 @@ func TestStore_MigrateApiKeys(t *testing.T) {
for _, c := range cases { for _, c := range cases {
t.Run(c.desc, func(t *testing.T) { t.Run(c.desc, func(t *testing.T) {
db, store := setupTestDatabase(t) db, store := setupTestDatabase(t)
store.sqlStore.Cfg.AutoAssignOrg = true store.cfg.AutoAssignOrg = true
store.sqlStore.Cfg.AutoAssignOrgId = 1 store.cfg.AutoAssignOrgId = 1
store.sqlStore.Cfg.AutoAssignOrgRole = "Viewer" store.cfg.AutoAssignOrgRole = "Viewer"
_, err := store.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "main"}) _, err := store.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "main"})
require.NoError(t, err) require.NoError(t, err)
key := tests.SetupApiKey(t, db, c.key) key := tests.SetupApiKey(t, db, c.key)
@@ -186,11 +186,22 @@ func TestStore_MigrateApiKeys(t *testing.T) {
} else { } else {
require.NoError(t, err) require.NoError(t, err)
serviceAccounts, err := store.SearchOrgServiceAccounts(context.Background(), key.OrgId, "", "all", 1, 50, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ q := serviceaccounts.SearchOrgServiceAccountsQuery{
key.OrgId: { OrgID: key.OrgId,
"serviceaccounts:read": {"serviceaccounts:id:*"}, Query: "",
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.NoError(t, err)
require.Equal(t, int64(1), serviceAccounts.TotalCount) require.Equal(t, int64(1), serviceAccounts.TotalCount)
saMigrated := serviceAccounts.ServiceAccounts[0] saMigrated := serviceAccounts.ServiceAccounts[0]
@@ -251,9 +262,9 @@ func TestStore_MigrateAllApiKeys(t *testing.T) {
for _, c := range cases { for _, c := range cases {
t.Run(c.desc, func(t *testing.T) { t.Run(c.desc, func(t *testing.T) {
db, store := setupTestDatabase(t) db, store := setupTestDatabase(t)
store.sqlStore.Cfg.AutoAssignOrg = true store.cfg.AutoAssignOrg = true
store.sqlStore.Cfg.AutoAssignOrgId = 1 store.cfg.AutoAssignOrgId = 1
store.sqlStore.Cfg.AutoAssignOrgRole = "Viewer" store.cfg.AutoAssignOrgRole = "Viewer"
_, err := store.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "main"}) _, err := store.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "main"})
require.NoError(t, err) require.NoError(t, err)
@@ -267,11 +278,22 @@ func TestStore_MigrateAllApiKeys(t *testing.T) {
} else { } else {
require.NoError(t, err) require.NoError(t, err)
serviceAccounts, err := store.SearchOrgServiceAccounts(context.Background(), c.orgId, "", "all", 1, 50, &user.SignedInUser{UserID: 101, OrgID: c.orgId, Permissions: map[int64]map[string][]string{ q := serviceaccounts.SearchOrgServiceAccountsQuery{
c.orgId: { OrgID: c.orgId,
"serviceaccounts:read": {"serviceaccounts:id:*"}, Query: "",
Page: 1,
Limit: 50,
SignedInUser: &user.SignedInUser{
UserID: 1,
OrgID: 1,
Permissions: map[int64]map[string][]string{
c.orgId: {
"serviceaccounts:read": {"serviceaccounts:id:*"},
},
},
}, },
}}) }
serviceAccounts, err := store.SearchOrgServiceAccounts(context.Background(), &q)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, c.expectedServiceAccouts, serviceAccounts.TotalCount) require.Equal(t, c.expectedServiceAccouts, serviceAccounts.TotalCount)
if c.expectedServiceAccouts > 0 { if c.expectedServiceAccouts > 0 {
@@ -313,9 +335,9 @@ func TestStore_RevertApiKey(t *testing.T) {
for _, c := range cases { for _, c := range cases {
t.Run(c.desc, func(t *testing.T) { t.Run(c.desc, func(t *testing.T) {
db, store := setupTestDatabase(t) db, store := setupTestDatabase(t)
store.sqlStore.Cfg.AutoAssignOrg = true store.cfg.AutoAssignOrg = true
store.sqlStore.Cfg.AutoAssignOrgId = 1 store.cfg.AutoAssignOrgId = 1
store.sqlStore.Cfg.AutoAssignOrgRole = "Viewer" store.cfg.AutoAssignOrgRole = "Viewer"
_, err := store.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "main"}) _, err := store.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "main"})
require.NoError(t, err) require.NoError(t, err)
@@ -327,11 +349,22 @@ func TestStore_RevertApiKey(t *testing.T) {
if c.forceMismatchServiceAccount { if c.forceMismatchServiceAccount {
saId = rand.Int63() saId = rand.Int63()
} else { } else {
serviceAccounts, err := store.SearchOrgServiceAccounts(context.Background(), key.OrgId, "", "all", 1, 50, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ q := serviceaccounts.SearchOrgServiceAccountsQuery{
key.OrgId: { OrgID: key.OrgId,
"serviceaccounts:read": {"serviceaccounts:id:*"}, Query: "",
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.NoError(t, err)
saId = serviceAccounts.ServiceAccounts[0].Id saId = serviceAccounts.ServiceAccounts[0].Id
} }
@@ -342,12 +375,22 @@ func TestStore_RevertApiKey(t *testing.T) {
require.ErrorIs(t, err, c.expectedErr) require.ErrorIs(t, err, c.expectedErr)
} else { } else {
require.NoError(t, err) require.NoError(t, err)
q := serviceaccounts.SearchOrgServiceAccountsQuery{
serviceAccounts, err := store.SearchOrgServiceAccounts(context.Background(), key.OrgId, "", "all", 1, 50, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{ OrgID: key.OrgId,
key.OrgId: { Query: "",
"serviceaccounts:read": {"serviceaccounts:id:*"}, 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.NoError(t, err)
// Service account should be deleted // Service account should be deleted
require.Equal(t, int64(0), serviceAccounts.TotalCount) require.Equal(t, int64(0), serviceAccounts.TotalCount)

View File

@@ -6,4 +6,9 @@ var (
ErrServiceAccountNotFound = errors.New("service account not found") ErrServiceAccountNotFound = errors.New("service account not found")
ErrServiceAccountInvalidRole = errors.New("invalid role specified") ErrServiceAccountInvalidRole = errors.New("invalid role specified")
ErrServiceAccountRolePrivilegeDenied = errors.New("can not assign a role higher than user's role") ErrServiceAccountRolePrivilegeDenied = errors.New("can not assign a role higher than user's role")
ErrServiceAccountInvalidOrgID = errors.New("invalid org id specified")
ErrServiceAccountInvalidID = errors.New("invalid service account id specified")
ErrServiceAccountInvalidAPIKeyID = errors.New("invalid api key id specified")
ErrServiceAccountInvalidTokenID = errors.New("invalid service account token id specified")
ErrServiceAccountUpdateForm = errors.New("invalid update form")
) )

View File

@@ -2,16 +2,23 @@ package manager
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"time" "time"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/api" "github.com/grafana/grafana/pkg/services/serviceaccounts/api"
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
"github.com/grafana/grafana/pkg/services/serviceaccounts/secretscan" "github.com/grafana/grafana/pkg/services/serviceaccounts/secretscan"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@@ -21,7 +28,7 @@ const (
) )
type ServiceAccountsService struct { type ServiceAccountsService struct {
store serviceaccounts.Store store store
log log.Logger log log.Logger
backgroundLog log.Logger backgroundLog log.Logger
secretScanService secretscan.Checker secretScanService secretscan.Checker
@@ -35,13 +42,26 @@ func ProvideServiceAccountsService(
ac accesscontrol.AccessControl, ac accesscontrol.AccessControl,
routeRegister routing.RouteRegister, routeRegister routing.RouteRegister,
usageStats usagestats.Service, usageStats usagestats.Service,
serviceAccountsStore serviceaccounts.Store, store *sqlstore.SQLStore,
apiKeyService apikey.Service,
kvStore kvstore.KVStore,
userService user.Service,
orgService org.Service,
permissionService accesscontrol.ServiceAccountPermissionsService, permissionService accesscontrol.ServiceAccountPermissionsService,
accesscontrolService accesscontrol.Service, accesscontrolService accesscontrol.Service,
) (*ServiceAccountsService, error) { ) (*ServiceAccountsService, error) {
serviceAccountsStore := database.ProvideServiceAccountsStore(
cfg,
store,
apiKeyService,
kvStore,
userService,
orgService,
)
log := log.New("serviceaccounts")
s := &ServiceAccountsService{ s := &ServiceAccountsService{
store: serviceAccountsStore, store: serviceAccountsStore,
log: log.New("serviceaccounts"), log: log,
backgroundLog: log.New("serviceaccounts.background"), backgroundLog: log.New("serviceaccounts.background"),
} }
@@ -51,7 +71,7 @@ func ProvideServiceAccountsService(
usageStats.RegisterMetricsFunc(s.getUsageMetrics) usageStats.RegisterMetricsFunc(s.getUsageMetrics)
serviceaccountsAPI := api.NewServiceAccountsAPI(cfg, s, ac, accesscontrolService, routeRegister, s.store, permissionService) serviceaccountsAPI := api.NewServiceAccountsAPI(cfg, s, ac, accesscontrolService, routeRegister, permissionService)
serviceaccountsAPI.RegisterAPIEndpoints() serviceaccountsAPI.RegisterAPIEndpoints()
s.secretScanEnabled = cfg.SectionWithEnvOverrides("secretscan").Key("enabled").MustBool(false) s.secretScanEnabled = cfg.SectionWithEnvOverrides("secretscan").Key("enabled").MustBool(false)
@@ -122,13 +142,144 @@ func (sa *ServiceAccountsService) Run(ctx context.Context) error {
} }
func (sa *ServiceAccountsService) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) { func (sa *ServiceAccountsService) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) {
if err := validOrgID(orgID); err != nil {
return nil, err
}
return sa.store.CreateServiceAccount(ctx, orgID, saForm) return sa.store.CreateServiceAccount(ctx, orgID, saForm)
} }
func (sa *ServiceAccountsService) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error { func (sa *ServiceAccountsService) RetrieveServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
return sa.store.DeleteServiceAccount(ctx, orgID, serviceAccountID) if err := validOrgID(orgID); err != nil {
return nil, err
}
if err := validServiceAccountID(serviceAccountID); err != nil {
return nil, err
}
return sa.store.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
} }
func (sa *ServiceAccountsService) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) { func (sa *ServiceAccountsService) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) {
if err := validOrgID(orgID); err != nil {
return 0, err
}
if name == "" {
return 0, errors.New("name is required")
}
return sa.store.RetrieveServiceAccountIdByName(ctx, orgID, name) return sa.store.RetrieveServiceAccountIdByName(ctx, orgID, name)
} }
func (sa *ServiceAccountsService) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
if err := validOrgID(orgID); err != nil {
return err
}
if err := validServiceAccountID(serviceAccountID); err != nil {
return err
}
return sa.store.DeleteServiceAccount(ctx, orgID, serviceAccountID)
}
func (sa *ServiceAccountsService) UpdateServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64, saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) {
if err := validOrgID(orgID); err != nil {
return nil, err
}
if err := validServiceAccountID(serviceAccountID); err != nil {
return nil, err
}
return sa.store.UpdateServiceAccount(ctx, orgID, serviceAccountID, saForm)
}
func (sa *ServiceAccountsService) SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) {
if query.Page <= 0 || query.Limit <= 0 {
query.SetDefaults()
// optional: logging
}
return sa.store.SearchOrgServiceAccounts(ctx, query)
}
func (sa *ServiceAccountsService) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) {
return sa.store.ListTokens(ctx, query)
}
func (sa *ServiceAccountsService) AddServiceAccountToken(ctx context.Context, serviceAccountID int64, query *serviceaccounts.AddServiceAccountTokenCommand) error {
if err := validServiceAccountID(serviceAccountID); err != nil {
return err
}
return sa.store.AddServiceAccountToken(ctx, serviceAccountID, query)
}
func (sa *ServiceAccountsService) DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID int64, tokenID int64) error {
if err := validOrgID(orgID); err != nil {
return err
}
if err := validServiceAccountID(serviceAccountID); err != nil {
return err
}
if err := validServiceAccountTokenID(tokenID); err != nil {
return err
}
return sa.store.DeleteServiceAccountToken(ctx, orgID, serviceAccountID, tokenID)
}
func (sa *ServiceAccountsService) GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (status *serviceaccounts.APIKeysMigrationStatus, err error) {
if err := validOrgID(orgID); err != nil {
return nil, err
}
return sa.store.GetAPIKeysMigrationStatus(ctx, orgID)
}
func (sa *ServiceAccountsService) HideApiKeysTab(ctx context.Context, orgID int64) error {
if err := validOrgID(orgID); err != nil {
return err
}
return sa.store.HideApiKeysTab(ctx, orgID)
}
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) error {
if err := validOrgID(orgID); err != nil {
return err
}
return sa.store.MigrateApiKeysToServiceAccounts(ctx, orgID)
}
func (sa *ServiceAccountsService) RevertApiKey(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.RevertApiKey(ctx, orgID, keyID)
}
func validOrgID(orgID int64) error {
if orgID == 0 {
return serviceaccounts.ErrServiceAccountInvalidOrgID
}
return nil
}
func validServiceAccountID(serviceaccountID int64) error {
if serviceaccountID == 0 {
return serviceaccounts.ErrServiceAccountInvalidID
}
return nil
}
func validServiceAccountTokenID(tokenID int64) error {
if tokenID == 0 {
return serviceaccounts.ErrServiceAccountInvalidTokenID
}
return nil
}
func validAPIKeyID(apiKeyID int64) error {
if apiKeyID == 0 {
return serviceaccounts.ErrServiceAccountInvalidAPIKeyID
}
return nil
}

View File

@@ -4,17 +4,144 @@ import (
"context" "context"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests" "github.com/grafana/grafana/pkg/infra/log"
"github.com/stretchr/testify/assert" "github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type FakeServiceAccountStore struct {
ExpectedServiceAccountID *serviceaccounts.ServiceAccount
ExpectedServiceAccountDTO *serviceaccounts.ServiceAccountDTO
ExpectedServiceAccountProfileDTO *serviceaccounts.ServiceAccountProfileDTO
ExpectedSearchServiceAccountQueryResult *serviceaccounts.SearchOrgServiceAccountsResult
ExpectedServiceAccountMigrationStatus *serviceaccounts.APIKeysMigrationStatus
ExpectedStats *serviceaccounts.Stats
ExpectedApiKeys []apikey.APIKey
ExpectedError error
}
func newServiceAccountStoreFake() *FakeServiceAccountStore {
return &FakeServiceAccountStore{}
}
// CreateServiceAccount is a fake creating a service account.
func (f *FakeServiceAccountStore) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
return f.ExpectedServiceAccountProfileDTO, f.ExpectedError
}
// RetrieveServiceAccountIdByName is a fake retrieving a service account id by name.
func (f *FakeServiceAccountStore) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) {
return f.ExpectedServiceAccountID.Id, f.ExpectedError
}
// CreateServiceAccount is a fake creating a service account.
func (f *FakeServiceAccountStore) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) {
return f.ExpectedServiceAccountDTO, f.ExpectedError
}
// SearchOrgServiceAccounts is a fake searching for service accounts.
func (f *FakeServiceAccountStore) SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) {
return f.ExpectedSearchServiceAccountQueryResult, f.ExpectedError
}
// UpdateServiceAccount is a fake updating a service account.
func (f *FakeServiceAccountStore) UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) {
return f.ExpectedServiceAccountProfileDTO, f.ExpectedError
}
// DeleteServiceAccount is a fake deleting a service account.
func (f *FakeServiceAccountStore) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
return f.ExpectedError
}
// GetAPIKeysMigrationStatus is a fake getting the api keys migration status.
func (f *FakeServiceAccountStore) GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*serviceaccounts.APIKeysMigrationStatus, error) {
return f.ExpectedServiceAccountMigrationStatus, f.ExpectedError
}
// HideApiKeysTab is a fake hiding the api keys tab.
func (f *FakeServiceAccountStore) HideApiKeysTab(ctx context.Context, orgID int64) error {
return f.ExpectedError
}
// MigrateApiKeysToServiceAccounts is a fake migrating api keys to service accounts.
func (f *FakeServiceAccountStore) MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error {
return 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
}
// ListTokens is a fake listing tokens.
func (f *FakeServiceAccountStore) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) {
return f.ExpectedApiKeys, f.ExpectedError
}
// RevokeServiceAccountToken is a fake revoking a service account token.
func (f *FakeServiceAccountStore) RevokeServiceAccountToken(ctx context.Context, orgId, serviceAccountId, tokenId int64) error {
return f.ExpectedError
}
// AddServiceAccountToken is a fake adding a service account token.
func (f *FakeServiceAccountStore) AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) error {
return f.ExpectedError
}
// DeleteServiceAccountToken is a fake deleting a service account token.
func (f *FakeServiceAccountStore) DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error {
return f.ExpectedError
}
// GetUsageMetrics is a fake getting usage metrics.
func (f *FakeServiceAccountStore) GetUsageMetrics(ctx context.Context) (*serviceaccounts.Stats, error) {
return f.ExpectedStats, f.ExpectedError
}
type SecretsCheckerFake struct {
ExpectedError error
}
func (f *SecretsCheckerFake) CheckTokens(ctx context.Context) error {
return f.ExpectedError
}
func TestProvideServiceAccount_DeleteServiceAccount(t *testing.T) { func TestProvideServiceAccount_DeleteServiceAccount(t *testing.T) {
t.Run("should call store function", func(t *testing.T) { storeMock := newServiceAccountStoreFake()
storeMock := &tests.ServiceAccountsStoreMock{Calls: tests.Calls{}} svc := ServiceAccountsService{storeMock, log.New("test"), log.New("background.test"), &SecretsCheckerFake{}, false, 0}
svc := ServiceAccountsService{store: storeMock} testOrgId := 1
err := svc.DeleteServiceAccount(context.Background(), 1, 1)
t.Run("should create service account", func(t *testing.T) {
serviceAccountName := "new Service Account"
serviceAccountRole := org.RoleAdmin
isDisabled := true
saForm := &serviceaccounts.CreateServiceAccountForm{
Name: serviceAccountName,
Role: &serviceAccountRole,
IsDisabled: &isDisabled,
}
storeMock.ExpectedServiceAccountDTO = &serviceaccounts.ServiceAccountDTO{
Id: 1,
Name: serviceAccountName,
Role: string(serviceAccountRole),
IsDisabled: isDisabled,
}
_, err := svc.CreateServiceAccount(context.Background(), int64(testOrgId), saForm)
require.NoError(t, err)
})
t.Run("should delete service account", func(t *testing.T) {
err := svc.DeleteServiceAccount(context.Background(), int64(testOrgId), 1)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, storeMock.Calls.DeleteServiceAccount, 1)
}) })
} }

View File

@@ -4,22 +4,22 @@ import (
"context" "context"
"testing" "testing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func Test_UsageStats(t *testing.T) { func Test_UsageStats(t *testing.T) {
storeMock := &tests.ServiceAccountsStoreMock{Calls: tests.Calls{}, Stats: &serviceaccounts.Stats{ storeMock := newServiceAccountStoreFake()
ServiceAccounts: 1, svc := ServiceAccountsService{storeMock, log.New("test"), log.New("background-test"), &SecretsCheckerFake{}, true, 5}
Tokens: 1,
}}
svc := ServiceAccountsService{store: storeMock, secretScanEnabled: true}
err := svc.DeleteServiceAccount(context.Background(), 1, 1) err := svc.DeleteServiceAccount(context.Background(), 1, 1)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, storeMock.Calls.DeleteServiceAccount, 1)
storeMock.ExpectedStats = &serviceaccounts.Stats{
ServiceAccounts: 1,
Tokens: 1,
}
stats, err := svc.getUsageMetrics(context.Background()) stats, err := svc.getUsageMetrics(context.Background())
require.NoError(t, err) require.NoError(t, err)

View File

@@ -0,0 +1,39 @@
package manager
import (
"context"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
)
/*
Store is the database store for service accounts.
migration from apikeys to service accounts:
HideApiKeyTab is used to hide the api key tab in the UI.
MigrateApiKeysToServiceAccounts migrates all API keys to service accounts.
MigrateApiKey migrates a single API key to a service account.
// only used for interal api calls
RevertApiKey reverts a single service account to an API key.
*/
type store interface {
CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error)
SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error)
UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error)
RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error)
RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error)
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*serviceaccounts.APIKeysMigrationStatus, error)
HideApiKeysTab(ctx context.Context, orgID int64) error
MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error
MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error
RevertApiKey(ctx context.Context, saId int64, keyId int64) error
ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error)
RevokeServiceAccountToken(ctx context.Context, orgId, serviceAccountId, tokenId int64) error
AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) error
DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error
GetUsageMetrics(ctx context.Context) (*serviceaccounts.Stats, error)
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
) )
var ( var (
@@ -38,9 +39,10 @@ type CreateServiceAccountForm struct {
// swagger:model // swagger:model
type UpdateServiceAccountForm struct { type UpdateServiceAccountForm struct {
Name *string `json:"name"` Name *string `json:"name"`
Role *org.RoleType `json:"role"` ServiceAccountID int64 `json:"serviceAccountId"`
IsDisabled *bool `json:"isDisabled"` Role *org.RoleType `json:"role"`
IsDisabled *bool `json:"isDisabled"`
} }
// swagger: model // swagger: model
@@ -77,8 +79,22 @@ type AddServiceAccountTokenCommand struct {
Result *apikey.APIKey `json:"-"` Result *apikey.APIKey `json:"-"`
} }
type SearchOrgServiceAccountsQuery struct {
OrgID int64
Query string
Filter ServiceAccountFilter
Page int
Limit int
SignedInUser *user.SignedInUser
}
func (q *SearchOrgServiceAccountsQuery) SetDefaults() {
q.Page = 1
q.Limit = 100
}
// swagger: model // swagger: model
type SearchServiceAccountsResult struct { type SearchOrgServiceAccountsResult struct {
// It can be used for pagination of the user list // It can be used for pagination of the user list
// E.g. if totalCount is equal to 100 users and // E.g. if totalCount is equal to 100 users and
// the perpage parameter is set to 10 then there are 10 pages of users. // the perpage parameter is set to 10 then there are 10 pages of users.

View File

@@ -0,0 +1,52 @@
package retriever
import (
"context"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/serviceaccounts"
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
"github.com/grafana/grafana/pkg/services/user"
)
// ServiceAccountRetriever is the service that retrieves service accounts.
// 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)
}
// ServiceAccountRetriever is the service that manages service accounts.
type Service struct {
store *database.ServiceAccountsStoreImpl
logger log.Logger
}
func ProvideService(
store db.DB,
apiKeyService apikey.Service,
kvStore kvstore.KVStore,
userService user.Service,
orgService org.Service,
) *Service {
serviceAccountsStore := database.ProvideServiceAccountsStore(
nil,
store,
apiKeyService,
kvStore,
userService,
orgService,
)
return &Service{
store: serviceAccountsStore,
logger: log.New("serviceaccountretriever"),
}
}
func (s *Service) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
return s.store.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
}

View File

@@ -2,9 +2,6 @@ package serviceaccounts
import ( import (
"context" "context"
"github.com/grafana/grafana/pkg/services/apikey"
"github.com/grafana/grafana/pkg/services/user"
) )
/* /*
@@ -16,37 +13,9 @@ do not have a password.
type Service interface { type Service interface {
CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error) CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error)
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error)
}
/*
Store is the database store for service accounts.
migration from apikeys to service accounts:
HideApiKeyTab is used to hide the api key tab in the UI.
MigrateApiKeysToServiceAccounts migrates all API keys to service accounts.
MigrateApiKey migrates a single API key to a service account.
// only used for interal api calls
RevertApiKey reverts a single service account to an API key.
*/
type Store interface {
CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error)
SearchOrgServiceAccounts(ctx context.Context, orgID int64, query string, filter ServiceAccountFilter, page int, limit int,
signedInUser *user.SignedInUser) (*SearchServiceAccountsResult, error)
UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error)
RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*ServiceAccountProfileDTO, error) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*ServiceAccountProfileDTO, error)
RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error)
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*APIKeysMigrationStatus, error) saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error)
HideApiKeysTab(ctx context.Context, orgID int64) error
MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error
MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error
RevertApiKey(ctx context.Context, saId int64, keyId int64) error
ListTokens(ctx context.Context, query *GetSATokensQuery) ([]apikey.APIKey, error)
DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error
RevokeServiceAccountToken(ctx context.Context, orgId, serviceAccountId, tokenId int64) error
AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *AddServiceAccountTokenCommand) error AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *AddServiceAccountTokenCommand) error
GetUsageMetrics(ctx context.Context) (*Stats, error)
} }

View File

@@ -101,21 +101,59 @@ func SetupApiKey(t *testing.T, sqlStore *sqlstore.SQLStore, testKey TestApiKey)
return addKeyCmd.Result return addKeyCmd.Result
} }
// Service implements the API exposed methods for service accounts.
type serviceAccountStore interface {
CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error)
RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error)
UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error)
SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error)
ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error)
DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*serviceaccounts.APIKeysMigrationStatus, error)
HideApiKeysTab(ctx context.Context, orgID int64) error
MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error
MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error
RevertApiKey(ctx context.Context, saId int64, keyId int64) error
// Service account tokens
AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) error
DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error
}
// create mock for serviceaccountservice // create mock for serviceaccountservice
type ServiceAccountMock struct{} type ServiceAccountMock struct {
Store serviceAccountStore
Calls Calls
Stats *serviceaccounts.Stats
SecretScanEnabled bool
ExpectedTokens []apikey.APIKey
ExpectedError error
}
func (s *ServiceAccountMock) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) {
s.Calls.CreateServiceAccount = append(s.Calls.CreateServiceAccount, []interface{}{ctx, orgID, saForm})
return s.Store.CreateServiceAccount(ctx, orgID, saForm)
}
func (s *ServiceAccountMock) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
s.Calls.DeleteServiceAccount = append(s.Calls.DeleteServiceAccount, []interface{}{ctx, orgID, serviceAccountID})
return s.Store.DeleteServiceAccount(ctx, orgID, serviceAccountID)
}
func (s *ServiceAccountMock) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) {
s.Calls.RetrieveServiceAccount = append(s.Calls.RetrieveServiceAccount, []interface{}{ctx, orgID, serviceAccountID})
return s.Store.RetrieveServiceAccount(ctx, orgID, serviceAccountID)
}
func (s *ServiceAccountMock) UpdateServiceAccount(ctx context.Context,
orgID, serviceAccountID int64,
saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) {
s.Calls.UpdateServiceAccount = append(s.Calls.UpdateServiceAccount, []interface{}{ctx, orgID, serviceAccountID, saForm})
return s.Store.UpdateServiceAccount(ctx, orgID, serviceAccountID, saForm)
}
func (s *ServiceAccountMock) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) { func (s *ServiceAccountMock) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) {
return 0, nil return 0, nil
} }
func (s *ServiceAccountMock) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) {
return nil, nil
}
func (s *ServiceAccountMock) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
return nil
}
func (s *ServiceAccountMock) Migrated(ctx context.Context, orgID int64) bool { func (s *ServiceAccountMock) Migrated(ctx context.Context, orgID int64) bool {
return false return false
} }
@@ -132,9 +170,6 @@ func SetupMockAccesscontrol(t *testing.T,
return acmock return acmock
} }
// this is a way to see
// that the Mock implements the store interface
var _ serviceaccounts.Store = new(ServiceAccountsStoreMock)
var _ serviceaccounts.Service = new(ServiceAccountMock) var _ serviceaccounts.Service = new(ServiceAccountMock)
type Calls struct { type Calls struct {
@@ -154,95 +189,52 @@ type Calls struct {
RetrieveServiceAccountIdByName []interface{} RetrieveServiceAccountIdByName []interface{}
} }
type ServiceAccountsStoreMock struct { func (s *ServiceAccountMock) HideApiKeysTab(ctx context.Context, orgID int64) error {
serviceaccounts.Store
Stats *serviceaccounts.Stats
Calls Calls
}
func (s *ServiceAccountsStoreMock) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) {
s.Calls.RetrieveServiceAccountIdByName = append(s.Calls.RetrieveServiceAccountIdByName, []interface{}{ctx, orgID, name})
return 0, nil
}
func (s *ServiceAccountsStoreMock) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) {
// now we can test that the mock has these calls when we call the function
s.Calls.CreateServiceAccount = append(s.Calls.CreateServiceAccount, []interface{}{ctx, orgID, saForm})
return nil, nil
}
func (s *ServiceAccountsStoreMock) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
// now we can test that the mock has these calls when we call the function
s.Calls.DeleteServiceAccount = append(s.Calls.DeleteServiceAccount, []interface{}{ctx, orgID, serviceAccountID})
return nil
}
func (s *ServiceAccountsStoreMock) HideApiKeysTab(ctx context.Context, orgID int64) error {
s.Calls.HideApiKeysTab = append(s.Calls.HideApiKeysTab, []interface{}{ctx}) s.Calls.HideApiKeysTab = append(s.Calls.HideApiKeysTab, []interface{}{ctx})
return nil return nil
} }
func (s *ServiceAccountsStoreMock) GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*serviceaccounts.APIKeysMigrationStatus, error) { func (s *ServiceAccountMock) GetAPIKeysMigrationStatus(ctx context.Context, orgID int64) (*serviceaccounts.APIKeysMigrationStatus, error) {
s.Calls.GetAPIKeysMigrationStatus = append(s.Calls.GetAPIKeysMigrationStatus, []interface{}{ctx}) s.Calls.GetAPIKeysMigrationStatus = append(s.Calls.GetAPIKeysMigrationStatus, []interface{}{ctx})
return nil, nil return nil, nil
} }
func (s *ServiceAccountsStoreMock) MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error { func (s *ServiceAccountMock) MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) error {
s.Calls.MigrateApiKeysToServiceAccounts = append(s.Calls.MigrateApiKeysToServiceAccounts, []interface{}{ctx}) s.Calls.MigrateApiKeysToServiceAccounts = append(s.Calls.MigrateApiKeysToServiceAccounts, []interface{}{ctx})
return nil return nil
} }
func (s *ServiceAccountsStoreMock) MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error { func (s *ServiceAccountMock) MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error {
s.Calls.MigrateApiKey = append(s.Calls.MigrateApiKey, []interface{}{ctx}) s.Calls.MigrateApiKey = append(s.Calls.MigrateApiKey, []interface{}{ctx})
return nil return nil
} }
func (s *ServiceAccountsStoreMock) RevertApiKey(ctx context.Context, saId int64, keyId int64) error { func (s *ServiceAccountMock) RevertApiKey(ctx context.Context, saId int64, keyId int64) error {
s.Calls.RevertApiKey = append(s.Calls.RevertApiKey, []interface{}{ctx}) s.Calls.RevertApiKey = append(s.Calls.RevertApiKey, []interface{}{ctx})
return nil return nil
} }
func (s *ServiceAccountsStoreMock) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) { func (s *ServiceAccountMock) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) {
s.Calls.ListTokens = append(s.Calls.ListTokens, []interface{}{ctx, query.OrgID, query.ServiceAccountID}) s.Calls.ListTokens = append(s.Calls.ListTokens, []interface{}{ctx, query.OrgID, query.ServiceAccountID})
return s.ExpectedTokens, s.ExpectedError
}
func (s *ServiceAccountMock) SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) {
s.Calls.SearchOrgServiceAccounts = append(s.Calls.SearchOrgServiceAccounts, []interface{}{ctx, query})
return nil, nil return nil, nil
} }
func (s *ServiceAccountsStoreMock) RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*serviceaccounts.ServiceAccountProfileDTO, error) { func (s *ServiceAccountMock) AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) error {
s.Calls.RetrieveServiceAccount = append(s.Calls.RetrieveServiceAccount, []interface{}{ctx, orgID, serviceAccountID})
return nil, nil
}
func (s *ServiceAccountsStoreMock) UpdateServiceAccount(ctx context.Context,
orgID, serviceAccountID int64,
saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) {
s.Calls.UpdateServiceAccount = append(s.Calls.UpdateServiceAccount, []interface{}{ctx, orgID, serviceAccountID, saForm})
return nil, nil
}
func (s *ServiceAccountsStoreMock) SearchOrgServiceAccounts(
ctx context.Context,
orgID int64,
query string,
filter serviceaccounts.ServiceAccountFilter,
page int,
limit int,
user *user.SignedInUser) (*serviceaccounts.SearchServiceAccountsResult, error) {
s.Calls.SearchOrgServiceAccounts = append(s.Calls.SearchOrgServiceAccounts, []interface{}{ctx, orgID, query, page, limit, user})
return nil, nil
}
func (s *ServiceAccountsStoreMock) DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error {
s.Calls.DeleteServiceAccountToken = append(s.Calls.DeleteServiceAccountToken, []interface{}{ctx, orgID, serviceAccountID, tokenID})
return nil
}
func (s *ServiceAccountsStoreMock) AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) error {
s.Calls.AddServiceAccountToken = append(s.Calls.AddServiceAccountToken, []interface{}{ctx, cmd}) s.Calls.AddServiceAccountToken = append(s.Calls.AddServiceAccountToken, []interface{}{ctx, cmd})
return nil return s.Store.AddServiceAccountToken(ctx, serviceAccountID, cmd)
} }
func (s *ServiceAccountsStoreMock) GetUsageMetrics(ctx context.Context) (*serviceaccounts.Stats, error) { func (s *ServiceAccountMock) DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error {
s.Calls.DeleteServiceAccountToken = append(s.Calls.DeleteServiceAccountToken, []interface{}{ctx, orgID, serviceAccountID, tokenID})
return s.Store.DeleteServiceAccountToken(ctx, orgID, serviceAccountID, tokenID)
}
func (s *ServiceAccountMock) GetUsageMetrics(ctx context.Context) (*serviceaccounts.Stats, error) {
if s.Stats == nil { if s.Stats == nil {
return &serviceaccounts.Stats{}, nil return &serviceaccounts.Stats{}, nil
} }

View File

@@ -16,14 +16,13 @@ type CrawlerAuthSetupService interface {
Setup(ctx context.Context) (CrawlerAuth, error) Setup(ctx context.Context) (CrawlerAuth, error)
} }
func ProvideCrawlerAuthSetupService(serviceAccounts serviceaccounts.Service, serviceAccountsStore serviceaccounts.Store, func ProvideCrawlerAuthSetupService(serviceAccounts serviceaccounts.Service,
sqlStore db.DB, orgService org.Service) *OSSCrawlerAuthSetupService { sqlStore db.DB, orgService org.Service) *OSSCrawlerAuthSetupService {
return &OSSCrawlerAuthSetupService{ return &OSSCrawlerAuthSetupService{
serviceAccountNamePrefix: "dashboard-previews-crawler-org-", serviceAccountNamePrefix: "dashboard-previews-crawler-org-",
serviceAccounts: serviceAccounts, serviceAccounts: serviceAccounts,
log: log.New("oss_crawler_account_setup_service"), log: log.New("oss_crawler_account_setup_service"),
sqlStore: sqlStore, sqlStore: sqlStore,
serviceAccountsStore: serviceAccountsStore,
orgService: orgService, orgService: orgService,
} }
} }
@@ -32,7 +31,6 @@ type OSSCrawlerAuthSetupService struct {
log log.Logger log log.Logger
serviceAccountNamePrefix string serviceAccountNamePrefix string
serviceAccounts serviceaccounts.Service serviceAccounts serviceaccounts.Service
serviceAccountsStore serviceaccounts.Store
sqlStore db.DB sqlStore db.DB
orgService org.Service orgService org.Service
} }
@@ -117,7 +115,7 @@ func (o *OSSCrawlerAuthSetupService) Setup(ctx context.Context) (CrawlerAuth, er
} }
// update org_role to make sure everything works properly if someone has changed the role since SA's original creation // update org_role to make sure everything works properly if someone has changed the role since SA's original creation
dto, err := o.serviceAccountsStore.UpdateServiceAccount(ctx, orgId, id, &serviceaccounts.UpdateServiceAccountForm{ dto, err := o.serviceAccounts.UpdateServiceAccount(ctx, orgId, id, &serviceaccounts.UpdateServiceAccountForm{
Name: &serviceAccountNameOrg, Name: &serviceAccountNameOrg,
Role: &orgRole, Role: &orgRole,
}) })