Add delete user from other services/stores (#51912)

* Remove user from preferences, stars, orguser, team member

* Fix lint

* Add Delete user from org and dashboard acl

* Delete user from user auth

* Add DeleteUser to quota

* Add test files and adjust user auth store

* Rename package in wire for user auth

* Import Quota Service interface in other services

* do the same in tests

* fix lint tests

* Fix tests

* Add some tests

* Rename InsertUser and DeleteUser to InsertOrgUser and DeleteOrgUser

* Rename DeleteUser to DeleteByUser in quota

* changing a method name in few additional places

* Fix in other places

* Fix lint

* Fix tests

* Rename DeleteOrgUser to DeleteUserFromAll

* Update pkg/services/org/orgimpl/org_test.go

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>

* Update pkg/services/preference/prefimpl/inmemory_test.go

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>

* Rename Acl to ACL

* Fix wire after merge with main

* Move test to uni test

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:
idafurjes
2022-07-15 18:06:44 +02:00
committed by GitHub
parent 5d052be6ff
commit 17ec9cac83
58 changed files with 666 additions and 243 deletions

View File

@ -38,7 +38,7 @@ import (
"github.com/grafana/grafana/pkg/services/login/loginservice"
"github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/services/preference/preftest"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/searchusers"
"github.com/grafana/grafana/pkg/services/searchusers/filters"
@ -238,7 +238,7 @@ func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url strin
Live: newTestLive(t, store),
License: &licensing.OSSLicensingService{},
Features: featuremgmt.WithFeatures(),
QuotaService: &quota.QuotaService{Cfg: cfg},
QuotaService: &quotaimpl.Service{Cfg: cfg},
RouteRegister: routing.NewRouteRegister(),
AccessControl: accesscontrolmock.New().WithPermissions(permissions),
searchUsersService: searchusers.ProvideUsersService(store, filters.ProvideOSSSearchUserFilter()),
@ -379,7 +379,7 @@ func setupHTTPServerWithCfgDb(t *testing.T, useFakeAccessControl, enableAccessCo
Cfg: cfg,
Features: features,
Live: newTestLive(t, db),
QuotaService: &quota.QuotaService{Cfg: cfg},
QuotaService: &quotaimpl.Service{Cfg: cfg},
RouteRegister: routeRegister,
SQLStore: store,
License: &licensing.OSSLicensingService{},

View File

@ -34,7 +34,7 @@ import (
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/services/preference/preftest"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting"
@ -1012,7 +1012,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
Cfg: cfg,
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
Live: newTestLive(t, sqlstore.InitTestDB(t)),
QuotaService: &quota.QuotaService{
QuotaService: &quotaimpl.Service{
Cfg: cfg,
},
pluginStore: &fakePluginStore{},
@ -1048,7 +1048,7 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string
Cfg: cfg,
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
Live: newTestLive(t, sqlstore.InitTestDB(t)),
QuotaService: &quota.QuotaService{Cfg: cfg},
QuotaService: &quotaimpl.Service{Cfg: cfg},
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
SQLStore: sqlmock,
@ -1085,7 +1085,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
Cfg: cfg,
ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()),
Live: newTestLive(t, sqlstore.InitTestDB(t)),
QuotaService: &quota.QuotaService{Cfg: cfg},
QuotaService: &quotaimpl.Service{Cfg: cfg},
LibraryPanelService: &mockLibraryPanelService{},
LibraryElementService: &mockLibraryElementService{},
DashboardService: mock,

View File

@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web/webtest"
@ -143,6 +144,7 @@ func TestHTTPServer_FolderMetadata(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.folderService = folderService
hs.AccessControl = acmock.New()
hs.QuotaService = quotatest.NewQuotaServiceFake()
})
t.Run("Should attach access control metadata to multiple folders", func(t *testing.T) {

View File

@ -62,11 +62,11 @@ import (
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service"
pref "github.com/grafana/grafana/pkg/services/preference"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/quota"
publicdashboardsApi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
"github.com/grafana/grafana/pkg/services/query"
"github.com/grafana/grafana/pkg/services/queryhistory"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/searchusers"
@ -80,6 +80,7 @@ import (
"github.com/grafana/grafana/pkg/services/teamguardian"
"github.com/grafana/grafana/pkg/services/thumbs"
"github.com/grafana/grafana/pkg/services/updatechecker"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
@ -103,7 +104,7 @@ type HTTPServer struct {
CacheService *localcache.CacheService
DataSourceCache datasources.CacheService
AuthTokenService models.UserTokenService
QuotaService *quota.QuotaService
QuotaService quota.Service
RemoteCacheService *remotecache.RemoteCache
ProvisioningService provisioning.ProvisioningService
Login login.Service
@ -171,6 +172,7 @@ type HTTPServer struct {
CoremodelStaticRegistry *registry.Static
kvStore kvstore.KVStore
secretsMigrator secrets.Migrator
userService user.Service
}
type ServerOptions struct {
@ -191,7 +193,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
live *live.GrafanaLive, livePushGateway *pushhttp.Gateway, plugCtxProvider *plugincontext.Provider,
contextHandler *contexthandler.ContextHandler, features *featuremgmt.FeatureManager,
alertNG *ngalert.AlertNG, libraryPanelService librarypanels.Service, libraryElementService libraryelements.Service,
quotaService *quota.QuotaService, socialService social.Service, tracer tracing.Tracer, exportService export.ExportService,
quotaService quota.Service, socialService social.Service, tracer tracing.Tracer, exportService export.ExportService,
encryptionService encryption.Internal, grafanaUpdateChecker *updatechecker.GrafanaService,
pluginsUpdateChecker *updatechecker.PluginsService, searchUsersService searchusers.Service,
dataSourcesService datasources.DataSourceService, secretsService secrets.Service, queryDataService *query.Service,
@ -205,8 +207,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
teamsPermissionsService accesscontrol.TeamPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service,
starService star.Service, csrfService csrf.Service, coremodelRegistry *registry.Generic, coremodelStaticRegistry *registry.Static,
kvStore kvstore.KVStore, secretsMigrator secrets.Migrator, remoteSecretsCheck secretsKV.UseRemoteSecretsPluginCheck, publicDashboardsApi *publicdashboardsApi.Api,
) (*HTTPServer, error) {
kvStore kvstore.KVStore, secretsMigrator secrets.Migrator, remoteSecretsCheck secretsKV.UseRemoteSecretsPluginCheck,
publicDashboardsApi *publicdashboardsApi.Api, userService user.Service) (*HTTPServer, error) {
web.Env = cfg.Env
m := web.New()
@ -292,6 +294,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
kvStore: kvStore,
PublicDashboardsApi: publicDashboardsApi,
secretsMigrator: secretsMigrator,
userService: userService,
}
if hs.Listener != nil {
hs.log.Debug("Using provided listener")

View File

@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/web/webtest"
"golang.org/x/oauth2"
@ -87,10 +88,12 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) {
serverFeatureEnabled := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.queryDataService = qds
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagDatasourceQueryMultiStatus, true)
hs.QuotaService = quotatest.NewQuotaServiceFake()
})
serverFeatureDisabled := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.queryDataService = qds
hs.Features = featuremgmt.WithFeatures(featuremgmt.FlagDatasourceQueryMultiStatus, false)
hs.QuotaService = quotatest.NewQuotaServiceFake()
})
t.Run("Status code is 400 when data source response has an error and feature toggle is disabled", func(t *testing.T) {
@ -133,6 +136,7 @@ func TestAPIEndpoint_Metrics_PluginDecryptionFailure(t *testing.T) {
)
httpServer := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.queryDataService = qds
hs.QuotaService = quotatest.NewQuotaServiceFake()
})
t.Run("Status code is 500 and a secrets plugin error is returned if there is a problem getting secrets from the remote plugin", func(t *testing.T) {

View File

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/plugindashboards"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/web/webtest"
"github.com/stretchr/testify/require"
)
@ -39,6 +40,7 @@ func TestGetPluginDashboards(t *testing.T) {
s := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.pluginDashboardService = pluginDashboardService
hs.QuotaService = quotatest.NewQuotaServiceFake()
})
t.Run("Not signed in should return 404 Not Found", func(t *testing.T) {

View File

@ -21,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log/logtest"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web/webtest"
)
@ -54,6 +55,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled,
}
hs.pluginManager = pm
hs.QuotaService = quotatest.NewQuotaServiceFake()
})
t.Run(testName("Install", tc), func(t *testing.T) {

View File

@ -31,6 +31,10 @@ func (t *TeamGuardianMock) CanAdmin(ctx context.Context, orgId int64, teamId int
return t.result
}
func (t *TeamGuardianMock) DeleteByUser(ctx context.Context, userID int64) error {
return t.result
}
func setUpGetTeamMembersHandler(t *testing.T, sqlStore *sqlstore.SQLStore) {
const testOrgID int64 = 1
var userCmd user.CreateUserCommand

View File

@ -253,3 +253,7 @@ func (m *mockQuotaService) QuotaReached(c *models.ReqContext, target string) (bo
func (m *mockQuotaService) CheckQuotaReached(c context.Context, target string, params *quota.ScopeParameters) (bool, error) {
return m.reached, m.err
}
func (m *mockQuotaService) DeleteByUser(c context.Context, userID int64) error {
return m.err
}

View File

@ -84,7 +84,7 @@ import (
publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service"
"github.com/grafana/grafana/pkg/services/query"
"github.com/grafana/grafana/pkg/services/queryhistory"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/searchV2"
@ -181,7 +181,7 @@ var wireBasicSet = wire.NewSet(
wire.Bind(new(shorturls.Service), new(*shorturls.ShortURLService)),
queryhistory.ProvideService,
wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)),
quota.ProvideService,
quotaimpl.ProvideService,
remotecache.ProvideService,
loginservice.ProvideService,
wire.Bind(new(login.Service), new(*loginservice.Implementation)),

View File

@ -18,7 +18,7 @@ import (
)
func ProvideService(routeRegister routing.RouteRegister,
quotaService *quota.QuotaService,
quotaService quota.Service,
pluginDashboardService plugindashboards.Service, pluginStore plugins.Store,
libraryPanelService librarypanels.Service, dashboardService dashboards.DashboardService,
ac accesscontrol.AccessControl,

View File

@ -70,6 +70,7 @@ type Store interface {
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
// ValidateDashboardBeforeSave validates a dashboard before save.
ValidateDashboardBeforeSave(dashboard *models.Dashboard, overwrite bool) (bool, error)
DeleteACLByUser(context.Context, int64) error
FolderStore
}

View File

@ -148,3 +148,11 @@ func (d *DashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Con
return nil
})
}
func (d *DashboardStore) DeleteACLByUser(ctx context.Context, userID int64) error {
return d.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
var rawSQL = "DELETE FROM dashboard_acl WHERE user_id = ?"
_, err := sess.Exec(rawSQL, userID)
return err
})
}

View File

@ -247,6 +247,12 @@ func TestIntegrationDashboardAclDataAccess(t *testing.T) {
require.Equal(t, models.ROLE_EDITOR, *query.Result[1].Role)
require.False(t, query.Result[1].Inherited)
})
t.Run("Delete acl by user", func(t *testing.T) {
setup(t)
err := dashboardStore.DeleteACLByUser(context.Background(), currentUser.ID)
require.NoError(t, err)
})
}
func createUser(t *testing.T, sqlStore *sqlstore.SQLStore, name string, role string, isAdmin bool) user.User {

View File

@ -593,3 +593,7 @@ func (dr *DashboardServiceImpl) HasEditPermissionInFolders(ctx context.Context,
func (dr *DashboardServiceImpl) GetDashboardTags(ctx context.Context, query *models.GetDashboardTagsQuery) error {
return dr.dashboardStore.GetDashboardTags(ctx, query)
}
func (dr *DashboardServiceImpl) DeleteACLByUser(ctx context.Context, userID int64) error {
return dr.dashboardStore.DeleteACLByUser(ctx, userID)
}

View File

@ -17,10 +17,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func TestIntegrationDashboardService(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
func TestDashboardService(t *testing.T) {
t.Run("Dashboard service tests", func(t *testing.T) {
fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t)
@ -216,6 +213,14 @@ func TestIntegrationDashboardService(t *testing.T) {
err := service.DeleteDashboard(context.Background(), 1, 1)
require.NoError(t, err)
})
// t.Run("Delete ACL by user", func(t *testing.T) {
// fakeStore := dashboards.FakeDashboardStore{}
// args := 1
// fakeStore.On("DeleteACLByUser", mock.Anything, args).Return(nil).Once()
// err := service.DeleteACLByUser(context.Background(), 1)
// require.NoError(t, err)
// })
})
})
}

