mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 00:25:46 -06:00
AuthN: Add metrics to external service accounts management (#76789)
* AuthN: Add metrics to external service accounts management * Add a new metric to count stored external service accounts * Update variable names Co-authored-by: linoman <2051016+linoman@users.noreply.github.com> * Add test to SearchOrgServiceAccounts * Add feature flags checks before registering and using the metrics --------- Co-authored-by: linoman <2051016+linoman@users.noreply.github.com>
This commit is contained in:
parent
07909464f1
commit
897e3a4dab
@ -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 {
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
68
pkg/services/serviceaccounts/extsvcaccounts/metrics.go
Normal file
68
pkg/services/serviceaccounts/extsvcaccounts/metrics.go
Normal file
@ -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
|
||||
}
|
@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
metricsNamespace = "grafana"
|
||||
|
||||
kvStoreType = "extsvc-token"
|
||||
// #nosec G101 - this is not a hardcoded secret
|
||||
tokenNamePrefix = "extsvc-token"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user