mirror of https://github.com/docusealco/docuseal
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							133 lines
						
					
					
						
							5.3 KiB
						
					
					
				
			
		
		
	
	
							133 lines
						
					
					
						
							5.3 KiB
						
					
					
				| # frozen_string_literal: true
 | |
| 
 | |
| class SsoSettingsController < ApplicationController
 | |
|   before_action :load_encrypted_config
 | |
|   authorize_resource :encrypted_config, only: [:show, :update]
 | |
| 
 | |
|   def show; end
 | |
| 
 | |
|   def update
 | |
|     saml_config = params[:saml_config] || {}
 | |
|     
 | |
|     # Handle IdP metadata file upload and parsing
 | |
|     if params[:idp_metadata_file].present?
 | |
|       begin
 | |
|         parsed_config = parse_idp_metadata(params[:idp_metadata_file])
 | |
|         saml_config.merge!(parsed_config)
 | |
|         
 | |
|         # Save the parsed configuration immediately
 | |
|         @encrypted_config.value = saml_config.to_json
 | |
|         
 | |
|         if @encrypted_config.save
 | |
|           redirect_to settings_sso_path, notice: 'IdP metadata parsed and saved successfully!'
 | |
|         else
 | |
|           redirect_to settings_sso_path, alert: 'Failed to save parsed configuration. Please try again.'
 | |
|         end
 | |
|         return
 | |
|       rescue StandardError => e
 | |
|         Rails.logger.error "Failed to parse IdP metadata: #{e.message}"
 | |
|         redirect_to settings_sso_path, alert: "Failed to parse IdP metadata: #{e.message}"
 | |
|         return
 | |
|       end
 | |
|     end
 | |
|     
 | |
|     # Validate required fields for manual configuration
 | |
|     if saml_config['idp_sso_service_url'].blank? || saml_config['idp_cert_fingerprint'].blank?
 | |
|       redirect_to settings_sso_path, alert: 'Please fill in all required SAML configuration fields.'
 | |
|       return
 | |
|     end
 | |
| 
 | |
|     # Save the SAML configuration
 | |
|     @encrypted_config.value = saml_config.to_json
 | |
|     
 | |
|     if @encrypted_config.save
 | |
|       redirect_to settings_sso_path, notice: 'SAML configuration saved successfully.'
 | |
|     else
 | |
|       redirect_to settings_sso_path, alert: 'Failed to save SAML configuration. Please try again.'
 | |
|     end
 | |
|   rescue StandardError => e
 | |
|     Rails.logger.error "Failed to save SAML config: #{e.message}"
 | |
|     redirect_to settings_sso_path, alert: 'An error occurred while saving the configuration.'
 | |
|   end
 | |
| 
 | |
|   private
 | |
| 
 | |
|   def load_encrypted_config
 | |
|     @encrypted_config =
 | |
|       EncryptedConfig.find_or_initialize_by(account: current_account, key: 'saml_configs')
 | |
|   end
 | |
|   
 | |
|   def parse_idp_metadata(metadata_file)
 | |
|     require 'nokogiri'
 | |
|     
 | |
|     # Read and parse the XML metadata file
 | |
|     xml_content = metadata_file.read
 | |
|     doc = Nokogiri::XML(xml_content)
 | |
|     
 | |
|     # Remove default namespace to make XPath queries simpler
 | |
|     doc.remove_namespaces!
 | |
|     
 | |
|     config = {}
 | |
|     
 | |
|     # Extract Entity ID
 | |
|     entity_descriptor = doc.at_xpath('//EntityDescriptor')
 | |
|     config['idp_entity_id'] = entity_descriptor['entityID'] if entity_descriptor
 | |
|     
 | |
|     # Try SAML 2.0 SSO Service URL (Azure AD puts this in IDPSSODescriptor)
 | |
|     sso_service = doc.at_xpath('//IDPSSODescriptor/SingleSignOnService[@Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"]')
 | |
|     sso_service ||= doc.at_xpath('//IDPSSODescriptor/SingleSignOnService[@Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"]')
 | |
|     
 | |
|     if sso_service
 | |
|       config['idp_sso_service_url'] = sso_service['Location']
 | |
|     else
 | |
|       # Fallback: Try WS-Federation endpoints and convert to SAML
 | |
|       wsfed_endpoint = doc.at_xpath('//SecurityTokenServiceEndpoint/EndpointReference/Address')
 | |
|       wsfed_endpoint ||= doc.at_xpath('//PassiveRequestorEndpoint/EndpointReference/Address')
 | |
|       if wsfed_endpoint
 | |
|         # Convert WS-Fed endpoint to SAML endpoint (Azure AD pattern)
 | |
|         wsfed_url = wsfed_endpoint.text
 | |
|         config['idp_sso_service_url'] = wsfed_url.gsub('/wsfed', '/saml2')
 | |
|       end
 | |
|     end
 | |
|     
 | |
|     # Extract SLO Service URL
 | |
|     slo_service = doc.at_xpath('//IDPSSODescriptor/SingleLogoutService[@Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"]')
 | |
|     slo_service ||= doc.at_xpath('//IDPSSODescriptor/SingleLogoutService[@Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"]')
 | |
|     config['idp_slo_service_url'] = slo_service['Location'] if slo_service
 | |
|     
 | |
|     # Extract certificate and calculate fingerprint (try multiple locations)
 | |
|     cert_element = doc.at_xpath('//IDPSSODescriptor/KeyDescriptor[@use="signing"]/KeyInfo/X509Data/X509Certificate')
 | |
|     cert_element ||= doc.at_xpath('//IDPSSODescriptor/KeyDescriptor/KeyInfo/X509Data/X509Certificate')
 | |
|     cert_element ||= doc.at_xpath('//KeyDescriptor[@use="signing"]/KeyInfo/X509Data/X509Certificate')
 | |
|     cert_element ||= doc.at_xpath('//KeyDescriptor/KeyInfo/X509Data/X509Certificate')
 | |
|     cert_element ||= doc.at_xpath('//X509Certificate')
 | |
|     
 | |
|     if cert_element
 | |
|       cert_data = cert_element.text.gsub(/\s+/, '')
 | |
|       cert_der = Base64.decode64(cert_data)
 | |
|       fingerprint = Digest::SHA1.hexdigest(cert_der).upcase.scan(/../).join(':')
 | |
|       config['idp_cert_fingerprint'] = fingerprint
 | |
|     end
 | |
|     
 | |
|     # Extract Name ID formats
 | |
|     name_id_format = doc.at_xpath('//IDPSSODescriptor/NameIDFormat')
 | |
|     config['name_identifier_format'] = name_id_format.text if name_id_format
 | |
|     
 | |
|     # Set default name identifier format if not found
 | |
|     config['name_identifier_format'] ||= 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
 | |
|     
 | |
|     # Validation
 | |
|     if config['idp_sso_service_url'].blank?
 | |
|       raise 'No SSO service URL found in metadata. Please ensure this is a valid SAML 2.0 or Azure AD metadata file.'
 | |
|     end
 | |
|     
 | |
|     if config['idp_cert_fingerprint'].blank?
 | |
|       raise 'No certificate found in metadata. Please ensure the metadata contains a valid X.509 certificate.'
 | |
|     end
 | |
|     
 | |
|     config
 | |
|   rescue Nokogiri::XML::SyntaxError => e
 | |
|     raise "Invalid XML metadata: #{e.message}"
 | |
|   end
 | |
| end
 |