View File

@ -435,6 +435,19 @@ func (_m *FakeDashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dash
return r0, r1
}
func (_m *FakeDashboardStore) DeleteACLByUser(ctx context.Context, userID int64) error{
ret := _m.Called(ctx, userID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
r0 = rf(ctx, userID)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewFakeDashboardStore creates a new instance of FakeDashboardStore. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
func NewFakeDashboardStore(t testing.TB) *FakeDashboardStore {
mock := &FakeDashboardStore{}

View File

@ -19,7 +19,7 @@ var (
func ProvideService(
sqlStore sqlstore.Store,
userService user.Service,
quotaService *quota.QuotaService,
quotaService quota.Service,
authInfoService login.AuthInfoService,
) *Implementation {
s := &Implementation{
@ -35,7 +35,7 @@ type Implementation struct {
SQLStore sqlstore.Store
userService user.Service
AuthInfoService login.AuthInfoService
QuotaService *quota.QuotaService
QuotaService quota.Service
TeamSync login.TeamSyncFunc
}

View File

@ -10,7 +10,7 @@ import (
"github.com/go-kit/log/level"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/stretchr/testify/assert"
@ -28,7 +28,7 @@ func Test_syncOrgRoles_doesNotBreakWhenTryingToRemoveLastOrgAdmin(t *testing.T)
}
login := Implementation{
QuotaService: &quota.QuotaService{},
QuotaService: &quotaimpl.Service{},
AuthInfoService: authInfoMock,
SQLStore: store,
}
@ -52,7 +52,7 @@ func Test_syncOrgRoles_whenTryingToRemoveLastOrgLogsError(t *testing.T) {
}
login := Implementation{
QuotaService: &quota.QuotaService{},
QuotaService: &quotaimpl.Service{},
AuthInfoService: authInfoMock,
SQLStore: store,
}
@ -65,7 +65,7 @@ func Test_syncOrgRoles_whenTryingToRemoveLastOrgLogsError(t *testing.T) {
func Test_teamSync(t *testing.T) {
authInfoMock := &logintest.AuthInfoServiceFake{}
login := Implementation{
QuotaService: &quota.QuotaService{},
QuotaService: &quotaimpl.Service{},
AuthInfoService: authInfoMock,
}

View File

@ -64,7 +64,7 @@ type API struct {
DatasourceCache datasources.CacheService
RouteRegister routing.RouteRegister
ExpressionService *expr.Service
QuotaService *quota.QuotaService
QuotaService quota.Service
Schedule schedule.ScheduleService
TransactionManager provisioning.TransactionManager
ProvenanceStore provisioning.ProvisioningStore

View File

@ -34,7 +34,7 @@ type RulerSrv struct {
provenanceStore provisioning.ProvisioningStore
store store.RuleStore
DatasourceCache datasources.CacheService
QuotaService *quota.QuotaService
QuotaService quota.Service
scheduleService schedule.ScheduleService
log log.Logger
cfg *setting.UnifiedAlertingSettings
@ -393,8 +393,8 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *models.ReqContext, groupKey ngmod
if len(finalChanges.New) > 0 {
limitReached, err := srv.QuotaService.CheckQuotaReached(tranCtx, "alert_rule", &quota.ScopeParameters{
OrgId: c.OrgId,
UserId: c.UserId,
OrgID: c.OrgId,
UserID: c.UserId,
}) // alert rule is table name
if err != nil {
return fmt.Errorf("failed to get alert rules quota: %w", err)

View File

@ -37,7 +37,7 @@ import (
func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, routeRegister routing.RouteRegister,
sqlStore *sqlstore.SQLStore, kvStore kvstore.KVStore, expressionService *expr.Service, dataProxy *datasourceproxy.DataSourceProxyService,
quotaService *quota.QuotaService, secretsService secrets.Service, notificationService notifications.Service, m *metrics.NGAlert,
quotaService quota.Service, secretsService secrets.Service, notificationService notifications.Service, m *metrics.NGAlert,
folderService dashboards.FolderService, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService, renderService rendering.Service,
bus bus.Bus) (*AlertNG, error) {
ng := &AlertNG{
@ -80,7 +80,7 @@ type AlertNG struct {
KVStore kvstore.KVStore
ExpressionService *expr.Service
DataProxy *datasourceproxy.DataSourceProxyService
QuotaService *quota.QuotaService
QuotaService quota.Service
SecretsService secrets.Service
Metrics *metrics.NGAlert
NotificationService notifications.Service

View File

@ -88,8 +88,8 @@ func (service *AlertRuleService) CreateAlertRule(ctx context.Context, rule model
}
limitReached, err := service.quotas.CheckQuotaReached(ctx, "alert_rule", &quota.ScopeParameters{
OrgId: rule.OrgID,
UserId: userID,
OrgID: rule.OrgID,
UserID: userID,
})
if err != nil {
return fmt.Errorf("failed to check alert rule quota: %w", err)

View File

@ -6,5 +6,6 @@ import (
type Service interface {
GetIDForNewUser(context.Context, GetOrgIDForNewUserCommand) (int64, error)
InsertUser(context.Context, *OrgUser) (int64, error)
InsertOrgUser(context.Context, *OrgUser) (int64, error)
DeleteUserFromAll(context.Context, int64) error
}

View File

@ -73,6 +73,10 @@ func (s *Service) GetIDForNewUser(ctx context.Context, cmd org.GetOrgIDForNewUse
return s.store.Insert(ctx, &orga)
}
func (s *Service) InsertUser(ctx context.Context, orguser *org.OrgUser) (int64, error) {
return s.store.InsertUser(ctx, orguser)
func (s *Service) InsertOrgUser(ctx context.Context, orguser *org.OrgUser) (int64, error) {
return s.store.InsertOrgUser(ctx, orguser)
}
func (s *Service) DeleteUserFromAll(ctx context.Context, userID int64) error {
return s.store.DeleteUserFromAll(ctx, userID)
}

View File

@ -46,6 +46,11 @@ func TestOrgService(t *testing.T) {
setting.AutoAssignOrg = false
setting.AutoAssignOrgId = 0
t.Run("delete user from all orgs", func(t *testing.T) {
err := orgService.DeleteUserFromAll(context.Background(), 1)
require.NoError(t, err)
})
}
type FakeOrgStore struct {
@ -67,6 +72,10 @@ func (f *FakeOrgStore) Insert(ctx context.Context, org *org.Org) (int64, error)
return f.ExpectedOrgID, f.ExpectedError
}
func (f *FakeOrgStore) InsertUser(ctx context.Context, org *org.OrgUser) (int64, error) {
func (f *FakeOrgStore) InsertOrgUser(ctx context.Context, org *org.OrgUser) (int64, error) {
return f.ExpectedUserID, f.ExpectedError
}
func (f *FakeOrgStore) DeleteUserFromAll(ctx context.Context, userID int64) error {
return f.ExpectedError
}

View File

@ -15,7 +15,8 @@ const MainOrgName = "Main Org."
type store interface {
Get(context.Context, int64) (*org.Org, error)
Insert(context.Context, *org.Org) (int64, error)
InsertUser(context.Context, *org.OrgUser) (int64, error)
InsertOrgUser(context.Context, *org.OrgUser) (int64, error)
DeleteUserFromAll(context.Context, int64) error
}
type sqlStore struct {
@ -67,7 +68,7 @@ func (ss *sqlStore) Insert(ctx context.Context, org *org.Org) (int64, error) {
return orgID, nil
}
func (ss *sqlStore) InsertUser(ctx context.Context, cmd *org.OrgUser) (int64, error) {
func (ss *sqlStore) InsertOrgUser(ctx context.Context, cmd *org.OrgUser) (int64, error) {
var orgID int64
var err error
err = ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
@ -81,3 +82,12 @@ func (ss *sqlStore) InsertUser(ctx context.Context, cmd *org.OrgUser) (int64, er
}
return orgID, nil
}
func (ss *sqlStore) DeleteUserFromAll(ctx context.Context, userID int64) error {
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
if _, err := sess.Exec("DELETE FROM org_user WHERE user_id = ?", userID); err != nil {
return err
}
return nil
})
}

View File

@ -61,7 +61,7 @@ func TestIntegrationOrgUserDataAccess(t *testing.T) {
}
t.Run("org user inserted", func(t *testing.T) {
_, err := orgUserStore.InsertUser(context.Background(), &org.OrgUser{
_, err := orgUserStore.InsertOrgUser(context.Background(), &org.OrgUser{
ID: 1,
OrgID: 1,
UserID: 1,
@ -70,4 +70,9 @@ func TestIntegrationOrgUserDataAccess(t *testing.T) {
})
require.NoError(t, err)
})
t.Run("delete by user", func(t *testing.T) {
err := orgUserStore.DeleteUserFromAll(context.Background(), 1)
require.NoError(t, err)
})
}

View File

@ -23,6 +23,10 @@ func (f *FakeOrgService) Insert(ctx context.Context, cmd *org.OrgUser) (int64, e
return f.ExpectedOrgUserID, f.ExpectedError
}
func (f *FakeOrgService) InsertUser(ctx context.Context, cmd *org.OrgUser) (int64, error) {
func (f *FakeOrgService) InsertOrgUser(ctx context.Context, cmd *org.OrgUser) (int64, error) {
return f.ExpectedOrgUserID, f.ExpectedError
}
func (f *FakeOrgService) DeleteUserFromAll(ctx context.Context, userID int64) error {
return f.ExpectedError
}

View File

@ -10,4 +10,5 @@ type Service interface {
Save(context.Context, *SavePreferenceCommand) error
Patch(context.Context, *PatchPreferenceCommand) error
GetDefaults() *Preference
DeleteByUser(context.Context, int64) error
}

View File

@ -120,3 +120,7 @@ func (s *inmemStore) Update(ctx context.Context, preference *pref.Preference) er
s.preference[key] = *preference
return nil
}
func (s *inmemStore) DeleteByUser(ctx context.Context, userID int64) error {
panic("not yet implemented")
}

View File

@ -236,3 +236,7 @@ func (s *Service) GetDefaults() *pref.Preference {
return defaults
}
func (s *Service) DeleteByUser(ctx context.Context, userID int64) error {
return s.store.DeleteByUser(ctx, userID)
}

View File

@ -14,6 +14,7 @@ type store interface {
List(context.Context, *pref.Preference) ([]*pref.Preference, error)
Insert(context.Context, *pref.Preference) (int64, error)
Update(context.Context, *pref.Preference) error
DeleteByUser(context.Context, int64) error
}
type sqlStore struct {
@ -86,3 +87,11 @@ func (s *sqlStore) Insert(ctx context.Context, cmd *pref.Preference) (int64, err
})
return ID, err
}
func (s *sqlStore) DeleteByUser(ctx context.Context, userID int64) error {
return s.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
var rawSQL = "DELETE FROM preferences WHERE user_id = ?"
_, err := dbSession.Exec(rawSQL, userID)
return err
})
}

View File

@ -34,3 +34,7 @@ func (f *FakePreferenceService) GetDefaults() *pref.Preference {
func (f *FakePreferenceService) Patch(ctx context.Context, cmd *pref.PatchPreferenceCommand) error {
return f.ExpectedError
}
func (f *FakePreferenceService) DeleteByUser(context.Context, int64) error {
return f.ExpectedError
}

View File

@ -44,7 +44,7 @@ func ProvideService(
alertingService *alerting.AlertNotificationService,
pluginSettings pluginsettings.Service,
searchService searchV2.SearchService,
quotaService *quota.QuotaService,
quotaService quota.Service,
) (*ProvisioningServiceImpl, error) {
s := &ProvisioningServiceImpl{
Cfg: cfg,

View File

@ -0,0 +1,10 @@
package quota
import "errors"
var ErrInvalidQuotaTarget = errors.New("invalid quota target")
type ScopeParameters struct {
OrgID int64
UserID int64
}

View File

@ -2,201 +2,12 @@ package quota
import (
"context"
"errors"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
var ErrInvalidQuotaTarget = errors.New("invalid quota target")
func ProvideService(cfg *setting.Cfg, tokenService models.UserTokenService, sqlStore *sqlstore.SQLStore) *QuotaService {
return &QuotaService{
Cfg: cfg,
AuthTokenService: tokenService,
SQLStore: sqlStore,
Logger: log.New("quota_service"),
}
}
type QuotaService struct {
AuthTokenService models.UserTokenService
Cfg *setting.Cfg
SQLStore sqlstore.Store
Logger log.Logger
}
type Service interface {
QuotaReached(c *models.ReqContext, target string) (bool, error)
CheckQuotaReached(ctx context.Context, target string, scopeParams *ScopeParameters) (bool, error)
}
type ScopeParameters struct {
OrgId int64
UserId int64
}
// QuotaReached checks that quota is reached for a target. Runs CheckQuotaReached and take context and scope parameters from the request context
func (qs *QuotaService) QuotaReached(c *models.ReqContext, target string) (bool, error) {
if !qs.Cfg.Quota.Enabled {
return false, nil
}
// No request context means this is a background service, like LDAP Background Sync
if c == nil {
return false, nil
}
var params *ScopeParameters
if c.IsSignedIn {
params = &ScopeParameters{
OrgId: c.OrgId,
UserId: c.UserId,
}
}
return qs.CheckQuotaReached(c.Req.Context(), target, params)
}
// CheckQuotaReached check that quota is reached for a target. If ScopeParameters are not defined, only global scope is checked
func (qs *QuotaService) CheckQuotaReached(ctx context.Context, target string, scopeParams *ScopeParameters) (bool, error) {
if !qs.Cfg.Quota.Enabled {
return false, nil
}
// get the list of scopes that this target is valid for. Org, User, Global
scopes, err := qs.getQuotaScopes(target)
if err != nil {
return false, err
}
for _, scope := range scopes {
qs.Logger.Debug("Checking quota", "target", target, "scope", scope)
switch scope.Name {
case "global":
if scope.DefaultLimit < 0 {
continue
}
if scope.DefaultLimit == 0 {
return true, nil
}
if target == "session" {
usedSessions, err := qs.AuthTokenService.ActiveTokenCount(ctx)
if err != nil {
return false, err
}
if usedSessions > scope.DefaultLimit {
qs.Logger.Debug("Sessions limit reached", "active", usedSessions, "limit", scope.DefaultLimit)
return true, nil
}
continue
}
query := models.GetGlobalQuotaByTargetQuery{Target: scope.Target, UnifiedAlertingEnabled: qs.Cfg.UnifiedAlerting.IsEnabled()}
if err := qs.SQLStore.GetGlobalQuotaByTarget(ctx, &query); err != nil {
return true, err
}
if query.Result.Used >= scope.DefaultLimit {
return true, nil
}
case "org":
if scopeParams == nil {
continue
}
query := models.GetOrgQuotaByTargetQuery{
OrgId: scopeParams.OrgId,
Target: scope.Target,
Default: scope.DefaultLimit,
UnifiedAlertingEnabled: qs.Cfg.UnifiedAlerting.IsEnabled(),
}
if err := qs.SQLStore.GetOrgQuotaByTarget(ctx, &query); err != nil {
return true, err
}
if query.Result.Limit < 0 {
continue
}
if query.Result.Limit == 0 {
return true, nil
}
if query.Result.Used >= query.Result.Limit {
return true, nil
}
case "user":
if scopeParams == nil || scopeParams.UserId == 0 {
continue
}
query := models.GetUserQuotaByTargetQuery{UserId: scopeParams.UserId, Target: scope.Target, Default: scope.DefaultLimit, UnifiedAlertingEnabled: qs.Cfg.UnifiedAlerting.IsEnabled()}
if err := qs.SQLStore.GetUserQuotaByTarget(ctx, &query); err != nil {
return true, err
}
if query.Result.Limit < 0 {
continue
}
if query.Result.Limit == 0 {
return true, nil
}
if query.Result.Used >= query.Result.Limit {
return true, nil
}
}
}
return false, nil
}
func (qs *QuotaService) getQuotaScopes(target string) ([]models.QuotaScope, error) {
scopes := make([]models.QuotaScope, 0)
switch target {
case "user":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.User},
models.QuotaScope{Name: "org", Target: "org_user", DefaultLimit: qs.Cfg.Quota.Org.User},
)
return scopes, nil
case "org":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.Org},
models.QuotaScope{Name: "user", Target: "org_user", DefaultLimit: qs.Cfg.Quota.User.Org},
)
return scopes, nil
case "dashboard":
scopes = append(scopes,
models.QuotaScope{
Name: "global",
Target: target,
DefaultLimit: qs.Cfg.Quota.Global.Dashboard,
},
models.QuotaScope{
Name: "org",
Target: target,
DefaultLimit: qs.Cfg.Quota.Org.Dashboard,
},
)
return scopes, nil
case "data_source":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.DataSource},
models.QuotaScope{Name: "org", Target: target, DefaultLimit: qs.Cfg.Quota.Org.DataSource},
)
return scopes, nil
case "api_key":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.ApiKey},
models.QuotaScope{Name: "org", Target: target, DefaultLimit: qs.Cfg.Quota.Org.ApiKey},
)
return scopes, nil
case "session":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.Session},
)
return scopes, nil
case "alert_rule": // target need to match the respective database name
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: qs.Cfg.Quota.Global.AlertRule},
models.QuotaScope{Name: "org", Target: target, DefaultLimit: qs.Cfg.Quota.Org.AlertRule},
)
return scopes, nil
default:
return scopes, ErrInvalidQuotaTarget
}
DeleteByUser(context.Context, int64) error
}

View File

@ -0,0 +1,200 @@
package quotaimpl
import (
"context"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/setting"
)
type Service struct {
store store
AuthTokenService models.UserTokenService
Cfg *setting.Cfg
SQLStore sqlstore.Store
Logger log.Logger
}
func ProvideService(db db.DB, cfg *setting.Cfg, tokenService models.UserTokenService, ss *sqlstore.SQLStore) quota.Service {
return &Service{
store: &sqlStore{db: db},
Cfg: cfg,
AuthTokenService: tokenService,
SQLStore: ss,
Logger: log.New("quota_service"),
}
}
// QuotaReached checks that quota is reached for a target. Runs CheckQuotaReached and take context and scope parameters from the request context
func (s *Service) QuotaReached(c *models.ReqContext, target string) (bool, error) {
if !s.Cfg.Quota.Enabled {
return false, nil
}
// No request context means this is a background service, like LDAP Background Sync
if c == nil {
return false, nil
}
var params *quota.ScopeParameters
if c.IsSignedIn {
params = &quota.ScopeParameters{
OrgID: c.OrgId,
UserID: c.UserId,
}
}
return s.CheckQuotaReached(c.Req.Context(), target, params)
}
// CheckQuotaReached check that quota is reached for a target. If ScopeParameters are not defined, only global scope is checked
func (s *Service) CheckQuotaReached(ctx context.Context, target string, scopeParams *quota.ScopeParameters) (bool, error) {
if !s.Cfg.Quota.Enabled {
return false, nil
}
// get the list of scopes that this target is valid for. Org, User, Global
scopes, err := s.getQuotaScopes(target)
if err != nil {
return false, err
}
for _, scope := range scopes {
s.Logger.Debug("Checking quota", "target", target, "scope", scope)
switch scope.Name {
case "global":
if scope.DefaultLimit < 0 {
continue
}
if scope.DefaultLimit == 0 {
return true, nil
}
if target == "session" {
usedSessions, err := s.AuthTokenService.ActiveTokenCount(ctx)
if err != nil {
return false, err
}
if usedSessions > scope.DefaultLimit {
s.Logger.Debug("Sessions limit reached", "active", usedSessions, "limit", scope.DefaultLimit)
return true, nil
}
continue
}
query := models.GetGlobalQuotaByTargetQuery{Target: scope.Target, UnifiedAlertingEnabled: s.Cfg.UnifiedAlerting.IsEnabled()}
// TODO : move GetGlobalQuotaByTarget to a global quota service
if err := s.SQLStore.GetGlobalQuotaByTarget(ctx, &query); err != nil {
return true, err
}
if query.Result.Used >= scope.DefaultLimit {
return true, nil
}
case "org":
if scopeParams == nil {
continue
}
query := models.GetOrgQuotaByTargetQuery{
OrgId: scopeParams.OrgID,
Target: scope.Target,
Default: scope.DefaultLimit,
UnifiedAlertingEnabled: s.Cfg.UnifiedAlerting.IsEnabled(),
}
// TODO: move GetOrgQuotaByTarget from sqlstore to quota store
if err := s.SQLStore.GetOrgQuotaByTarget(ctx, &query); err != nil {
return true, err
}
if query.Result.Limit < 0 {
continue
}
if query.Result.Limit == 0 {
return true, nil
}
if query.Result.Used >= query.Result.Limit {
return true, nil
}
case "user":
if scopeParams == nil || scopeParams.UserID == 0 {
continue
}
query := models.GetUserQuotaByTargetQuery{UserId: scopeParams.UserID, Target: scope.Target, Default: scope.DefaultLimit, UnifiedAlertingEnabled: s.Cfg.UnifiedAlerting.IsEnabled()}
// TODO: move GetUserQuotaByTarget from sqlstore to quota store
if err := s.SQLStore.GetUserQuotaByTarget(ctx, &query); err != nil {
return true, err
}
if query.Result.Limit < 0 {
continue
}
if query.Result.Limit == 0 {
return true, nil
}
if query.Result.Used >= query.Result.Limit {
return true, nil
}
}
}
return false, nil
}
func (s *Service) getQuotaScopes(target string) ([]models.QuotaScope, error) {
scopes := make([]models.QuotaScope, 0)
switch target {
case "user":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.User},
models.QuotaScope{Name: "org", Target: "org_user", DefaultLimit: s.Cfg.Quota.Org.User},
)
return scopes, nil
case "org":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.Org},
models.QuotaScope{Name: "user", Target: "org_user", DefaultLimit: s.Cfg.Quota.User.Org},
)
return scopes, nil
case "dashboard":
scopes = append(scopes,
models.QuotaScope{
Name: "global",
Target: target,
DefaultLimit: s.Cfg.Quota.Global.Dashboard,
},
models.QuotaScope{
Name: "org",
Target: target,
DefaultLimit: s.Cfg.Quota.Org.Dashboard,
},
)
return scopes, nil
case "data_source":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.DataSource},
models.QuotaScope{Name: "org", Target: target, DefaultLimit: s.Cfg.Quota.Org.DataSource},
)
return scopes, nil
case "api_key":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.ApiKey},
models.QuotaScope{Name: "org", Target: target, DefaultLimit: s.Cfg.Quota.Org.ApiKey},
)
return scopes, nil
case "session":
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.Session},
)
return scopes, nil
case "alert_rule": // target need to match the respective database name
scopes = append(scopes,
models.QuotaScope{Name: "global", Target: target, DefaultLimit: s.Cfg.Quota.Global.AlertRule},
models.QuotaScope{Name: "org", Target: target, DefaultLimit: s.Cfg.Quota.Org.AlertRule},
)
return scopes, nil
default:
return scopes, quota.ErrInvalidQuotaTarget
}
}
func (s *Service) DeleteByUser(ctx context.Context, userID int64) error {
return s.store.DeleteByUser(ctx, userID)
}

