require 'xmlenc'
module SamlIdp
  class Encryptor
    attr_accessor :encryption_key
    attr_accessor :block_encryption
    attr_accessor :key_transport
    attr_accessor :cert

    def initialize(opts)
      self.block_encryption = opts[:block_encryption]
      self.key_transport = opts[:key_transport]
      self.cert = opts[:cert]
    end

    def encrypt(raw_xml) 
      encryption_template = Nokogiri::XML::Document.parse(build_encryption_template).root
      encrypted_data = Xmlenc::EncryptedData.new(encryption_template)
      @encryption_key = encrypted_data.encrypt(raw_xml)
      encrypted_key_node = encrypted_data.node.at_xpath(
        '//xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey',
        Xmlenc::NAMESPACES
      )   
      encrypted_key = Xmlenc::EncryptedKey.new(encrypted_key_node)
      encrypted_key.encrypt(openssl_cert.public_key, encryption_key)
      xml = Builder::XmlMarkup.new
      xml.EncryptedAssertion xmlns: Saml::XML::Namespaces::ASSERTION do |enc_assert|
        enc_assert << encrypted_data.node.to_s
      end 
    end

    def openssl_cert
      if cert.is_a?(String)
        @_openssl_cert ||= OpenSSL::X509::Certificate.new(Base64.decode64(cert))
      else
        @_openssl_cert ||= cert
      end 
    end 
    private :openssl_cert

    def block_encryption_ns
      "http://www.w3.org/2001/04/xmlenc##{block_encryption}"
    end 
    private :block_encryption_ns

    def key_transport_ns
      "http://www.w3.org/2001/04/xmlenc##{key_transport}"
    end 
    private :key_transport_ns

    def cipher_algorithm
      Xmlenc::EncryptedData::ALGORITHMS[block_encryption_ns]
    end 
    private :cipher_algorithm

    def build_encryption_template
      xml = Builder::XmlMarkup.new
      xml.EncryptedData Id: 'ED', Type: 'http://www.w3.org/2001/04/xmlenc#Element',
        xmlns: 'http://www.w3.org/2001/04/xmlenc#' do |enc_data|
        enc_data.EncryptionMethod Algorithm: block_encryption_ns
        enc_data.tag! 'ds:KeyInfo', 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#' do |key_info|
          key_info.EncryptedKey Id: 'EK', xmlns: 'http://www.w3.org/2001/04/xmlenc#' do |enc_key|
            enc_key.EncryptionMethod Algorithm: key_transport_ns
            enc_key.tag! 'ds:KeyInfo', 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#' do |key_info2|
              key_info2.tag! 'ds:KeyName'
              key_info2.tag! 'ds:X509Data' do |x509_data|
                x509_data.tag! 'ds:X509Certificate' do |x509_cert|
                  x509_cert << cert.to_s.gsub(/-+(BEGIN|END) CERTIFICATE-+/, '') 
                end
              end
            end
            enc_key.CipherData do |cipher_data|
              cipher_data.CipherValue
            end
            enc_key.ReferenceList do |ref_list|
              ref_list.DataReference URI: '#ED'
            end
          end
        end
        enc_data.CipherData do |cipher_data|
          cipher_data.CipherValue
        end
      end
    end
    private :build_encryption_template
  end
end
