SCIM: Add flag for rejecting non provisioned users from logging in (#108568)
add flag for rejecting non provisioned users from logging in
This commit is contained in:
@@ -84,8 +84,8 @@ var (
|
||||
|
||||
// StaticSCIMConfig represents the static SCIM configuration from config.ini
|
||||
type StaticSCIMConfig struct {
|
||||
AllowNonProvisionedUsers bool
|
||||
IsUserProvisioningEnabled bool
|
||||
RejectNonProvisionedUsers bool
|
||||
}
|
||||
|
||||
func ProvideUserSync(userService user.Service, userProtectionService login.UserProtectionService, authInfoService login.AuthInfoService,
|
||||
@@ -94,13 +94,13 @@ func ProvideUserSync(userService user.Service, userProtectionService login.UserP
|
||||
) *UserSync {
|
||||
scimSection := cfg.Raw.Section("auth.scim")
|
||||
staticConfig := &StaticSCIMConfig{
|
||||
AllowNonProvisionedUsers: scimSection.Key("allow_non_provisioned_users").MustBool(false),
|
||||
IsUserProvisioningEnabled: scimSection.Key("user_sync_enabled").MustBool(false),
|
||||
RejectNonProvisionedUsers: scimSection.Key("reject_non_provisioned_users").MustBool(false),
|
||||
}
|
||||
|
||||
return &UserSync{
|
||||
allowNonProvisionedUsers: staticConfig.AllowNonProvisionedUsers,
|
||||
isUserProvisioningEnabled: staticConfig.IsUserProvisioningEnabled,
|
||||
rejectNonProvisionedUsers: staticConfig.RejectNonProvisionedUsers,
|
||||
userService: userService,
|
||||
authInfoService: authInfoService,
|
||||
userProtectionService: userProtectionService,
|
||||
@@ -115,8 +115,8 @@ func ProvideUserSync(userService user.Service, userProtectionService login.UserP
|
||||
}
|
||||
|
||||
type UserSync struct {
|
||||
allowNonProvisionedUsers bool
|
||||
isUserProvisioningEnabled bool
|
||||
rejectNonProvisionedUsers bool
|
||||
userService user.Service
|
||||
authInfoService login.AuthInfoService
|
||||
userProtectionService login.UserProtectionService
|
||||
@@ -187,9 +187,13 @@ func (s *UserSync) ValidateUserProvisioningHook(ctx context.Context, currentIden
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reject non-provisioned users
|
||||
log.Error("Failed to access user, user is not provisioned")
|
||||
return errUserNotProvisioned.Errorf("user is not provisioned")
|
||||
// Reject non-provisioned users if configured to do so
|
||||
if s.shouldRejectNonProvisionedUsers(ctx, currentIdentity) {
|
||||
log.Error("Failed to authenticate user, user is not provisioned")
|
||||
return errUserNotProvisioned.Errorf("user is not provisioned")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UserSync) skipProvisioningValidation(ctx context.Context, currentIdentity *authn.Identity) bool {
|
||||
@@ -197,12 +201,10 @@ func (s *UserSync) skipProvisioningValidation(ctx context.Context, currentIdenti
|
||||
|
||||
// Use dynamic SCIM settings if available, otherwise fall back to static config
|
||||
effectiveUserSyncEnabled := s.isUserProvisioningEnabled
|
||||
effectiveAllowNonProvisionedUsers := s.allowNonProvisionedUsers
|
||||
|
||||
if s.scimUtil != nil {
|
||||
orgID := currentIdentity.GetOrgID()
|
||||
effectiveUserSyncEnabled = s.scimUtil.IsUserSyncEnabled(ctx, orgID, s.staticConfig.IsUserProvisioningEnabled)
|
||||
effectiveAllowNonProvisionedUsers = s.scimUtil.AreNonProvisionedUsersAllowed(ctx, orgID, s.staticConfig.AllowNonProvisionedUsers)
|
||||
}
|
||||
|
||||
if !effectiveUserSyncEnabled {
|
||||
@@ -210,11 +212,6 @@ func (s *UserSync) skipProvisioningValidation(ctx context.Context, currentIdenti
|
||||
return true
|
||||
}
|
||||
|
||||
if effectiveAllowNonProvisionedUsers {
|
||||
log.Debug("Non-provisioned users are allowed, skipping validation")
|
||||
return true
|
||||
}
|
||||
|
||||
if currentIdentity.AuthenticatedBy == login.GrafanaComAuthModule {
|
||||
log.Debug("User is authenticated via GrafanaComAuthModule, skipping validation")
|
||||
return true
|
||||
@@ -223,6 +220,17 @@ func (s *UserSync) skipProvisioningValidation(ctx context.Context, currentIdenti
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *UserSync) shouldRejectNonProvisionedUsers(ctx context.Context, currentIdentity *authn.Identity) bool {
|
||||
effectiveRejectNonProvisionedUsers := s.rejectNonProvisionedUsers
|
||||
|
||||
if s.scimUtil != nil {
|
||||
orgID := currentIdentity.GetOrgID()
|
||||
effectiveRejectNonProvisionedUsers = s.scimUtil.AreNonProvisionedUsersRejected(ctx, orgID, s.staticConfig.RejectNonProvisionedUsers)
|
||||
}
|
||||
|
||||
return effectiveRejectNonProvisionedUsers
|
||||
}
|
||||
|
||||
// SyncUserHook syncs a user with the database
|
||||
func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *authn.Request) error {
|
||||
ctx, span := s.tracer.Start(ctx, "user.sync.SyncUserHook")
|
||||
|
||||
@@ -1092,11 +1092,24 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "it should skip validation if allowedNonProvisionedUsers is enabled",
|
||||
desc: "it should skip validation if rejectNonProvisionedUsers is disabled",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = true
|
||||
userSyncService.rejectNonProvisionedUsers = false
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{
|
||||
ID: 1,
|
||||
IsProvisioned: false,
|
||||
},
|
||||
}
|
||||
userSyncService.authInfoService = &authinfotest.FakeService{
|
||||
ExpectedUserAuth: &login.UserAuth{
|
||||
UserId: 1,
|
||||
AuthModule: login.GenericOAuthModule,
|
||||
AuthId: "1",
|
||||
},
|
||||
}
|
||||
return userSyncService
|
||||
},
|
||||
identity: &authn.Identity{
|
||||
@@ -1111,7 +1124,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
desc: "it should skip validation if the user is authenticated via GrafanaComAuthModule",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = false
|
||||
userSyncService.rejectNonProvisionedUsers = true
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
return userSyncService
|
||||
},
|
||||
@@ -1127,7 +1140,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
desc: "it should fail to validate the identity with the provisioned user, unexpected error",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = false
|
||||
userSyncService.rejectNonProvisionedUsers = true
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{
|
||||
ExpectedError: errors.New("random error"),
|
||||
@@ -1148,7 +1161,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
desc: "it should fail to validate the identity with the provisioned user, no user found",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = false
|
||||
userSyncService.rejectNonProvisionedUsers = true
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{}
|
||||
return userSyncService
|
||||
@@ -1167,7 +1180,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
desc: "it should fail to validate the provisioned user.ExternalUID with the identity.ExternalUID - empty ExternalUID",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = false
|
||||
userSyncService.rejectNonProvisionedUsers = true
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{
|
||||
@@ -1198,7 +1211,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
desc: "it should fail to validate the provisioned user.ExternalUID with the identity.ExternalUID - different ExternalUID",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = false
|
||||
userSyncService.rejectNonProvisionedUsers = true
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{
|
||||
@@ -1230,7 +1243,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
desc: "it should successfully validate the provisioned user.ExternalUID with the identity.ExternalUID",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = false
|
||||
userSyncService.rejectNonProvisionedUsers = true
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{
|
||||
@@ -1259,10 +1272,10 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "it should failed to validate a non provisioned user when retrieved from the database",
|
||||
desc: "it should fail to validate a non provisioned user when configured to reject non provisioned users",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = false
|
||||
userSyncService.rejectNonProvisionedUsers = true
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{
|
||||
@@ -1290,6 +1303,38 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
},
|
||||
expectedErr: errUserNotProvisioned.Errorf("user is not provisioned"),
|
||||
},
|
||||
{
|
||||
desc: "it should skip to validate a non provisioned user when configured to allow non provisioned users",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.rejectNonProvisionedUsers = false
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{
|
||||
ID: 1,
|
||||
IsProvisioned: false,
|
||||
},
|
||||
}
|
||||
userSyncService.authInfoService = &authinfotest.FakeService{
|
||||
ExpectedUserAuth: &login.UserAuth{
|
||||
UserId: 1,
|
||||
AuthModule: login.SAMLAuthModule,
|
||||
AuthId: "1",
|
||||
ExternalUID: "random-external-uid",
|
||||
},
|
||||
}
|
||||
return userSyncService
|
||||
},
|
||||
identity: &authn.Identity{
|
||||
AuthenticatedBy: login.SAMLAuthModule,
|
||||
AuthID: "1",
|
||||
ExternalUID: "different-external-uid",
|
||||
ClientParams: authn.ClientParams{
|
||||
SyncUser: true,
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
desc: "ValidateProvisioning: DB ExternalUID is empty, Incoming ExternalUID is empty - expect mismatch (stricter logic)",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
@@ -1351,7 +1396,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
desc: "it should skip ExternalUID validation for a SAML-provisioned user accessed by a non-SAML method with an empty incoming ExternalUID",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = false
|
||||
userSyncService.rejectNonProvisionedUsers = false
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{
|
||||
@@ -1380,7 +1425,7 @@ func TestUserSync_ValidateUserProvisioningHook(t *testing.T) {
|
||||
desc: "it should fail validation when a provisioned user is accessed by SAML with an empty incoming ExternalUID",
|
||||
userSyncServiceSetup: func() *UserSync {
|
||||
userSyncService := initUserSyncService()
|
||||
userSyncService.allowNonProvisionedUsers = false
|
||||
userSyncService.rejectNonProvisionedUsers = true
|
||||
userSyncService.isUserProvisioningEnabled = true
|
||||
userSyncService.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{
|
||||
@@ -1425,10 +1470,10 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) {
|
||||
|
||||
// Mock SCIM utility for testing
|
||||
type mockSCIMUtil struct {
|
||||
userSyncEnabled bool
|
||||
nonProvisionedUsersAllowed bool
|
||||
shouldUseDynamicConfig bool
|
||||
shouldReturnError bool
|
||||
userSyncEnabled bool
|
||||
nonProvisionedUsersRejected bool
|
||||
shouldUseDynamicConfig bool
|
||||
shouldReturnError bool
|
||||
}
|
||||
|
||||
createMockSCIMUtil := func(mockCfg *mockSCIMUtil) *scimutil.SCIMUtil {
|
||||
@@ -1453,9 +1498,9 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) {
|
||||
"namespace": "default",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"enableUserSync": mockCfg.userSyncEnabled,
|
||||
"enableGroupSync": false, // Not used for this test
|
||||
"allowNonProvisionedUsers": mockCfg.nonProvisionedUsersAllowed,
|
||||
"enableUserSync": mockCfg.userSyncEnabled,
|
||||
"enableGroupSync": false, // Not used for this test
|
||||
"rejectNonProvisionedUsers": mockCfg.nonProvisionedUsersRejected,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1467,13 +1512,13 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
identity *authn.Identity
|
||||
staticConfig *StaticSCIMConfig
|
||||
mockSCIMUtil *mockSCIMUtil
|
||||
expectedUserSyncEnabled bool
|
||||
expectedNonProvisionedAllowed bool
|
||||
expectedError error
|
||||
name string
|
||||
identity *authn.Identity
|
||||
staticConfig *StaticSCIMConfig
|
||||
mockSCIMUtil *mockSCIMUtil
|
||||
expectedUserSyncEnabled bool
|
||||
expectedNonProvisionedRejected bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "SCIM util nil - uses static config",
|
||||
@@ -1483,11 +1528,11 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) {
|
||||
},
|
||||
staticConfig: &StaticSCIMConfig{
|
||||
IsUserProvisioningEnabled: true,
|
||||
AllowNonProvisionedUsers: false,
|
||||
RejectNonProvisionedUsers: false,
|
||||
},
|
||||
mockSCIMUtil: nil, // No SCIM util
|
||||
expectedUserSyncEnabled: true,
|
||||
expectedNonProvisionedAllowed: false,
|
||||
mockSCIMUtil: nil, // No SCIM util
|
||||
expectedUserSyncEnabled: true,
|
||||
expectedNonProvisionedRejected: false,
|
||||
},
|
||||
{
|
||||
name: "SCIM util with dynamic config - user sync enabled",
|
||||
@@ -1497,15 +1542,15 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) {
|
||||
},
|
||||
staticConfig: &StaticSCIMConfig{
|
||||
IsUserProvisioningEnabled: false, // Static disabled
|
||||
AllowNonProvisionedUsers: false,
|
||||
RejectNonProvisionedUsers: true,
|
||||
},
|
||||
mockSCIMUtil: &mockSCIMUtil{
|
||||
userSyncEnabled: true, // Dynamic enabled
|
||||
nonProvisionedUsersAllowed: true,
|
||||
shouldUseDynamicConfig: true,
|
||||
userSyncEnabled: true, // Dynamic enabled
|
||||
nonProvisionedUsersRejected: true,
|
||||
shouldUseDynamicConfig: true,
|
||||
},
|
||||
expectedUserSyncEnabled: true,
|
||||
expectedNonProvisionedAllowed: true,
|
||||
expectedUserSyncEnabled: true,
|
||||
expectedNonProvisionedRejected: true,
|
||||
},
|
||||
{
|
||||
name: "SCIM util with dynamic config - user sync disabled",
|
||||
@@ -1515,15 +1560,15 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) {
|
||||
},
|
||||
staticConfig: &StaticSCIMConfig{
|
||||
IsUserProvisioningEnabled: true, // Static enabled
|
||||
AllowNonProvisionedUsers: true,
|
||||
RejectNonProvisionedUsers: true,
|
||||
},
|
||||
mockSCIMUtil: &mockSCIMUtil{
|
||||
userSyncEnabled: false, // Dynamic disabled
|
||||
nonProvisionedUsersAllowed: false,
|
||||
shouldUseDynamicConfig: true,
|
||||
userSyncEnabled: false, // Dynamic disabled
|
||||
nonProvisionedUsersRejected: false,
|
||||
shouldUseDynamicConfig: true,
|
||||
},
|
||||
expectedUserSyncEnabled: false,
|
||||
expectedNonProvisionedAllowed: false,
|
||||
expectedUserSyncEnabled: false,
|
||||
expectedNonProvisionedRejected: false,
|
||||
},
|
||||
{
|
||||
name: "SCIM util with error - falls back to static config",
|
||||
@@ -1533,13 +1578,13 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) {
|
||||
},
|
||||
staticConfig: &StaticSCIMConfig{
|
||||
IsUserProvisioningEnabled: true,
|
||||
AllowNonProvisionedUsers: false,
|
||||
RejectNonProvisionedUsers: false,
|
||||
},
|
||||
mockSCIMUtil: &mockSCIMUtil{
|
||||
shouldReturnError: true,
|
||||
},
|
||||
expectedUserSyncEnabled: true,
|
||||
expectedNonProvisionedAllowed: false,
|
||||
expectedUserSyncEnabled: true,
|
||||
expectedNonProvisionedRejected: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1559,14 +1604,14 @@ func TestUserSync_SCIMUtilIntegration(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, tt.expectedUserSyncEnabled, userSyncEnabled, "User sync enabled mismatch")
|
||||
|
||||
// Test non-provisioned users allowed check
|
||||
var nonProvisionedAllowed bool
|
||||
// Test non-provisioned users rejected check
|
||||
var nonProvisionedReject bool
|
||||
if userSync.scimUtil != nil {
|
||||
nonProvisionedAllowed = userSync.scimUtil.AreNonProvisionedUsersAllowed(ctx, orgID, tt.staticConfig.AllowNonProvisionedUsers)
|
||||
nonProvisionedReject = userSync.scimUtil.AreNonProvisionedUsersRejected(ctx, orgID, tt.staticConfig.RejectNonProvisionedUsers)
|
||||
} else {
|
||||
nonProvisionedAllowed = tt.staticConfig.AllowNonProvisionedUsers
|
||||
nonProvisionedReject = tt.staticConfig.RejectNonProvisionedUsers
|
||||
}
|
||||
assert.Equal(t, tt.expectedNonProvisionedAllowed, nonProvisionedAllowed, "Non-provisioned users allowed mismatch")
|
||||
assert.Equal(t, tt.expectedNonProvisionedRejected, nonProvisionedReject, "Non-provisioned users rejected mismatch")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1765,7 +1810,7 @@ func TestUserSync_GetUsageStats(t *testing.T) {
|
||||
|
||||
func TestUserSync_SCIMLoginUsageStatSet(t *testing.T) {
|
||||
userSync := initUserSyncService()
|
||||
userSync.allowNonProvisionedUsers = false
|
||||
userSync.rejectNonProvisionedUsers = false
|
||||
userSync.isUserProvisioningEnabled = true
|
||||
userSync.userService = &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{
|
||||
|
||||
@@ -44,23 +44,23 @@ func (s *SCIMUtil) IsUserSyncEnabled(ctx context.Context, orgID int64, staticEna
|
||||
return staticEnabled
|
||||
}
|
||||
|
||||
// AreNonProvisionedUsersAllowed checks if non-provisioned users are allowed using dynamic configuration with static fallback
|
||||
func (s *SCIMUtil) AreNonProvisionedUsersAllowed(ctx context.Context, orgID int64, staticAllowed bool) bool {
|
||||
// AreNonProvisionedUsersRejected checks if non-provisioned users are rejected using dynamic configuration with static fallback
|
||||
func (s *SCIMUtil) AreNonProvisionedUsersRejected(ctx context.Context, orgID int64, staticRejected bool) bool {
|
||||
if s.k8sClient == nil {
|
||||
s.logger.Debug("K8s client not configured, using static SCIM config for non-provisioned users")
|
||||
return staticAllowed
|
||||
return staticRejected
|
||||
}
|
||||
|
||||
dynamicAllowed, dynamicConfigFetched := s.fetchDynamicSCIMSetting(ctx, orgID, "allowNonProvisionedUsers")
|
||||
dynamicRejected, dynamicConfigFetched := s.fetchDynamicSCIMSetting(ctx, orgID, "rejectNonProvisionedUsers")
|
||||
|
||||
if dynamicConfigFetched {
|
||||
s.logger.Debug("Using dynamic SCIM config for user sync", "orgID", orgID, "enabled", dynamicAllowed)
|
||||
return dynamicAllowed
|
||||
s.logger.Debug("Using dynamic SCIM config for user sync", "orgID", orgID, "enabled", dynamicRejected)
|
||||
return dynamicRejected
|
||||
}
|
||||
|
||||
// Fallback to static config if dynamic config wasn't fetched successfully
|
||||
s.logger.Debug("Using static SCIM config for user sync", "orgID", orgID, "enabled", staticAllowed)
|
||||
return staticAllowed
|
||||
s.logger.Debug("Using static SCIM config for user sync", "orgID", orgID, "enabled", staticRejected)
|
||||
return staticRejected
|
||||
}
|
||||
|
||||
// fetchDynamicSCIMSetting attempts to retrieve a specific dynamic SCIM configuration setting
|
||||
@@ -82,8 +82,8 @@ func (s *SCIMUtil) fetchDynamicSCIMSetting(ctx context.Context, orgID int64, set
|
||||
enabled = scimConfig.EnableUserSync
|
||||
case "group":
|
||||
enabled = scimConfig.EnableGroupSync
|
||||
case "allowNonProvisionedUsers":
|
||||
enabled = scimConfig.AllowNonProvisionedUsers
|
||||
case "rejectNonProvisionedUsers":
|
||||
enabled = scimConfig.RejectNonProvisionedUsers
|
||||
default:
|
||||
s.logger.Error("Invalid setting type provided to fetchDynamicSCIMSetting", "settingType", settingType)
|
||||
return false, false
|
||||
@@ -108,9 +108,9 @@ func (s *SCIMUtil) getOrgSCIMConfig(ctx context.Context, orgID int64) (*SCIMConf
|
||||
|
||||
// SCIMConfigSpec represents the spec part of a SCIMConfig resource
|
||||
type SCIMConfigSpec struct {
|
||||
EnableUserSync bool `json:"enableUserSync"`
|
||||
EnableGroupSync bool `json:"enableGroupSync"`
|
||||
AllowNonProvisionedUsers bool `json:"allowNonProvisionedUsers"`
|
||||
EnableUserSync bool `json:"enableUserSync"`
|
||||
EnableGroupSync bool `json:"enableGroupSync"`
|
||||
RejectNonProvisionedUsers bool `json:"rejectNonProvisionedUsers"`
|
||||
}
|
||||
|
||||
// unstructuredToSCIMConfig converts an unstructured object to a SCIMConfigSpec
|
||||
@@ -130,11 +130,11 @@ func (s *SCIMUtil) unstructuredToSCIMConfig(obj *unstructured.Unstructured) (*SC
|
||||
|
||||
enableUserSync, _, _ := unstructured.NestedBool(spec, "enableUserSync")
|
||||
enableGroupSync, _, _ := unstructured.NestedBool(spec, "enableGroupSync")
|
||||
allowNonProvisionedUsers, _, _ := unstructured.NestedBool(spec, "allowNonProvisionedUsers")
|
||||
rejectNonProvisionedUsers, _, _ := unstructured.NestedBool(spec, "rejectNonProvisionedUsers")
|
||||
|
||||
return &SCIMConfigSpec{
|
||||
EnableUserSync: enableUserSync,
|
||||
EnableGroupSync: enableGroupSync,
|
||||
AllowNonProvisionedUsers: allowNonProvisionedUsers,
|
||||
EnableUserSync: enableUserSync,
|
||||
EnableGroupSync: enableGroupSync,
|
||||
RejectNonProvisionedUsers: rejectNonProvisionedUsers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -213,33 +213,33 @@ func TestSCIMUtil_IsUserSyncEnabled(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) {
|
||||
func TestSCIMUtil_AreNonProvisionedUsersRejected(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
orgID := int64(1)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
k8sClient client.K8sHandler
|
||||
staticAllowed bool
|
||||
staticRejected bool
|
||||
expectedResult bool
|
||||
setupMock func(*MockK8sHandler)
|
||||
}{
|
||||
{
|
||||
name: "k8s client nil - returns static config",
|
||||
k8sClient: nil,
|
||||
staticAllowed: true,
|
||||
staticRejected: true,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "k8s client nil - returns static config false",
|
||||
k8sClient: nil,
|
||||
staticAllowed: false,
|
||||
staticRejected: false,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "k8s client error - falls back to static config",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticAllowed: true,
|
||||
name: "k8s client error - falls back to static config",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticRejected: true,
|
||||
setupMock: func(mockHandler *MockK8sHandler) {
|
||||
mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything).
|
||||
Return(nil, errors.New("k8s error"))
|
||||
@@ -247,9 +247,9 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) {
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "dynamic config user sync enabled - non-provisioned users allowed",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticAllowed: false,
|
||||
name: "dynamic config user sync enabled - non-provisioned users rejected",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticRejected: false,
|
||||
setupMock: func(mockHandler *MockK8sHandler) {
|
||||
obj := createMockSCIMConfigWithNonProvisioned(true, false, true)
|
||||
mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything).
|
||||
@@ -258,9 +258,9 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) {
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "dynamic config user sync disabled - non-provisioned users not allowed",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticAllowed: true,
|
||||
name: "dynamic config user sync disabled - non-provisioned users allowed",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticRejected: true,
|
||||
setupMock: func(mockHandler *MockK8sHandler) {
|
||||
obj := createMockSCIMConfigWithNonProvisioned(false, true, false)
|
||||
mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything).
|
||||
@@ -269,9 +269,9 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) {
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "dynamic config both settings disabled - non-provisioned users not allowed",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticAllowed: true,
|
||||
name: "dynamic config both settings disabled - non-provisioned users allowed",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticRejected: true,
|
||||
setupMock: func(mockHandler *MockK8sHandler) {
|
||||
obj := createMockSCIMConfigWithNonProvisioned(false, false, false)
|
||||
mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything).
|
||||
@@ -280,9 +280,9 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) {
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "dynamic config both settings enabled - non-provisioned users allowed",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticAllowed: false,
|
||||
name: "dynamic config both settings enabled - non-provisioned users rejected",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
staticRejected: false,
|
||||
setupMock: func(mockHandler *MockK8sHandler) {
|
||||
obj := createMockSCIMConfigWithNonProvisioned(true, true, true)
|
||||
mockHandler.On("Get", ctx, "default", orgID, metav1.GetOptions{}, mock.Anything).
|
||||
@@ -299,7 +299,7 @@ func TestSCIMUtil_AreNonProvisionedUsersAllowed(t *testing.T) {
|
||||
}
|
||||
|
||||
util := NewSCIMUtil(tt.k8sClient)
|
||||
result := util.AreNonProvisionedUsersAllowed(ctx, orgID, tt.staticAllowed)
|
||||
result := util.AreNonProvisionedUsersRejected(ctx, orgID, tt.staticRejected)
|
||||
|
||||
assert.Equal(t, tt.expectedResult, result)
|
||||
|
||||
@@ -449,9 +449,9 @@ func TestSCIMUtil_fetchDynamicSCIMSetting(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allowNonProvisionedUsers setting enabled",
|
||||
name: "rejectNonProvisionedUsers setting enabled",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
settingType: "allowNonProvisionedUsers",
|
||||
settingType: "rejectNonProvisionedUsers",
|
||||
expectedEnabled: true,
|
||||
expectedDynamicFetched: true,
|
||||
setupMock: func(mockHandler *MockK8sHandler) {
|
||||
@@ -461,9 +461,9 @@ func TestSCIMUtil_fetchDynamicSCIMSetting(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allowNonProvisionedUsers setting disabled",
|
||||
name: "rejectNonProvisionedUsers setting disabled",
|
||||
k8sClient: &MockK8sHandler{},
|
||||
settingType: "allowNonProvisionedUsers",
|
||||
settingType: "rejectNonProvisionedUsers",
|
||||
expectedEnabled: false,
|
||||
expectedDynamicFetched: true,
|
||||
setupMock: func(mockHandler *MockK8sHandler) {
|
||||
@@ -570,36 +570,36 @@ func TestSCIMUtil_unstructuredToSCIMConfig(t *testing.T) {
|
||||
name: "valid object with both settings enabled",
|
||||
obj: createMockSCIMConfig(true, true),
|
||||
expectedSpec: SCIMConfigSpec{
|
||||
EnableUserSync: true,
|
||||
EnableGroupSync: true,
|
||||
AllowNonProvisionedUsers: false,
|
||||
EnableUserSync: true,
|
||||
EnableGroupSync: true,
|
||||
RejectNonProvisionedUsers: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid object with both settings disabled",
|
||||
obj: createMockSCIMConfig(false, false),
|
||||
expectedSpec: SCIMConfigSpec{
|
||||
EnableUserSync: false,
|
||||
EnableGroupSync: false,
|
||||
AllowNonProvisionedUsers: false,
|
||||
EnableUserSync: false,
|
||||
EnableGroupSync: false,
|
||||
RejectNonProvisionedUsers: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid object with mixed settings",
|
||||
obj: createMockSCIMConfig(true, false),
|
||||
expectedSpec: SCIMConfigSpec{
|
||||
EnableUserSync: true,
|
||||
EnableGroupSync: false,
|
||||
AllowNonProvisionedUsers: false,
|
||||
EnableUserSync: true,
|
||||
EnableGroupSync: false,
|
||||
RejectNonProvisionedUsers: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid object with allowNonProvisionedUsers enabled",
|
||||
name: "valid object with rejectNonProvisionedUsers enabled",
|
||||
obj: createMockSCIMConfigWithNonProvisioned(false, false, true),
|
||||
expectedSpec: SCIMConfigSpec{
|
||||
EnableUserSync: false,
|
||||
EnableGroupSync: false,
|
||||
AllowNonProvisionedUsers: true,
|
||||
EnableUserSync: false,
|
||||
EnableGroupSync: false,
|
||||
RejectNonProvisionedUsers: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -641,7 +641,7 @@ func createMockSCIMConfig(userSyncEnabled, groupSyncEnabled bool) *unstructured.
|
||||
}
|
||||
|
||||
// Helper function to create a mock SCIMConfig unstructured object with non-provisioned users setting
|
||||
func createMockSCIMConfigWithNonProvisioned(userSyncEnabled, groupSyncEnabled, allowNonProvisionedUsers bool) *unstructured.Unstructured {
|
||||
func createMockSCIMConfigWithNonProvisioned(userSyncEnabled, groupSyncEnabled, rejectNonProvisionedUsers bool) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "scim.grafana.com/v0alpha1",
|
||||
@@ -651,9 +651,9 @@ func createMockSCIMConfigWithNonProvisioned(userSyncEnabled, groupSyncEnabled, a
|
||||
"namespace": "default",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"enableUserSync": userSyncEnabled,
|
||||
"enableGroupSync": groupSyncEnabled,
|
||||
"allowNonProvisionedUsers": allowNonProvisionedUsers,
|
||||
"enableUserSync": userSyncEnabled,
|
||||
"enableGroupSync": groupSyncEnabled,
|
||||
"rejectNonProvisionedUsers": rejectNonProvisionedUsers,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -676,9 +676,9 @@ func TestSCIMUtil_Integration(t *testing.T) {
|
||||
userSyncEnabled := util.IsUserSyncEnabled(ctx, orgID, false)
|
||||
assert.True(t, userSyncEnabled)
|
||||
|
||||
// Test non-provisioned users allowed
|
||||
nonProvisionedAllowed := util.AreNonProvisionedUsersAllowed(ctx, orgID, false)
|
||||
assert.True(t, nonProvisionedAllowed)
|
||||
// Test non-provisioned users rejected
|
||||
nonProvisionedRejected := util.AreNonProvisionedUsersRejected(ctx, orgID, false)
|
||||
assert.True(t, nonProvisionedRejected)
|
||||
|
||||
mockClient.AssertExpectations(t)
|
||||
})
|
||||
@@ -695,8 +695,8 @@ func TestSCIMUtil_Integration(t *testing.T) {
|
||||
assert.True(t, userSyncEnabled)
|
||||
|
||||
// Test non-provisioned users falls back to static config
|
||||
nonProvisionedAllowed := util.AreNonProvisionedUsersAllowed(ctx, orgID, true)
|
||||
assert.True(t, nonProvisionedAllowed)
|
||||
nonProvisionedRejected := util.AreNonProvisionedUsersRejected(ctx, orgID, true)
|
||||
assert.True(t, nonProvisionedRejected)
|
||||
|
||||
mockClient.AssertExpectations(t)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user