View File

@ -0,0 +1,28 @@
package quotaimpl
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestQuotaService(t *testing.T) {
quotaStore := &FakeQuotaStore{}
quotaService := Service{
store: quotaStore,
}
t.Run("delete quota", func(t *testing.T) {
err := quotaService.DeleteByUser(context.Background(), 1)
require.NoError(t, err)
})
}
type FakeQuotaStore struct {
ExpectedError error
}
func (f *FakeQuotaStore) DeleteByUser(ctx context.Context, userID int64) error {
return f.ExpectedError
}

View File

@ -0,0 +1,24 @@
package quotaimpl
import (
"context"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
)
type store interface {
DeleteByUser(context.Context, int64) error
}
type sqlStore struct {
db db.DB
}
func (ss *sqlStore) DeleteByUser(ctx context.Context, userID int64) error {
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
var rawSQL = "DELETE FROM quota WHERE user_id = ?"
_, err := sess.Exec(rawSQL, userID)
return err
})
}

View File

@ -0,0 +1,25 @@
package quotaimpl
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/require"
)
func TestIntegrationQuotaDataAccess(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ss := sqlstore.InitTestDB(t)
quotaStore := sqlStore{
db: ss,
}
t.Run("quota deleted", func(t *testing.T) {
err := quotaStore.DeleteByUser(context.Background(), 1)
require.NoError(t, err)
})
}

