Newer
Older
# name: discourse-saml
# about: SAML Auth Provider
# version: 0.1
# author: Robin Ward
require_dependency 'auth/oauth2_authenticator'
gem 'macaddr', '1.0.0'
gem 'uuid', '2.3.7'
gem 'rexml', '3.2.5'
gem 'ruby-saml', '1.13.0'
gem "omniauth-saml", '1.9.0'
on(:before_session_destroy) do |data|
next if !DiscourseSaml.setting(:slo_target_url).present?
data[:redirect_url] = Discourse.base_path + "/auth/saml/spslo"
end
module ::DiscourseSaml
def self.setting(key, prefer_prefix: "saml_")
if prefer_prefix == "saml_" && SiteSetting.has_setting?("saml_#{key}")
SiteSetting.get("saml_#{key}")
else
GlobalSetting.try("#{prefer_prefix}#{key}") || GlobalSetting.try("saml_#{key}")
end
end
def self.is_saml_forced_domain?(email)
return if !DiscourseSaml.setting(:forced_domains).present?
return if email.blank?
DiscourseSaml.setting(:forced_domains).split(",").each do |domain|
return true if email.end_with?("@#{domain}")
end
false
end
end
after_initialize do
# "SAML Forced Domains" - Prevent login via email
on(:before_email_login) do |user|
if ::DiscourseSaml.is_saml_forced_domain?(user.email)
raise Discourse::InvalidAccess.new(nil, nil, custom_message: "login.use_saml_auth")
# "SAML Forced Domains" - Prevent login via regular username/password
module ::DiscourseSaml::SessionControllerExtensions
def login_error_check(user)
if ::DiscourseSaml.is_saml_forced_domain?(user.email)
return { error: I18n.t("login.use_saml_auth") }
end
::SessionController.prepend(::DiscourseSaml::SessionControllerExtensions)
# "SAML Forced Dvomains" - Prevent login via other omniauth strategies
class ::DiscourseSaml::ForcedSamlError < StandardError; end
on(:after_auth) do |authenticator, result|
next if authenticator.name == "saml"
if [result.user&.email, result.email].any? { |e| ::DiscourseSaml.is_saml_forced_domain?(e) }
raise ::DiscourseSaml::ForcedSamlError
Users::OmniauthCallbacksController.rescue_from(::DiscourseSaml::ForcedSamlError) do
flash[:error] = I18n.t("login.use_saml_auth")
render('failure')
end
require_relative "lib/discourse_saml/saml_omniauth_strategy"
require_relative "lib/saml_authenticator"
# Allow GlobalSettings to override the translations
# If the global settings are not provided, will use the `js.login.saml.name` and `js.login.saml.title` translations
name = GlobalSetting.try(:saml_title)
button_title = GlobalSetting.try(:saml_button_title) || GlobalSetting.try(:saml_title)
authenticator: SamlAuthenticator.new('saml')