diff --git a/pkg/services/serviceaccounts/database/store.go b/pkg/services/serviceaccounts/database/store.go index 292719cedc5..5790b755baa 100644 --- a/pkg/services/serviceaccounts/database/store.go +++ b/pkg/services/serviceaccounts/database/store.go @@ -270,9 +270,6 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, } err := s.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error { - sess := dbSession.Table("org_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) whereParams := make([]any, 0) @@ -312,10 +309,38 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, whereConditions, "is_disabled = ?") whereParams = append(whereParams, s.sqlStore.GetDialect().BooleanStr(true)) + case serviceaccounts.FilterOnlyExternal: + whereConditions = append( + whereConditions, + "login "+s.sqlStore.GetDialect().LikeStr()+" ?") + whereParams = append(whereParams, serviceaccounts.ServiceAccountPrefix+serviceaccounts.ExtSvcPrefix+"%") default: s.log.Warn("Invalid filter user for service account filtering", "service account search filtering", query.Filter) } + // Count the number of accounts + serviceaccount := serviceaccounts.ServiceAccountDTO{} + countSess := dbSession.Table("org_user") + countSess.Join("INNER", s.sqlStore.GetDialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", s.sqlStore.GetDialect().Quote("user"))) + + if len(whereConditions) > 0 { + countSess.Where(strings.Join(whereConditions, " AND "), whereParams...) + } + count, err := countSess.Count(&serviceaccount) + if err != nil { + return err + } + searchResult.TotalCount = count + + // Stop here if we only wanted to count the number of accounts + if query.CountOnly { + return nil + } + + // Fetch service accounts + sess := dbSession.Table("org_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 { sess.Where(strings.Join(whereConditions, " AND "), whereParams...) } @@ -338,21 +363,6 @@ func (s *ServiceAccountsStoreImpl) SearchOrgServiceAccounts(ctx context.Context, if err := sess.Find(&searchResult.ServiceAccounts); err != nil { return err } - - // get total - serviceaccount := serviceaccounts.ServiceAccountDTO{} - countSess := dbSession.Table("org_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 { - countSess.Where(strings.Join(whereConditions, " AND "), whereParams...) - } - count, err := countSess.Count(&serviceaccount) - if err != nil { - return err - } - searchResult.TotalCount = count - return nil }) if err != nil { diff --git a/pkg/services/serviceaccounts/database/store_test.go b/pkg/services/serviceaccounts/database/store_test.go index 11bf66caa72..e9e11d5f3ba 100644 --- a/pkg/services/serviceaccounts/database/store_test.go +++ b/pkg/services/serviceaccounts/database/store_test.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/kvstore" + ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey/apikeyimpl" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgimpl" @@ -368,3 +369,129 @@ func TestStore_MigrateAllApiKeys(t *testing.T) { }) } } +func TestServiceAccountsStoreImpl_SearchOrgServiceAccounts(t *testing.T) { + initUsers := []tests.TestUser{ + {Name: "satest-1", Role: string(org.RoleViewer), Login: "sa-satest-1", IsServiceAccount: true}, + {Name: "usertest-2", Role: string(org.RoleEditor), Login: "usertest-2", IsServiceAccount: false}, + {Name: "satest-3", Role: string(org.RoleEditor), Login: "sa-satest-3", IsServiceAccount: true}, + {Name: "satest-4", Role: string(org.RoleAdmin), Login: "sa-satest-4", IsServiceAccount: true}, + {Name: "extsvc-test-5", Role: string(org.RoleNone), Login: "sa-extsvc-test-5", IsServiceAccount: true}, + {Name: "extsvc-test-6", Role: string(org.RoleNone), Login: "sa-extsvc-test-6", IsServiceAccount: true}, + {Name: "extsvc-test-7", Role: string(org.RoleNone), Login: "sa-extsvc-test-7", IsServiceAccount: true}, + {Name: "extsvc-test-8", Role: string(org.RoleNone), Login: "sa-extsvc-test-8", IsServiceAccount: true}, + } + + db, store := setupTestDatabase(t) + orgID := tests.SetupUsersServiceAccounts(t, db, initUsers) + + userWithPerm := &user.SignedInUser{ + OrgID: orgID, + Permissions: map[int64]map[string][]string{orgID: {serviceaccounts.ActionRead: {serviceaccounts.ScopeAll}}}, + } + + tt := []struct { + desc string + query *serviceaccounts.SearchOrgServiceAccountsQuery + expectedTotal int64 // Value of the result.TotalCount + expectedCount int // Length of the result.ServiceAccounts slice + expectedErr error + }{ + { + desc: "should list all service accounts", + query: &serviceaccounts.SearchOrgServiceAccountsQuery{ + OrgID: orgID, + SignedInUser: userWithPerm, + Filter: serviceaccounts.FilterIncludeAll, + }, + expectedTotal: 7, + expectedCount: 7, + }, + { + desc: "should list no service accounts without permissions", + query: &serviceaccounts.SearchOrgServiceAccountsQuery{ + OrgID: orgID, + SignedInUser: &user.SignedInUser{ + OrgID: orgID, + Permissions: map[int64]map[string][]string{orgID: {}}, + }, + Filter: serviceaccounts.FilterIncludeAll, + }, + expectedTotal: 0, + expectedCount: 0, + }, + { + desc: "should list one service accounts with restricted permissions", + query: &serviceaccounts.SearchOrgServiceAccountsQuery{ + OrgID: orgID, + SignedInUser: &user.SignedInUser{ + OrgID: orgID, + Permissions: map[int64]map[string][]string{orgID: {serviceaccounts.ActionRead: { + ac.Scope("serviceaccounts", "id", "1"), + ac.Scope("serviceaccounts", "id", "7"), + }}}, + }, + Filter: serviceaccounts.FilterIncludeAll, + }, + expectedTotal: 2, + expectedCount: 2, + }, + { + desc: "should list only external service accounts", + query: &serviceaccounts.SearchOrgServiceAccountsQuery{ + OrgID: orgID, + SignedInUser: userWithPerm, + Filter: serviceaccounts.FilterOnlyExternal, + }, + expectedTotal: 4, + expectedCount: 4, + }, + { + desc: "should return service accounts with sa-satest login", + query: &serviceaccounts.SearchOrgServiceAccountsQuery{ + OrgID: orgID, + Query: "sa-satest", + SignedInUser: userWithPerm, + Filter: serviceaccounts.FilterIncludeAll, + }, + expectedTotal: 3, + expectedCount: 3, + }, + { + desc: "should only count service accounts", + query: &serviceaccounts.SearchOrgServiceAccountsQuery{ + OrgID: orgID, + SignedInUser: userWithPerm, + Filter: serviceaccounts.FilterIncludeAll, + CountOnly: true, + }, + expectedTotal: 7, + expectedCount: 0, + }, + { + desc: "should paginate result", + query: &serviceaccounts.SearchOrgServiceAccountsQuery{ + OrgID: orgID, + Page: 4, + Limit: 2, + SignedInUser: userWithPerm, + Filter: serviceaccounts.FilterIncludeAll, + }, + expectedTotal: 7, + expectedCount: 1, + }, + } + for _, tc := range tt { + t.Run(tc.desc, func(t *testing.T) { + ctx := context.Background() + + got, err := store.SearchOrgServiceAccounts(ctx, tc.query) + if tc.expectedErr != nil { + require.ErrorIs(t, err, tc.expectedErr) + return + } + + require.Equal(t, tc.expectedTotal, got.TotalCount) + require.Len(t, got.ServiceAccounts, tc.expectedCount) + }) + } +} diff --git a/pkg/services/serviceaccounts/extsvcaccounts/metrics.go b/pkg/services/serviceaccounts/extsvcaccounts/metrics.go new file mode 100644 index 00000000000..b0da11e64f1 --- /dev/null +++ b/pkg/services/serviceaccounts/extsvcaccounts/metrics.go @@ -0,0 +1,68 @@ +package extsvcaccounts + +import ( + "context" + "time" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/extsvcauth" + "github.com/grafana/grafana/pkg/services/serviceaccounts" + "github.com/grafana/grafana/pkg/services/user" + "github.com/prometheus/client_golang/prometheus" +) + +type metrics struct { + storedCount prometheus.GaugeFunc + savedCount prometheus.Counter + deletedCount prometheus.Counter +} + +func newMetrics(reg prometheus.Registerer, saSvc serviceaccounts.Service, logger log.Logger) *metrics { + var m metrics + + m.storedCount = prometheus.NewGaugeFunc( + prometheus.GaugeOpts{ + Namespace: metricsNamespace, + Name: "extsvc_total", + Help: "Number of external service accounts in store", + }, + func() float64 { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + res, err := saSvc.SearchOrgServiceAccounts(ctx, &serviceaccounts.SearchOrgServiceAccountsQuery{ + OrgID: extsvcauth.TmpOrgID, + Filter: serviceaccounts.FilterOnlyExternal, + CountOnly: true, + SignedInUser: &user.SignedInUser{ + OrgID: extsvcauth.TmpOrgID, + Permissions: map[int64]map[string][]string{ + extsvcauth.TmpOrgID: {serviceaccounts.ActionRead: {"serviceaccounts:id:*"}}, + }, + }, + }) + if err != nil { + logger.Error("Could not compute extsvc_total metric", "error", err) + return 0.0 + } + return float64(res.TotalCount) + }, + ) + m.savedCount = prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: metricsNamespace, + Name: "extsvc_saved_total", + Help: "Number of external service accounts saved since start up.", + }) + m.deletedCount = prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: metricsNamespace, + Name: "extsvc_deleted_total", + Help: "Number of external service accounts deleted since start up.", + }) + + if reg != nil { + reg.MustRegister(m.storedCount) + reg.MustRegister(m.savedCount) + reg.MustRegister(m.deletedCount) + } + + return &m +} diff --git a/pkg/services/serviceaccounts/extsvcaccounts/models.go b/pkg/services/serviceaccounts/extsvcaccounts/models.go index 8ea0cb77d36..3485d0289a4 100644 --- a/pkg/services/serviceaccounts/extsvcaccounts/models.go +++ b/pkg/services/serviceaccounts/extsvcaccounts/models.go @@ -7,6 +7,8 @@ import ( ) const ( + metricsNamespace = "grafana" + kvStoreType = "extsvc-token" // #nosec G101 - this is not a hardcoded secret tokenNamePrefix = "extsvc-token" diff --git a/pkg/services/serviceaccounts/extsvcaccounts/service.go b/pkg/services/serviceaccounts/extsvcaccounts/service.go index 6eed6b6e978..ae29ccaedab 100644 --- a/pkg/services/serviceaccounts/extsvcaccounts/service.go +++ b/pkg/services/serviceaccounts/extsvcaccounts/service.go @@ -4,6 +4,8 @@ import ( "context" "errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/grafana/pkg/components/satokengen" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" @@ -11,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/models/roletype" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/extsvcauth" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/secrets/kvstore" sa "github.com/grafana/grafana/pkg/services/serviceaccounts" @@ -19,19 +22,29 @@ import ( type ExtSvcAccountsService struct { acSvc ac.Service + features *featuremgmt.FeatureManager logger log.Logger + metrics *metrics saSvc sa.Service skvStore kvstore.SecretsKVStore } -func ProvideExtSvcAccountsService(acSvc ac.Service, saSvc *manager.ServiceAccountsService, db db.DB, secretsSvc secrets.Service) *ExtSvcAccountsService { +func ProvideExtSvcAccountsService(acSvc ac.Service, db db.DB, features *featuremgmt.FeatureManager, reg prometheus.Registerer, saSvc *manager.ServiceAccountsService, secretsSvc secrets.Service) *ExtSvcAccountsService { logger := log.New("serviceauth.extsvcaccounts") - return &ExtSvcAccountsService{ + esa := &ExtSvcAccountsService{ acSvc: acSvc, logger: logger, saSvc: saSvc, + features: features, skvStore: kvstore.NewSQLSecretsKVStore(db, secretsSvc, logger), // Using SQL store to avoid a cyclic dependency } + + // Register the metrics + if features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) || features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + esa.metrics = newMetrics(reg, saSvc, logger) + } + + return esa } // RetrieveExtSvcAccount fetches an external service account by ID @@ -52,6 +65,12 @@ func (esa *ExtSvcAccountsService) RetrieveExtSvcAccount(ctx context.Context, org // SaveExternalService creates, updates or delete a service account (and its token) with the requested permissions. func (esa *ExtSvcAccountsService) SaveExternalService(ctx context.Context, cmd *extsvcauth.ExternalServiceRegistration) (*extsvcauth.ExternalService, error) { + // This is double proofing, we should never reach here anyway the flags have already been checked. + if !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) && !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services") + return nil, nil + } + if cmd == nil { esa.logger.Warn("Received no input") return nil, nil @@ -92,6 +111,12 @@ func (esa *ExtSvcAccountsService) SaveExternalService(ctx context.Context, cmd * // ManageExtSvcAccount creates, updates or deletes the service account associated with an external service func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd *sa.ManageExtSvcAccountCmd) (int64, error) { + // This is double proofing, we should never reach here anyway the flags have already been checked. + if !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAccounts) && !esa.features.IsEnabled(featuremgmt.FlagExternalServiceAuth) { + esa.logger.Warn("This feature is behind a feature flag, please set it if you want to save external services") + return 0, nil + } + if cmd == nil { esa.logger.Warn("Received no input") return 0, nil @@ -111,6 +136,7 @@ func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd * "error", err.Error()) return 0, err } + esa.metrics.deletedCount.Inc() } esa.logger.Info("Skipping service account creation", "service", cmd.ExtSvcSlug, @@ -130,6 +156,7 @@ func (esa *ExtSvcAccountsService) ManageExtSvcAccount(ctx context.Context, cmd * esa.logger.Error("Could not save service account", "service", cmd.ExtSvcSlug, "error", errSave.Error()) return 0, errSave } + esa.metrics.savedCount.Inc() return saID, nil } diff --git a/pkg/services/serviceaccounts/extsvcaccounts/service_test.go b/pkg/services/serviceaccounts/extsvcaccounts/service_test.go index dc2034786bc..4adfd06f3ae 100644 --- a/pkg/services/serviceaccounts/extsvcaccounts/service_test.go +++ b/pkg/services/serviceaccounts/extsvcaccounts/service_test.go @@ -4,6 +4,9 @@ import ( "context" "testing" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models/roletype" @@ -17,8 +20,6 @@ import ( sa "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts/tests" "github.com/grafana/grafana/pkg/setting" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) type TestEnv struct { @@ -39,9 +40,12 @@ func setupTestEnv(t *testing.T) *TestEnv { SaSvc: &tests.MockServiceAccountService{}, SkvStore: kvstore.NewFakeSecretsKVStore(), } + logger := log.New("extsvcaccounts.test") env.S = &ExtSvcAccountsService{ acSvc: acimpl.ProvideOSSService(cfg, env.AcStore, localcache.New(0, 0), fmgt), - logger: log.New("extsvcaccounts.test"), + features: fmgt, + logger: logger, + metrics: newMetrics(nil, env.SaSvc, logger), saSvc: env.SaSvc, skvStore: env.SkvStore, } diff --git a/pkg/services/serviceaccounts/models.go b/pkg/services/serviceaccounts/models.go index 6346cd6cd7c..595818bc0e0 100644 --- a/pkg/services/serviceaccounts/models.go +++ b/pkg/services/serviceaccounts/models.go @@ -84,6 +84,8 @@ type ServiceAccountDTO struct { OrgId int64 `json:"orgId" xorm:"org_id"` // example: false IsDisabled bool `json:"isDisabled" xorm:"is_disabled"` + // example: false + IsExternal bool `json:"isExternal,omitempty" xorm:"-"` // example: Viewer Role string `json:"role" xorm:"role"` // example: 0 @@ -112,6 +114,7 @@ type SearchOrgServiceAccountsQuery struct { Filter ServiceAccountFilter Page int Limit int + CountOnly bool SignedInUser identity.Requester } @@ -166,6 +169,7 @@ const ( FilterOnlyExpiredTokens ServiceAccountFilter = "expiredTokens" FilterOnlyDisabled ServiceAccountFilter = "disabled" FilterIncludeAll ServiceAccountFilter = "all" + FilterOnlyExternal ServiceAccountFilter = "external" ) type Stats struct { diff --git a/pkg/services/serviceaccounts/proxy/service.go b/pkg/services/serviceaccounts/proxy/service.go index 95e79cc1a28..d6bda2a2452 100644 --- a/pkg/services/serviceaccounts/proxy/service.go +++ b/pkg/services/serviceaccounts/proxy/service.go @@ -100,6 +100,18 @@ func (s *ServiceAccountsProxy) UpdateServiceAccount(ctx context.Context, orgID, return s.proxiedService.UpdateServiceAccount(ctx, orgID, serviceAccountID, saForm) } +func (s *ServiceAccountsProxy) SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) { + sa, err := s.proxiedService.SearchOrgServiceAccounts(ctx, query) + if err != nil { + return nil, err + } + + for i := range sa.ServiceAccounts { + sa.ServiceAccounts[i].IsExternal = isExternalServiceAccount(sa.ServiceAccounts[i].Login) + } + return sa, nil +} + func isNameValid(name string) bool { return !strings.HasPrefix(name, serviceaccounts.ExtSvcPrefix) } diff --git a/pkg/services/serviceaccounts/proxy/service_test.go b/pkg/services/serviceaccounts/proxy/service_test.go index a1258b80369..e9bc7461618 100644 --- a/pkg/services/serviceaccounts/proxy/service_test.go +++ b/pkg/services/serviceaccounts/proxy/service_test.go @@ -4,15 +4,18 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts/extsvcaccounts" - "github.com/stretchr/testify/assert" ) type FakeServiceAccountsService struct { - ExpectedServiceAccountProfileDTO *serviceaccounts.ServiceAccountProfileDTO + ExpectedServiceAccountProfileDTO *serviceaccounts.ServiceAccountProfileDTO + ExpectedSearchOrgServiceAccountsResult *serviceaccounts.SearchOrgServiceAccountsResult } var _ serviceaccounts.Service = (*FakeServiceAccountsService)(nil) @@ -47,7 +50,11 @@ func (f *FakeServiceAccountsService) AddServiceAccountToken(ctx context.Context, return nil, nil } -func TestProvideServiceAccount_DeleteServiceAccount(t *testing.T) { +func (f *FakeServiceAccountsService) SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) { + return f.ExpectedSearchOrgServiceAccountsResult, nil +} + +func TestProvideServiceAccount_crudServiceAccount(t *testing.T) { testOrgId := int64(1) testServiceAccountId := int64(1) serviceMock := newServiceAccountServiceFake() @@ -257,3 +264,28 @@ func TestProvideServiceAccount_DeleteServiceAccount(t *testing.T) { assert.True(t, isExternalServiceAccount("sa-extsvc-my-service-account")) }) } + +func TestProvideServiceAccount_SearchServiceAccount(t *testing.T) { + serviceMock := newServiceAccountServiceFake() + svc := ServiceAccountsProxy{ + log.New("test"), + serviceMock, + } + + t.Run("should mark external service accounts correctly", func(t *testing.T) { + serviceMock.ExpectedSearchOrgServiceAccountsResult = &serviceaccounts.SearchOrgServiceAccountsResult{ + TotalCount: 2, + ServiceAccounts: []*serviceaccounts.ServiceAccountDTO{ + {Login: "test"}, + {Login: serviceaccounts.ServiceAccountPrefix + serviceaccounts.ExtSvcPrefix + "test"}, + }, + Page: 1, + PerPage: 2, + } + res, err := svc.SearchOrgServiceAccounts(context.Background(), &serviceaccounts.SearchOrgServiceAccountsQuery{OrgID: 1}) + require.Len(t, res.ServiceAccounts, 2) + require.NoError(t, err) + require.False(t, res.ServiceAccounts[0].IsExternal) + require.True(t, res.ServiceAccounts[1].IsExternal) + }) +} diff --git a/pkg/services/serviceaccounts/serviceaccounts.go b/pkg/services/serviceaccounts/serviceaccounts.go index f7ab7e25b3f..ed889c47475 100644 --- a/pkg/services/serviceaccounts/serviceaccounts.go +++ b/pkg/services/serviceaccounts/serviceaccounts.go @@ -13,14 +13,13 @@ Service accounts are used to authenticate API requests. They are not users and do not have a password. */ type Service interface { + AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *AddServiceAccountTokenCommand) (*apikey.APIKey, error) CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error RetrieveServiceAccount(ctx context.Context, orgID, serviceAccountID int64) (*ServiceAccountProfileDTO, error) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) - UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64, - saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error) - AddServiceAccountToken(ctx context.Context, serviceAccountID int64, - cmd *AddServiceAccountTokenCommand) (*apikey.APIKey, error) + SearchOrgServiceAccounts(ctx context.Context, query *SearchOrgServiceAccountsQuery) (*SearchOrgServiceAccountsResult, error) + UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64, saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error) } //go:generate mockery --name ExtSvcAccountsService --structname MockExtSvcAccountsService --output tests --outpkg tests --filename extsvcaccmock.go diff --git a/pkg/services/serviceaccounts/tests/common.go b/pkg/services/serviceaccounts/tests/common.go index b10fb4e8a33..f559f128f05 100644 --- a/pkg/services/serviceaccounts/tests/common.go +++ b/pkg/services/serviceaccounts/tests/common.go @@ -102,3 +102,35 @@ func SetupApiKey(t *testing.T, sqlStore *sqlstore.SQLStore, testKey TestApiKey) return key } + +// SetupUsersServiceAccounts creates in "test org" all users or service accounts passed in parameter +// To achieve this, it sets the AutoAssignOrg and AutoAssignOrgId settings. +func SetupUsersServiceAccounts(t *testing.T, sqlStore *sqlstore.SQLStore, testUsers []TestUser) (orgID int64) { + role := string(org.RoleNone) + + quotaService := quotaimpl.ProvideService(sqlStore, sqlStore.Cfg) + orgService, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService) + require.NoError(t, err) + usrSvc, err := userimpl.ProvideService(sqlStore, orgService, sqlStore.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService()) + require.NoError(t, err) + + org, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{ + Name: "test org", + }) + require.NoError(t, err) + + sqlStore.Cfg.AutoAssignOrg = true + sqlStore.Cfg.AutoAssignOrgId = int(org.ID) + + for i := range testUsers { + _, err := usrSvc.Create(context.Background(), &user.CreateUserCommand{ + Login: testUsers[i].Login, + IsServiceAccount: testUsers[i].IsServiceAccount, + DefaultOrgRole: role, + Name: testUsers[i].Name, + OrgID: org.ID, + }) + require.NoError(t, err) + } + return org.ID +} diff --git a/pkg/services/serviceaccounts/tests/mocks.go b/pkg/services/serviceaccounts/tests/mocks.go index 0d06a560e2b..d7b5a30d03e 100644 --- a/pkg/services/serviceaccounts/tests/mocks.go +++ b/pkg/services/serviceaccounts/tests/mocks.go @@ -50,3 +50,8 @@ func (s *MockServiceAccountService) UpdateServiceAccount(ctx context.Context, or mockedArgs := s.Called(ctx, orgID, serviceAccountID) return mockedArgs.Get(0).(*serviceaccounts.ServiceAccountProfileDTO), mockedArgs.Error(1) } + +func (s *MockServiceAccountService) SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) { + mockedArgs := s.Called(ctx, query) + return mockedArgs.Get(0).(*serviceaccounts.SearchOrgServiceAccountsResult), mockedArgs.Error(1) +}