View File

@ -0,0 +1,29 @@
package quotatest
import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/quota"
)
type FakeQuotaService struct {
reached bool
err error
}
func NewQuotaServiceFake() *FakeQuotaService {
return &FakeQuotaService{}
}
func (f *FakeQuotaService) QuotaReached(c *models.ReqContext, target string) (bool, error) {
return f.reached, f.err
}
func (f *FakeQuotaService) CheckQuotaReached(c context.Context, target string, params *quota.ScopeParameters) (bool, error) {
return f.reached, f.err
}
func (f *FakeQuotaService) DeleteByUser(c context.Context, userID int64) error {
return f.err
}

View File

@ -5,8 +5,9 @@ import (
)
type Service interface {
Add(ctx context.Context, cmd *StarDashboardCommand) error
Delete(ctx context.Context, cmd *UnstarDashboardCommand) error
IsStarredByUser(ctx context.Context, query *IsStarredByUserQuery) (bool, error)
GetByUser(ctx context.Context, cmd *GetUserStarsQuery) (*GetUserStarsResult, error)
Add(context.Context, *StarDashboardCommand) error
Delete(context.Context, *UnstarDashboardCommand) error
DeleteByUser(context.Context, int64) error
IsStarredByUser(context.Context, *IsStarredByUserQuery) (bool, error)
GetByUser(context.Context, *GetUserStarsQuery) (*GetUserStarsResult, error)
}

