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')); 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