From 63f1c3031317fd896926901805fa5b85cf9ef35b Mon Sep 17 00:00:00 2001 From: Misi Date: Tue, 12 Mar 2024 09:35:13 +0100 Subject: [PATCH] Auth: Set the default org after User login (#83918) * poc * add logger, skip hook when user is not assigned to default org * Add tests, move to hook folder * docs * Skip for OrgId < 1 * Address feedback * Update docs/sources/setup-grafana/configure-grafana/_index.md * lint * Move the hook to org_sync.go * Update pkg/services/authn/authnimpl/sync/org_sync.go * Handle the case when GetUserOrgList returns error --------- Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> Co-authored-by: Karl Persson --- conf/defaults.ini | 3 + conf/sample.ini | 3 + .../setup-grafana/configure-grafana/_index.md | 4 + pkg/services/authn/authnimpl/service.go | 4 +- pkg/services/authn/authnimpl/sync/org_sync.go | 69 ++- .../authn/authnimpl/sync/org_sync_test.go | 96 ++++ pkg/services/user/user.go | 1 + pkg/services/user/usertest/mock.go | 511 ++++++++++++++++++ pkg/setting/setting.go | 2 + 9 files changed, 687 insertions(+), 6 deletions(-) create mode 100644 pkg/services/user/usertest/mock.go diff --git a/conf/defaults.ini b/conf/defaults.ini index 8ac107137df750a..568af953c47f77b 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -473,6 +473,9 @@ auto_assign_org_role = Viewer # Require email validation before sign up completes verify_email_enabled = false +# Redirect to default OrgId after login +login_default_org_id = + # Background text for the user field on the login page login_hint = email or username password_hint = password diff --git a/conf/sample.ini b/conf/sample.ini index 8ceb3827db8cf60..29dfa4fcb96c2b1 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -449,6 +449,9 @@ # Require email validation before sign up completes ;verify_email_enabled = false +# Redirect to default OrgId after login +;login_default_org_id = + # Background text for the user field on the login page ;login_hint = email or username ;password_hint = password diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 32ca4c9a5fe589c..6508a24bdc27a67 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -820,6 +820,10 @@ The available options are `Viewer` (default), `Admin`, `Editor`, and `None`. For Require email validation before sign up completes or when updating a user email address. Default is `false`. +### login_default_org_id + +Set the default organization for users when they sign in. The default is `-1`. + ### login_hint Text used as placeholder text on login page for login/username input. diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 70d81fc53aed98d..8ebb9f93f41722a 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -147,7 +147,7 @@ func ProvideService( // FIXME (jguer): move to User package userSyncService := sync.ProvideUserSync(userService, userProtectionService, authInfoService, quotaService) - orgUserSyncService := sync.ProvideOrgSync(userService, orgService, accessControlService) + orgUserSyncService := sync.ProvideOrgSync(userService, orgService, accessControlService, cfg) s.RegisterPostAuthHook(userSyncService.SyncUserHook, 10) s.RegisterPostAuthHook(userSyncService.EnableUserHook, 20) s.RegisterPostAuthHook(orgUserSyncService.SyncOrgRolesHook, 30) @@ -162,6 +162,8 @@ func ProvideService( s.RegisterPostAuthHook(rbacSync.SyncPermissionsHook, 120) + s.RegisterPostAuthHook(orgUserSyncService.SetDefaultOrgHook, 130) + return s } diff --git a/pkg/services/authn/authnimpl/sync/org_sync.go b/pkg/services/authn/authnimpl/sync/org_sync.go index 43e1bbf0249ee05..fcc552646397177 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync.go +++ b/pkg/services/authn/authnimpl/sync/org_sync.go @@ -3,6 +3,7 @@ package sync import ( "context" "errors" + "fmt" "sort" "github.com/grafana/grafana/pkg/infra/log" @@ -11,16 +12,18 @@ import ( "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" ) -func ProvideOrgSync(userService user.Service, orgService org.Service, accessControl accesscontrol.Service) *OrgSync { - return &OrgSync{userService, orgService, accessControl, log.New("org.sync")} +func ProvideOrgSync(userService user.Service, orgService org.Service, accessControl accesscontrol.Service, cfg *setting.Cfg) *OrgSync { + return &OrgSync{userService, orgService, accessControl, cfg, log.New("org.sync")} } type OrgSync struct { userService user.Service orgService org.Service accessControl accesscontrol.Service + cfg *setting.Cfg log log.Logger } @@ -72,7 +75,7 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a // update role cmd := &org.UpdateOrgUserCommand{OrgID: orga.OrgID, UserID: userID, Role: extRole} if err := s.orgService.UpdateOrgUser(ctx, cmd); err != nil { - s.log.FromContext(ctx).Error("Failed to update active org user", "id", id.ID, "error", err) + ctxLogger.Error("Failed to update active org user", "id", id.ID, "error", err) return err } } @@ -90,7 +93,7 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a cmd := &org.AddOrgUserCommand{UserID: userID, Role: orgRole, OrgID: orgId} err := s.orgService.AddOrgUser(ctx, cmd) if err != nil && !errors.Is(err, org.ErrOrgNotFound) { - s.log.FromContext(ctx).Error("Failed to update active org for user", "id", id.ID, "error", err) + ctxLogger.Error("Failed to update active org for user", "id", id.ID, "error", err) return err } } @@ -100,7 +103,7 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a ctxLogger.Debug("Removing user's organization membership as part of syncing with OAuth login", "id", id.ID, "orgId", orgID) cmd := &org.RemoveOrgUserCommand{OrgID: orgID, UserID: userID} if err := s.orgService.RemoveOrgUser(ctx, cmd); err != nil { - s.log.FromContext(ctx).Error("Failed to remove user from org", "id", id.ID, "orgId", orgID, "error", err) + ctxLogger.Error("Failed to remove user from org", "id", id.ID, "orgId", orgID, "error", err) if errors.Is(err, org.ErrLastOrgAdmin) { continue } @@ -128,3 +131,59 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a return nil } + +func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn.Identity, r *authn.Request) error { + if s.cfg.LoginDefaultOrgId < 1 || currentIdentity == nil { + return nil + } + + ctxLogger := s.log.FromContext(ctx) + + namespace, identifier := currentIdentity.GetNamespacedID() + if namespace != identity.NamespaceUser { + ctxLogger.Debug("Skipping default org sync, not a user", "namespace", namespace) + return nil + } + + userID, err := identity.IntIdentifier(namespace, identifier) + if err != nil { + ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "namespace", namespace, "err", err) + return nil + } + + hasAssignedToOrg, err := s.validateUsingOrg(ctx, userID, s.cfg.LoginDefaultOrgId) + if err != nil { + ctxLogger.Error("Skipping default org sync, failed to validate user's organizations", "id", currentIdentity.ID, "err", err) + return nil + } + + if !hasAssignedToOrg { + ctxLogger.Debug("Skipping default org sync, user is not assigned to org", "id", currentIdentity.ID, "org", s.cfg.LoginDefaultOrgId) + return nil + } + + cmd := user.SetUsingOrgCommand{UserID: userID, OrgID: s.cfg.LoginDefaultOrgId} + if err := s.userService.SetUsingOrg(ctx, &cmd); err != nil { + ctxLogger.Error("Failed to set default org", "id", currentIdentity.ID, "err", err) + return err + } + + return nil +} + +func (s *OrgSync) validateUsingOrg(ctx context.Context, userID int64, orgID int64) (bool, error) { + query := org.GetUserOrgListQuery{UserID: userID} + + result, err := s.orgService.GetUserOrgList(ctx, &query) + if err != nil { + return false, fmt.Errorf("failed to get user's organizations: %w", err) + } + + // validate that the org id in the list + for _, other := range result { + if other.OrgID == orgID { + return true, nil + } + } + return false, nil +} diff --git a/pkg/services/authn/authnimpl/sync/org_sync_test.go b/pkg/services/authn/authnimpl/sync/org_sync_test.go index 00bbbf5b1a3126f..9288f4c1958a2c0 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/org_sync_test.go @@ -2,9 +2,11 @@ package sync import ( "context" + "fmt" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models/roletype" @@ -16,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user/usertest" + "github.com/grafana/grafana/pkg/setting" ) func TestOrgSync_SyncOrgRolesHook(t *testing.T) { @@ -124,3 +127,96 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { }) } } + +func TestOrgSync_SetDefaultOrgHook(t *testing.T) { + testCases := []struct { + name string + defaultOrgSetting int64 + identity *authn.Identity + setupMock func(*usertest.MockService, *orgtest.FakeOrgService) + + wantErr bool + }{ + { + name: "should set default org", + defaultOrgSetting: 2, + identity: &authn.Identity{ID: "user:1"}, + setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { + userService.On("SetUsingOrg", mock.Anything, mock.MatchedBy(func(cmd *user.SetUsingOrgCommand) bool { + return cmd.UserID == 1 && cmd.OrgID == 2 + })).Return(nil) + }, + }, + { + name: "should skip setting the default org when default org is not set", + defaultOrgSetting: -1, + identity: &authn.Identity{ID: "user:1"}, + }, + { + name: "should skip setting the default org when identity is nil", + defaultOrgSetting: -1, + identity: nil, + }, + { + name: "should skip setting the default org when identity is not a user", + defaultOrgSetting: 2, + identity: &authn.Identity{ID: "service-account:1"}, + }, + { + name: "should skip setting the default org when user id is not valid", + defaultOrgSetting: 2, + identity: &authn.Identity{ID: "user:invalid"}, + }, + { + name: "should skip setting the default org when user is not allowed to use the configured default org", + defaultOrgSetting: 3, + identity: &authn.Identity{ID: "user:1"}, + }, + { + name: "should skip setting the default org when validateUsingOrg returns error", + defaultOrgSetting: 2, + identity: &authn.Identity{ID: "user:1"}, + setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { + orgService.ExpectedError = fmt.Errorf("error") + }, + }, + { + name: "should return error when the user org update was unsuccessful", + defaultOrgSetting: 2, + identity: &authn.Identity{ID: "user:1"}, + setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { + userService.On("SetUsingOrg", mock.Anything, mock.Anything).Return(fmt.Errorf("error")) + }, + wantErr: true, + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + cfg := setting.NewCfg() + cfg.LoginDefaultOrgId = tt.defaultOrgSetting + + userService := &usertest.MockService{} + defer userService.AssertExpectations(t) + + orgService := &orgtest.FakeOrgService{ + ExpectedUserOrgDTO: []*org.UserOrgDTO{{OrgID: 2}}, + } + + if tt.setupMock != nil { + tt.setupMock(userService, orgService) + } + + s := &OrgSync{ + userService: userService, + orgService: orgService, + accessControl: actest.FakeService{}, + log: log.NewNopLogger(), + cfg: cfg, + } + + if err := s.SetDefaultOrgHook(context.Background(), tt.identity, nil); (err != nil) != tt.wantErr { + t.Errorf("OrgSync.SetDefaultOrgHook() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/services/user/user.go b/pkg/services/user/user.go index 66008e7cd0176ce..ab8555ec508e75c 100644 --- a/pkg/services/user/user.go +++ b/pkg/services/user/user.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/registry" ) +//go:generate mockery --name Service --structname MockService --outpkg usertest --filename mock.go --output ./usertest/ type Service interface { registry.ProvidesUsageStats Create(context.Context, *CreateUserCommand) (*User, error) diff --git a/pkg/services/user/usertest/mock.go b/pkg/services/user/usertest/mock.go new file mode 100644 index 000000000000000..590a66c9b08e31f --- /dev/null +++ b/pkg/services/user/usertest/mock.go @@ -0,0 +1,511 @@ +// Code generated by mockery v2.40.1. DO NOT EDIT. + +package usertest + +import ( + context "context" + + user "github.com/grafana/grafana/pkg/services/user" + mock "github.com/stretchr/testify/mock" +) + +// MockService is an autogenerated mock type for the Service type +type MockService struct { + mock.Mock +} + +// BatchDisableUsers provides a mock function with given fields: _a0, _a1 +func (_m *MockService) BatchDisableUsers(_a0 context.Context, _a1 *user.BatchDisableUsersCommand) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for BatchDisableUsers") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *user.BatchDisableUsersCommand) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ChangePassword provides a mock function with given fields: _a0, _a1 +func (_m *MockService) ChangePassword(_a0 context.Context, _a1 *user.ChangeUserPasswordCommand) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ChangePassword") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *user.ChangeUserPasswordCommand) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Create provides a mock function with given fields: _a0, _a1 +func (_m *MockService) Create(_a0 context.Context, _a1 *user.CreateUserCommand) (*user.User, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 *user.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *user.CreateUserCommand) (*user.User, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *user.CreateUserCommand) *user.User); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.User) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *user.CreateUserCommand) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateServiceAccount provides a mock function with given fields: _a0, _a1 +func (_m *MockService) CreateServiceAccount(_a0 context.Context, _a1 *user.CreateUserCommand) (*user.User, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for CreateServiceAccount") + } + + var r0 *user.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *user.CreateUserCommand) (*user.User, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *user.CreateUserCommand) *user.User); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.User) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *user.CreateUserCommand) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Delete provides a mock function with given fields: _a0, _a1 +func (_m *MockService) Delete(_a0 context.Context, _a1 *user.DeleteUserCommand) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *user.DeleteUserCommand) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Disable provides a mock function with given fields: _a0, _a1 +func (_m *MockService) Disable(_a0 context.Context, _a1 *user.DisableUserCommand) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Disable") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *user.DisableUserCommand) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetByEmail provides a mock function with given fields: _a0, _a1 +func (_m *MockService) GetByEmail(_a0 context.Context, _a1 *user.GetUserByEmailQuery) (*user.User, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetByEmail") + } + + var r0 *user.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByEmailQuery) (*user.User, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByEmailQuery) *user.User); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.User) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *user.GetUserByEmailQuery) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetByID provides a mock function with given fields: _a0, _a1 +func (_m *MockService) GetByID(_a0 context.Context, _a1 *user.GetUserByIDQuery) (*user.User, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetByID") + } + + var r0 *user.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByIDQuery) (*user.User, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByIDQuery) *user.User); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.User) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *user.GetUserByIDQuery) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetByLogin provides a mock function with given fields: _a0, _a1 +func (_m *MockService) GetByLogin(_a0 context.Context, _a1 *user.GetUserByLoginQuery) (*user.User, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetByLogin") + } + + var r0 *user.User + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByLoginQuery) (*user.User, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserByLoginQuery) *user.User); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.User) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *user.GetUserByLoginQuery) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetProfile provides a mock function with given fields: _a0, _a1 +func (_m *MockService) GetProfile(_a0 context.Context, _a1 *user.GetUserProfileQuery) (*user.UserProfileDTO, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetProfile") + } + + var r0 *user.UserProfileDTO + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserProfileQuery) (*user.UserProfileDTO, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *user.GetUserProfileQuery) *user.UserProfileDTO); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.UserProfileDTO) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *user.GetUserProfileQuery) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetSignedInUser provides a mock function with given fields: _a0, _a1 +func (_m *MockService) GetSignedInUser(_a0 context.Context, _a1 *user.GetSignedInUserQuery) (*user.SignedInUser, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetSignedInUser") + } + + var r0 *user.SignedInUser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *user.GetSignedInUserQuery) (*user.SignedInUser, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *user.GetSignedInUserQuery) *user.SignedInUser); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.SignedInUser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *user.GetSignedInUserQuery) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetSignedInUserWithCacheCtx provides a mock function with given fields: _a0, _a1 +func (_m *MockService) GetSignedInUserWithCacheCtx(_a0 context.Context, _a1 *user.GetSignedInUserQuery) (*user.SignedInUser, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetSignedInUserWithCacheCtx") + } + + var r0 *user.SignedInUser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *user.GetSignedInUserQuery) (*user.SignedInUser, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *user.GetSignedInUserQuery) *user.SignedInUser); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.SignedInUser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *user.GetSignedInUserQuery) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetUsageStats provides a mock function with given fields: ctx +func (_m *MockService) GetUsageStats(ctx context.Context) map[string]interface{} { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetUsageStats") + } + + var r0 map[string]interface{} + if rf, ok := ret.Get(0).(func(context.Context) map[string]interface{}); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]interface{}) + } + } + + return r0 +} + +// NewAnonymousSignedInUser provides a mock function with given fields: _a0 +func (_m *MockService) NewAnonymousSignedInUser(_a0 context.Context) (*user.SignedInUser, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for NewAnonymousSignedInUser") + } + + var r0 *user.SignedInUser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*user.SignedInUser, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) *user.SignedInUser); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.SignedInUser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Search provides a mock function with given fields: _a0, _a1 +func (_m *MockService) Search(_a0 context.Context, _a1 *user.SearchUsersQuery) (*user.SearchUserQueryResult, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Search") + } + + var r0 *user.SearchUserQueryResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *user.SearchUsersQuery) (*user.SearchUserQueryResult, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *user.SearchUsersQuery) *user.SearchUserQueryResult); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*user.SearchUserQueryResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *user.SearchUsersQuery) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetUserHelpFlag provides a mock function with given fields: _a0, _a1 +func (_m *MockService) SetUserHelpFlag(_a0 context.Context, _a1 *user.SetUserHelpFlagCommand) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SetUserHelpFlag") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *user.SetUserHelpFlagCommand) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetUsingOrg provides a mock function with given fields: _a0, _a1 +func (_m *MockService) SetUsingOrg(_a0 context.Context, _a1 *user.SetUsingOrgCommand) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SetUsingOrg") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *user.SetUsingOrgCommand) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Update provides a mock function with given fields: _a0, _a1 +func (_m *MockService) Update(_a0 context.Context, _a1 *user.UpdateUserCommand) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *user.UpdateUserCommand) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateLastSeenAt provides a mock function with given fields: _a0, _a1 +func (_m *MockService) UpdateLastSeenAt(_a0 context.Context, _a1 *user.UpdateUserLastSeenAtCommand) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for UpdateLastSeenAt") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *user.UpdateUserLastSeenAtCommand) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdatePermissions provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockService) UpdatePermissions(_a0 context.Context, _a1 int64, _a2 bool) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for UpdatePermissions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, bool) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMockService creates a new instance of MockService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockService(t interface { + mock.TestingT + Cleanup(func()) +}) *MockService { + mock := &MockService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 5b70904b2b13d67..2286d1b74343303 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -411,6 +411,7 @@ type Cfg struct { AutoAssignOrg bool AutoAssignOrgId int AutoAssignOrgRole string + LoginDefaultOrgId int64 OAuthSkipOrgRoleUpdateSync bool // ExpressionsEnabled specifies whether expressions are enabled. @@ -1658,6 +1659,7 @@ func readUserSettings(iniFile *ini.File, cfg *Cfg) error { cfg.AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true) cfg.AutoAssignOrg = users.Key("auto_assign_org").MustBool(true) cfg.AutoAssignOrgId = users.Key("auto_assign_org_id").MustInt(1) + cfg.LoginDefaultOrgId = users.Key("login_default_org_id").MustInt64(-1) cfg.AutoAssignOrgRole = users.Key("auto_assign_org_role").In( string(roletype.RoleViewer), []string{ string(roletype.RoleNone),