View File

@ -40,3 +40,7 @@ func (s *Service) IsStarredByUser(ctx context.Context, query *star.IsStarredByUs
func (s *Service) GetByUser(ctx context.Context, cmd *star.GetUserStarsQuery) (*star.GetUserStarsResult, error) {
return s.store.List(ctx, cmd)
}
func (s *Service) DeleteByUser(ctx context.Context, userID int64) error {
return s.store.DeleteByUser(ctx, userID)
}

View File

@ -9,10 +9,11 @@ import (
)
type store interface {
Get(ctx context.Context, query *star.IsStarredByUserQuery) (bool, error)
Insert(ctx context.Context, cmd *star.StarDashboardCommand) error
Delete(ctx context.Context, cmd *star.UnstarDashboardCommand) error
List(ctx context.Context, query *star.GetUserStarsQuery) (*star.GetUserStarsResult, error)
Get(context.Context, *star.IsStarredByUserQuery) (bool, error)
Insert(context.Context, *star.StarDashboardCommand) error
Delete(context.Context, *star.UnstarDashboardCommand) error
DeleteByUser(context.Context, int64) error
List(context.Context, *star.GetUserStarsQuery) (*star.GetUserStarsResult, error)
}
type sqlStore struct {
@ -55,6 +56,14 @@ func (s *sqlStore) Delete(ctx context.Context, cmd *star.UnstarDashboardCommand)
})
}
func (s *sqlStore) DeleteByUser(ctx context.Context, userID int64) error {
return s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
var rawSQL = "DELETE FROM star WHERE user_id = ?"
_, err := sess.Exec(rawSQL, userID)
return err
})
}
func (s *sqlStore) List(ctx context.Context, query *star.GetUserStarsQuery) (*star.GetUserStarsResult, error) {
userStars := make(map[int64]bool)
err := s.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {

View File

@ -56,5 +56,10 @@ func TestIntegrationUserStarsDataAccess(t *testing.T) {
require.False(t, isStarred)
})
})
t.Run("delete by user", func(t *testing.T) {
err := starStore.DeleteByUser(context.Background(), 1)
require.NoError(t, err)
})
})
}

