Class: SamlIdp::Request
- Inherits:
-
Object
- Object
- SamlIdp::Request
- Defined in:
- lib/saml_idp/request.rb
Instance Attribute Summary collapse
-
#errors ⇒ Object
Returns the value of attribute errors.
-
#raw_xml ⇒ Object
Returns the value of attribute raw_xml.
-
#relay_state ⇒ Object
Returns the value of attribute relay_state.
-
#saml_request ⇒ Object
Returns the value of attribute saml_request.
-
#sig_algorithm ⇒ Object
Returns the value of attribute sig_algorithm.
-
#signature ⇒ Object
Returns the value of attribute signature.
Class Method Summary collapse
Instance Method Summary collapse
- #acs_url ⇒ Object
- #authn_request? ⇒ Boolean
- #collect_errors(error_type) ⇒ Object
-
#initialize(raw_xml = "", external_attributes = {}) ⇒ Request
constructor
A new instance of Request.
- #issuer ⇒ Object
- #log(msg) ⇒ Object
- #logout_request? ⇒ Boolean
- #logout_url ⇒ Object
- #name_id ⇒ Object
- #request ⇒ Object
- #request_id ⇒ Object
- #requested_authn_context ⇒ Object
- #response_url ⇒ Object
- #service_provider ⇒ Object
- #service_provider? ⇒ Boolean
- #session_index ⇒ Object
- #valid?(external_attributes = {}) ⇒ Boolean
- #valid_external_signature? ⇒ Boolean
- #valid_signature? ⇒ Boolean
Constructor Details
#initialize(raw_xml = "", external_attributes = {}) ⇒ Request
Returns a new instance of Request.
33 34 35 36 37 38 39 40 |
# File 'lib/saml_idp/request.rb', line 33 def initialize(raw_xml = "", external_attributes = {}) self.raw_xml = raw_xml self.saml_request = external_attributes[:saml_request] self.relay_state = external_attributes[:relay_state] self.sig_algorithm = external_attributes[:sig_algorithm] self.signature = external_attributes[:signature] self.errors = [] end |
Instance Attribute Details
#errors ⇒ Object
Returns the value of attribute errors.
6 7 8 |
# File 'lib/saml_idp/request.rb', line 6 def errors @errors end |
#raw_xml ⇒ Object
Returns the value of attribute raw_xml.
26 27 28 |
# File 'lib/saml_idp/request.rb', line 26 def raw_xml @raw_xml end |
#relay_state ⇒ Object
Returns the value of attribute relay_state.
26 27 28 |
# File 'lib/saml_idp/request.rb', line 26 def relay_state @relay_state end |
#saml_request ⇒ Object
Returns the value of attribute saml_request.
26 27 28 |
# File 'lib/saml_idp/request.rb', line 26 def saml_request @saml_request end |
#sig_algorithm ⇒ Object
Returns the value of attribute sig_algorithm.
26 27 28 |
# File 'lib/saml_idp/request.rb', line 26 def sig_algorithm @sig_algorithm end |
#signature ⇒ Object
Returns the value of attribute signature.
26 27 28 |
# File 'lib/saml_idp/request.rb', line 26 def signature @signature end |
Class Method Details
.from_deflated_request(raw, external_attributes = {}) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/saml_idp/request.rb', line 8 def self.from_deflated_request(raw, external_attributes = {}) if raw decoded = Base64.decode64(raw) zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS) begin inflated = zstream.inflate(decoded).tap do zstream.finish zstream.close end rescue Zlib::BufError, Zlib::DataError # not compressed inflated = decoded end else inflated = "" end new(inflated, external_attributes) end |
Instance Method Details
#acs_url ⇒ Object
70 71 72 73 |
# File 'lib/saml_idp/request.rb', line 70 def acs_url service_provider.acs_url || authn_request["AssertionConsumerServiceURL"].to_s end |
#authn_request? ⇒ Boolean
46 47 48 |
# File 'lib/saml_idp/request.rb', line 46 def authn_request? authn_request.nil? ? false : true end |
#collect_errors(error_type) ⇒ Object
95 96 97 |
# File 'lib/saml_idp/request.rb', line 95 def collect_errors(error_type) errors.push(error_type) end |
#issuer ⇒ Object
192 193 194 195 |
# File 'lib/saml_idp/request.rb', line 192 def issuer @_issuer ||= xpath("//saml:Issuer", saml: assertion).first.try(:content) @_issuer if @_issuer.present? end |
#log(msg) ⇒ Object
87 88 89 90 91 92 93 |
# File 'lib/saml_idp/request.rb', line 87 def log(msg) if config.logger.respond_to?(:call) config.logger.call msg else config.logger.info msg end end |
#logout_request? ⇒ Boolean
42 43 44 |
# File 'lib/saml_idp/request.rb', line 42 def logout_request? logout_request.nil? ? false : true end |
#logout_url ⇒ Object
75 76 77 |
# File 'lib/saml_idp/request.rb', line 75 def logout_url service_provider.assertion_consumer_logout_service_url end |
#name_id ⇒ Object
197 198 199 |
# File 'lib/saml_idp/request.rb', line 197 def name_id @_name_id ||= xpath("//saml:NameID", saml: assertion).first.try(:content) end |
#request ⇒ Object
54 55 56 57 58 59 60 |
# File 'lib/saml_idp/request.rb', line 54 def request if authn_request? authn_request elsif logout_request? logout_request end end |
#request_id ⇒ Object
50 51 52 |
# File 'lib/saml_idp/request.rb', line 50 def request_id request["ID"] end |
#requested_authn_context ⇒ Object
62 63 64 65 66 67 68 |
# File 'lib/saml_idp/request.rb', line 62 def requested_authn_context if authn_request? && authn_context_node authn_context_node.content else nil end end |
#response_url ⇒ Object
79 80 81 82 83 84 85 |
# File 'lib/saml_idp/request.rb', line 79 def response_url if authn_request? acs_url elsif logout_request? logout_url end end |
#service_provider ⇒ Object
187 188 189 190 |
# File 'lib/saml_idp/request.rb', line 187 def service_provider return unless issuer.present? @_service_provider ||= ServiceProvider.new((service_provider_finder[issuer] || {}).merge(identifier: issuer)) end |
#service_provider? ⇒ Boolean
183 184 185 |
# File 'lib/saml_idp/request.rb', line 183 def service_provider? service_provider && service_provider.valid? end |
#session_index ⇒ Object
201 202 203 |
# File 'lib/saml_idp/request.rb', line 201 def session_index @_session_index ||= xpath("//samlp:SessionIndex", samlp: samlp).first.try(:content) end |
#valid?(external_attributes = {}) ⇒ Boolean
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/saml_idp/request.rb', line 99 def valid?(external_attributes = {}) unless service_provider? log "Unable to find service provider for issuer #{issuer}" collect_errors(:sp_not_found) return false end unless (authn_request? ^ logout_request?) log "One and only one of authnrequest and logout request is required. authnrequest: #{authn_request?} logout_request: #{logout_request?} " collect_errors(:unaccepted_request) return false end if (logout_request? || validate_auth_request_signature?) && (service_provider.cert.to_s.empty? || !!service_provider.fingerprint.to_s.empty?) log "Verifying request signature is required. But certificate and fingerprint was empty." collect_errors(:empty_certificate) return false end # XML embedded signature if signature.nil? && !valid_signature? log "Requested document signature is invalid in #{raw_xml}" collect_errors(:invalid_embedded_signature) return false end # URI query signature if signature.present? && !valid_external_signature? log "Requested URI signature is invalid in #{raw_xml}" collect_errors(:invalid_external_signature) return false end if response_url.nil? log "Unable to find response url for #{issuer}: #{raw_xml}" collect_errors(:empty_response_url) return false end if !service_provider.acceptable_response_hosts.include?(response_host) log "#{service_provider.acceptable_response_hosts} compare to #{response_host}" log "No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url host" collect_errors(:not_allowed_host) return false end return true end |
#valid_external_signature? ⇒ Boolean
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/saml_idp/request.rb', line 157 def valid_external_signature? return true if authn_request? && !validate_auth_request_signature? cert = OpenSSL::X509::Certificate.new(service_provider.cert) sha_version = sig_algorithm =~ /sha(.*?)$/i && $1.to_i raw_signature = Base64.decode64(signature) signature_algorithm = case sha_version when 256 then OpenSSL::Digest::SHA256 when 384 then OpenSSL::Digest::SHA384 when 512 then OpenSSL::Digest::SHA512 else OpenSSL::Digest::SHA1 end result = cert.public_key.verify(signature_algorithm.new, raw_signature, query_request_string) # Match all percent-encoded sequences (e.g., %20, %2B) and convert them to lowercase # Upper case is recommended for consistency but some services such as MS Entra Id not follows it # https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 result || cert.public_key.verify(signature_algorithm.new, raw_signature, query_request_string.gsub(/%[A-F0-9]{2}/) { |match| match.downcase }) rescue OpenSSL::X509::CertificateError => e log e. collect_errors(:cert_format_error) end |
#valid_signature? ⇒ Boolean
148 149 150 151 152 153 154 155 |
# File 'lib/saml_idp/request.rb', line 148 def valid_signature? # Force signatures for logout requests because there is no other protection against a cross-site DoS. if logout_request? || authn_request? && validate_auth_request_signature? document.valid_signature?(service_provider.cert, service_provider.fingerprint) else true end end |