saml_database.rb 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class OmniAuth::Strategies::SamlDatabase < OmniAuth::Strategies::SAML
  3. option :name, 'saml'
  4. def self.setup
  5. auth_saml_credentials = Setting.get('auth_saml_credentials') || {}
  6. http_type = Setting.get('http_type')
  7. fqdn = Setting.get('fqdn')
  8. # Use meta URL as entity id/issues as it is best practice.
  9. # See: https://community.zammad.org/t/saml-oidc-third-party-authentication/2533/13
  10. entity_id = "#{http_type}://#{fqdn}/auth/saml/metadata"
  11. assertion_consumer_service_url = "#{http_type}://#{fqdn}/auth/saml/callback"
  12. single_logout_service_url = "#{http_type}://#{fqdn}/auth/saml/slo"
  13. config = auth_saml_credentials.compact_blank
  14. .merge(
  15. assertion_consumer_service_url: assertion_consumer_service_url,
  16. sp_entity_id: entity_id,
  17. single_logout_service_url: single_logout_service_url,
  18. idp_slo_session_destroy: proc { |env, session| destroy_session(env, session) },
  19. )
  20. apply_security_settings(config)
  21. config
  22. end
  23. def self.destroy_session(env, session)
  24. session.delete('saml_uid')
  25. session.delete('saml_transaction_id')
  26. session.delete('saml_session_index')
  27. @_current_user = nil
  28. env['rack.session.options'][:expire_after] = nil
  29. session.destroy
  30. end
  31. def initialize(app, *args, &)
  32. args[0] = self.class.setup
  33. super
  34. end
  35. def self.apply_security_settings(settings)
  36. security = settings.delete(:security) || {}
  37. private_key = settings.delete(:private_key) || ''
  38. private_key_secret = settings.delete(:private_key_secret) || ''
  39. certificate = settings.delete(:certificate) || ''
  40. return if !check_security_settings(settings, security, private_key, private_key_secret, certificate)
  41. apply_security_default_settings(settings)
  42. apply_sign_only_settings(settings, security)
  43. apply_encrypt_only_settings(settings, security)
  44. true
  45. end
  46. def self.check_security_settings(settings, security, private_key, private_key_secret, certificate)
  47. return false if security.blank? || security.eql?('off')
  48. return false if private_key.blank? || certificate.blank?
  49. begin
  50. pkey = OpenSSL::PKey.read(private_key, private_key_secret)
  51. rescue
  52. return false
  53. end
  54. settings[:private_key] = pkey.to_pem
  55. settings[:certificate] = certificate
  56. true
  57. end
  58. def self.apply_security_default_settings(settings)
  59. settings[:security] = {
  60. digest_method: XMLSecurity::Document::SHA256,
  61. signature_method: XMLSecurity::Document::RSA_SHA256,
  62. authn_requests_signed: true,
  63. logout_requests_signed: true,
  64. want_assertions_signed: true,
  65. want_assertions_encrypted: true,
  66. }
  67. true
  68. end
  69. def self.apply_encrypt_only_settings(settings, security)
  70. return if !security.eql?('encrypt')
  71. settings[:security][:authn_requests_signed] = false
  72. settings[:security][:logout_requests_signed] = false
  73. settings[:security][:want_assertions_signed] = false
  74. true
  75. end
  76. def self.apply_sign_only_settings(settings, security)
  77. return if !security.eql?('sign')
  78. settings[:security][:want_assertions_encrypted] = false
  79. true
  80. end
  81. private_class_method %i[
  82. apply_security_settings
  83. check_security_settings
  84. apply_security_default_settings
  85. apply_encrypt_only_settings
  86. apply_sign_only_settings
  87. ].freeze
  88. private
  89. def handle_logout_response(raw_response, settings)
  90. logout_response = OneLogin::RubySaml::Logoutresponse.new(raw_response, settings, matches_request_id: session['saml_transaction_id'])
  91. logout_response.soft = false
  92. logout_response.validate
  93. redirect_path = if session['omniauth.origin']&.include?('/mobile')
  94. '/mobile'
  95. elsif session['omniauth.origin']&.include?('/desktop')
  96. '/desktop'
  97. else
  98. '/'
  99. end
  100. self.class.destroy_session(env, session)
  101. redirect "#{Setting.get('http_type')}://#{Setting.get('fqdn')}#{redirect_path}"
  102. end
  103. end