View File

@ -28,6 +28,10 @@ func (f *FakeStarService) Delete(ctx context.Context, cmd *star.UnstarDashboardC
return f.ExpectedError
}
func (f *FakeStarService) DeleteByUser(ctx context.Context, userID int64) error {
return f.ExpectedError
}
func (f *FakeStarService) GetByUser(ctx context.Context, query *star.GetUserStarsQuery) (*star.GetUserStarsResult, error) {
return f.ExpectedUserStars, f.ExpectedError
}

View File

@ -22,3 +22,11 @@ func (t *TeamGuardianStoreImpl) GetTeamMembers(ctx context.Context, query models
return query.Result, nil
}
func (t *TeamGuardianStoreImpl) DeleteByUser(ctx context.Context, userID int64) error {
return t.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
var rawSQL = "DELETE FROM team_member WHERE user_id = ?"
_, err := sess.Exec(rawSQL, userID)
return err
})
}

View File

@ -15,3 +15,8 @@ func (t *TeamGuardianStoreMock) GetTeamMembers(ctx context.Context, query models
args := t.Called(ctx, query)
return args.Get(0).([]*models.TeamMemberDTO), args.Error(1)
}
func (t *TeamGuardianStoreMock) DeleteByUser(ctx context.Context, userID int64) error {
args := t.Called(ctx, userID)
return args.Get(0).(error)
}

