main
 1# frozen_string_literal: true
 2
 3module Saml
 4  module Kit
 5    module Bindings
 6      # This class is responsible for
 7      # generating a url as per the
 8      # rules for the HTTP redirect binding
 9      # specification.
10      # https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
11      # {include:file:spec/saml/kit/bindings/url_builder_spec.rb}
12      class UrlBuilder
13        include Serializable
14        attr_reader :configuration
15
16        def initialize(configuration: Saml::Kit.configuration)
17          @configuration = configuration
18        end
19
20        def build(document, relay_state: nil)
21          destination = document.destination
22          if configuration.sign?
23            payload = canonicalize(document, relay_state)
24            "#{destination}?#{payload}&Signature=#{signature_for(payload)}"
25          else
26            "#{destination}?" + to_query_string(
27              document.query_string_parameter => serialize(document.to_xml),
28              'RelayState' => relay_state
29            )
30          end
31        end
32
33        private
34
35        def signature_for(payload)
36          private_key = configuration.private_keys(use: :signing).last
37          encode(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
38        end
39
40        def canonicalize(saml_document, relay_state)
41          xml = saml_document.to_xml
42          to_query_string(
43            saml_document.query_string_parameter => serialize(xml),
44            'RelayState' => relay_state,
45            'SigAlg' => ::Xml::Kit::Namespaces::SHA256
46          )
47        end
48
49        def to_query_string(query_params)
50          query_params.map do |(key, value)|
51            value.present? ? "#{key}=#{escape(value)}" : nil
52          end.compact.join('&')
53        end
54
55        def serialize(value)
56          encode(deflate(value))
57        end
58      end
59    end
60  end
61end