diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 57e8af0c2..5003c4c70 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -78,10 +78,18 @@ class DashboardController < ActionController::Base
WHATSAPP_CONFIGURATION_ID: GlobalConfigService.load('WHATSAPP_CONFIGURATION_ID', ''),
IS_ENTERPRISE: ChatwootApp.enterprise?,
AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''),
- GIT_SHA: GIT_HASH
+ GIT_SHA: GIT_HASH,
+ ALLOWED_LOGIN_METHODS: allowed_login_methods
}
end
+ def allowed_login_methods
+ methods = ['email']
+ methods << 'google_oauth' if GlobalConfigService.load('ENABLE_GOOGLE_OAUTH_LOGIN', 'true').to_s != 'false'
+ methods << 'saml' if ChatwootHub.pricing_plan != 'community' && GlobalConfigService.load('ENABLE_SAML_SSO_LOGIN', 'true').to_s != 'false'
+ methods
+ end
+
def set_application_pack
@application_pack = if request.path.include?('/auth') || request.path.include?('/login')
'v3app'
diff --git a/app/controllers/super_admin/app_configs_controller.rb b/app/controllers/super_admin/app_configs_controller.rb
index 3cf360594..1a9539bb6 100644
--- a/app/controllers/super_admin/app_configs_controller.rb
+++ b/app/controllers/super_admin/app_configs_controller.rb
@@ -15,14 +15,20 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
end
def create
+ errors = []
params['app_config'].each do |key, value|
next unless @allowed_configs.include?(key)
i = InstallationConfig.where(name: key).first_or_create(value: value, locked: false)
i.value = value
- i.save!
+ errors.concat(i.errors.full_messages) unless i.save
+ end
+
+ if errors.any?
+ redirect_to super_admin_app_config_path(config: @config), alert: errors.join(', ')
+ else
+ redirect_to super_admin_settings_path, notice: "App Configs - #{@config.titleize} updated successfully"
end
- redirect_to super_admin_settings_path, notice: "App Configs - #{@config.titleize} updated successfully"
end
private
@@ -42,7 +48,7 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
'instagram' => %w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT],
'whatsapp_embedded' => %w[WHATSAPP_APP_ID WHATSAPP_APP_SECRET WHATSAPP_CONFIGURATION_ID WHATSAPP_API_VERSION],
'notion' => %w[NOTION_CLIENT_ID NOTION_CLIENT_SECRET],
- 'google' => %w[GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET GOOGLE_OAUTH_REDIRECT_URI]
+ 'google' => %w[GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET GOOGLE_OAUTH_REDIRECT_URI ENABLE_GOOGLE_OAUTH_LOGIN]
}
@allowed_configs = mapping.fetch(
diff --git a/app/helpers/super_admin/features.yml b/app/helpers/super_admin/features.yml
index f49004e79..b05c603cd 100644
--- a/app/helpers/super_admin/features.yml
+++ b/app/helpers/super_admin/features.yml
@@ -9,6 +9,13 @@ captain:
icon: 'icon-captain'
config_key: 'captain'
enterprise: true
+saml:
+ name: 'SAML SSO'
+ description: 'Configuration for controlling SAML Single Sign-On availability'
+ enabled: <%= ChatwootApp.enterprise? %>
+ icon: 'icon-lock-line'
+ config_key: 'saml'
+ enterprise: true
custom_branding:
name: 'Custom Branding'
description: 'Apply your own branding to this installation.'
diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json
index 50cf38b58..4553572cb 100644
--- a/app/javascript/dashboard/i18n/locale/en/settings.json
+++ b/app/javascript/dashboard/i18n/locale/en/settings.json
@@ -411,6 +411,7 @@
"TITLE": "Security",
"DESCRIPTION": "Manage your account security settings.",
"LINK_TEXT": "Learn more about SAML SSO",
+ "SAML_DISABLED_MESSAGE": "SAML SSO is currently disabled. Please contact your administrator to enable this feature.",
"SAML": {
"TITLE": "SAML SSO",
"NOTE": "Configure SAML single sign-on for your account. Users will authenticate through your identity provider instead of using email/password.",
diff --git a/app/javascript/dashboard/routes/dashboard/settings/security/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/security/Index.vue
index 0ac35c9e2..b8e7a7bf4 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/security/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/security/Index.vue
@@ -10,13 +10,23 @@ import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
const { shouldShow, shouldShowPaywall } = usePolicy();
-const shouldShowSaml = computed(() =>
- shouldShow(
+const allowedLoginMethods = computed(
+ () => window.chatwootConfig.allowedLoginMethods || ['email']
+);
+
+const isSamlSsoEnabled = computed(() =>
+ allowedLoginMethods.value.includes('saml')
+);
+
+const shouldShowSaml = computed(() => {
+ const hasPermission = shouldShow(
FEATURE_FLAGS.SAML,
['administrator'],
[INSTALLATION_TYPES.CLOUD, INSTALLATION_TYPES.ENTERPRISE]
- )
-);
+ );
+ return hasPermission && isSamlSsoEnabled.value;
+});
+
const showPaywall = computed(() => shouldShowPaywall('saml'));
@@ -36,6 +46,9 @@ const showPaywall = computed(() => shouldShowPaywall('saml'));
+
+ {{ $t('SECURITY_SETTINGS.SAML_DISABLED_MESSAGE') }}
+
diff --git a/app/javascript/v3/views/auth/signup/components/Signup/Form.vue b/app/javascript/v3/views/auth/signup/components/Signup/Form.vue
index 6a4497948..cb7345ef1 100644
--- a/app/javascript/v3/views/auth/signup/components/Signup/Form.vue
+++ b/app/javascript/v3/views/auth/signup/components/Signup/Form.vue
@@ -99,8 +99,14 @@ export default {
}
return '';
},
+ allowedLoginMethods() {
+ return window.chatwootConfig.allowedLoginMethods || ['email'];
+ },
showGoogleOAuth() {
- return Boolean(window.chatwootConfig.googleOAuthClientId);
+ return (
+ this.allowedLoginMethods.includes('google_oauth') &&
+ Boolean(window.chatwootConfig.googleOAuthClientId)
+ );
},
isFormValid() {
return !this.v$.$invalid && this.hasAValidCaptcha;
@@ -296,7 +302,7 @@ export default {
{ name == 'ENABLE_SAML_SSO_LOGIN' }
# TODO: Get rid of default scope
# https://stackoverflow.com/a/1834250/939299
@@ -54,4 +55,11 @@ class InstallationConfig < ApplicationRecord
def clear_cache
GlobalConfig.clear_cache
end
+
+ def saml_sso_users_check
+ return unless value == false || value == 'false'
+ return unless User.exists?(provider: 'saml')
+
+ errors.add(:base, 'Cannot disable SAML SSO login while users are using SAML authentication')
+ end
end
diff --git a/app/views/layouts/vueapp.html.erb b/app/views/layouts/vueapp.html.erb
index 4d71a547d..3c21850ca 100644
--- a/app/views/layouts/vueapp.html.erb
+++ b/app/views/layouts/vueapp.html.erb
@@ -38,6 +38,7 @@
instagramAppId: '<%= @global_config['INSTAGRAM_APP_ID'] %>',
googleOAuthClientId: '<%= ENV.fetch('GOOGLE_OAUTH_CLIENT_ID', nil) %>',
googleOAuthCallbackUrl: '<%= ENV.fetch('GOOGLE_OAUTH_CALLBACK_URL', nil) %>',
+ allowedLoginMethods: <%= @global_config['ALLOWED_LOGIN_METHODS'].to_json.html_safe %>,
fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>',
whatsappAppId: '<%= @global_config['WHATSAPP_APP_ID'] %>',
whatsappConfigurationId: '<%= @global_config['WHATSAPP_CONFIGURATION_ID'] %>',
diff --git a/config/installation_config.yml b/config/installation_config.yml
index 9b84ddc2b..eff38ab3d 100644
--- a/config/installation_config.yml
+++ b/config/installation_config.yml
@@ -438,8 +438,23 @@
value:
locked: false
description: 'The redirect URI configured in your Google OAuth app'
+- name: ENABLE_GOOGLE_OAUTH_LOGIN
+ display_title: 'Enable Google OAuth login'
+ value: true
+ locked: false
+ description: 'Show Google OAuth as a login option when credentials are configured'
+ type: boolean
## ------ End of Configs added for Google OAuth ------ ##
+## ------ Configs added for SAML SSO ------ ##
+- name: ENABLE_SAML_SSO_LOGIN
+ display_title: 'Enable SAML SSO login'
+ value: true
+ locked: false
+ description: 'Show SAML SSO as a login option. Cannot be disabled if any users are using SAML authentication.'
+ type: boolean
+## ------ End of Configs added for SAML SSO ------ ##
+
## ------ Configs added for Cloudflare ------ ##
- name: CLOUDFLARE_API_KEY
display_title: 'Cloudflare API Key'
diff --git a/config/locales/en.yml b/config/locales/en.yml
index d0f256146..c03fb2ba6 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -64,6 +64,7 @@ en:
not_found: Assignment policy not found
saml:
feature_not_enabled: SAML feature not enabled for this account
+ sso_not_enabled: SAML SSO is not enabled for this installation
data_import:
data_type:
invalid: Invalid data type
diff --git a/enterprise/app/controllers/api/v1/accounts/saml_settings_controller.rb b/enterprise/app/controllers/api/v1/accounts/saml_settings_controller.rb
index 9f00138cb..e70804073 100644
--- a/enterprise/app/controllers/api/v1/accounts/saml_settings_controller.rb
+++ b/enterprise/app/controllers/api/v1/accounts/saml_settings_controller.rb
@@ -1,4 +1,5 @@
class Api::V1::Accounts::SamlSettingsController < Api::V1::Accounts::BaseController
+ before_action :check_saml_sso_enabled
before_action :check_saml_feature_enabled
before_action :check_authorization
before_action :set_saml_settings
@@ -53,4 +54,10 @@ class Api::V1::Accounts::SamlSettingsController < Api::V1::Accounts::BaseControl
render json: { error: I18n.t('errors.saml.feature_not_enabled') }, status: :forbidden
end
+
+ def check_saml_sso_enabled
+ return if GlobalConfigService.load('ENABLE_SAML_SSO_LOGIN', 'true').to_s == 'true'
+
+ render json: { error: I18n.t('errors.saml.sso_not_enabled') }, status: :forbidden
+ end
end
diff --git a/enterprise/app/controllers/api/v1/auth_controller.rb b/enterprise/app/controllers/api/v1/auth_controller.rb
index b0d8e1366..c25af3cc1 100644
--- a/enterprise/app/controllers/api/v1/auth_controller.rb
+++ b/enterprise/app/controllers/api/v1/auth_controller.rb
@@ -3,6 +3,11 @@ class Api::V1::AuthController < Api::BaseController
before_action :find_user_and_account, only: [:saml_login]
def saml_login
+ unless saml_sso_enabled?
+ render json: { error: 'SAML SSO login is not enabled' }, status: :forbidden
+ return
+ end
+
return if @account.nil?
relay_state = params[:target] || 'web'
@@ -69,4 +74,8 @@ class Api::V1::AuthController < Api::BaseController
"#{frontend_url}/app/login/sso#{query_fragment}"
end
+
+ def saml_sso_enabled?
+ GlobalConfigService.load('ENABLE_SAML_SSO_LOGIN', 'true').to_s == 'true'
+ end
end
diff --git a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb
index 934462b93..efaa139a9 100644
--- a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb
+++ b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb
@@ -11,6 +11,8 @@ module Enterprise::SuperAdmin::AppConfigsController
@allowed_configs = internal_config_options
when 'captain'
@allowed_configs = captain_config_options
+ when 'saml'
+ @allowed_configs = saml_config_options
else
super
end
@@ -46,4 +48,8 @@ module Enterprise::SuperAdmin::AppConfigsController
CAPTAIN_FIRECRAWL_API_KEY
]
end
+
+ def saml_config_options
+ %w[ENABLE_SAML_SSO_LOGIN]
+ end
end