View File

@ -44,3 +44,7 @@ func (s *Service) CanAdmin(ctx context.Context, orgId int64, teamId int64, user
return models.ErrNotAllowedToUpdateTeam
}
func (s *Service) DeleteByUser(ctx context.Context, userID int64) error {
return s.store.DeleteByUser(ctx, userID)
}

View File

@ -15,3 +15,8 @@ func (t *TeamGuardianMock) CanAdmin(ctx context.Context, orgId int64, teamId int
args := t.Called(ctx, orgId, teamId, user)
return args.Error(0)
}
func (t *TeamGuardianMock) DeleteByUser(context.Context, int64) error {
args := t.Called(context.Background(), 0)
return args.Error(0)
}

View File

@ -7,9 +7,11 @@ import (
)
type TeamGuardian interface {
CanAdmin(ctx context.Context, orgId int64, teamId int64, user *models.SignedInUser) error
CanAdmin(context.Context, int64, int64, *models.SignedInUser) error
DeleteByUser(context.Context, int64) error
}
type Store interface {
GetTeamMembers(ctx context.Context, query models.GetTeamMembersQuery) ([]*models.TeamMemberDTO, error)
GetTeamMembers(context.Context, models.GetTeamMembersQuery) ([]*models.TeamMemberDTO, error)
DeleteByUser(context.Context, int64) error
}

View File

@ -110,7 +110,7 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use
orgUser.Role = org.RoleType(setting.AutoAssignOrgRole)
}
}
_, err = s.orgService.InsertUser(ctx, &orgUser)
_, err = s.orgService.InsertOrgUser(ctx, &orgUser)
if err != nil {
// HERE ADD DELETE USER
return usr, err

View File

@ -0,0 +1,8 @@
package userauth
import "context"
type Service interface {
Delete(ctx context.Context, userID int64) error
DeleteToken(ctx context.Context, userID int64) error
}

View File

@ -0,0 +1,33 @@
package userauthimpl
import (
"context"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
)
type store interface {
Delete(context.Context, int64) error
DeleteToken(context.Context, int64) error
}
type sqlStore struct {
db db.DB
}
func (ss *sqlStore) Delete(ctx context.Context, userID int64) error {
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
var rawSQL = "DELETE FROM user_auth WHERE user_id = ?"
_, err := sess.Exec(rawSQL, userID)
return err
})
}
func (ss *sqlStore) DeleteToken(ctx context.Context, userID int64) error {
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
var rawSQL = "DELETE FROM user_auth_token WHERE user_id = ?"
_, err := sess.Exec(rawSQL, userID)
return err
})
}

View File

@ -0,0 +1,30 @@
package userauthimpl
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/require"
)
func TestIntegrationUserAuthDataAccess(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ss := sqlstore.InitTestDB(t)
userAuthStore := sqlStore{
db: ss,
}
t.Run("delete user auth", func(t *testing.T) {
err := userAuthStore.Delete(context.Background(), 1)
require.NoError(t, err)
})
t.Run("delete user auth token", func(t *testing.T) {
err := userAuthStore.DeleteToken(context.Background(), 1)
require.NoError(t, err)
})
}

View File

@ -0,0 +1,28 @@
package userauthimpl
import (
"context"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/services/userauth"
)
type Service struct {
store store
}
func ProvideService(db db.DB) userauth.Service {
return &Service{
store: &sqlStore{
db: db,
},
}
}
func (s *Service) Delete(ctx context.Context, userID int64) error {
return s.store.Delete(ctx, userID)
}
func (s *Service) DeleteToken(ctx context.Context, userID int64) error {
return s.store.DeleteToken(ctx, userID)
}

View File

@ -0,0 +1,37 @@
package userauthimpl
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
func TestUserAuthService(t *testing.T) {
userAuthStore := &FakeUserAuthStore{}
userAuthService := Service{
store: userAuthStore,
}
t.Run("delete user", func(t *testing.T) {
err := userAuthService.Delete(context.Background(), 1)
require.NoError(t, err)
})
t.Run("delete token", func(t *testing.T) {
err := userAuthService.DeleteToken(context.Background(), 1)
require.NoError(t, err)
})
}
type FakeUserAuthStore struct {
ExpectedError error
}
func (f *FakeUserAuthStore) Delete(ctx context.Context, userID int64) error {
return f.ExpectedError
}
func (f *FakeUserAuthStore) DeleteToken(ctx context.Context, userID int64) error {
return f.ExpectedError
}