require 'active_support'
require 'active_support/builder'
require 'base64'
require 'cgi'
require 'net/https'
require 'nokogiri'
require 'nori'
require 'oauth'
require 'openssl'
require 'securerandom'
require 'uri'
module Aggcat
class Base
SAML_URL = 'https://oauth.intuit.com/oauth/v1/get_access_token_by_saml'
NAMESPACE = 'http://schema.intuit.com/platform/fdatafeed/institutionlogin/v1'
TIME_FORMAT = '%Y-%m-%dT%T.%LZ'
DATE_FORMAT = '%Y-%m-%d'
TIMEOUT = 120
IGNORE_KEYS = Set.new([:'@xmlns', :'@xmlns:ns2', :'@xmlns:ns3', :'@xmlns:ns4', :'@xmlns:ns5', :'@xmlns:ns6', :'@xmlns:ns7', :'@xmlns:ns8'])
protected
def access_token(user_id)
token = oauth_token(user_id)
consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, {:timeout => TIMEOUT})
OAuth::AccessToken.new(consumer, token[:key], token[:secret])
end
def oauth_token(user_id)
now = Time.now.utc
if @oauth_token.nil? || @oauth_token[:expire_at] <= now || @oauth_token[:user_id] != user_id
@oauth_token = new_token(saml_message(user_id))
@oauth_token[:expire_at] = now + 9 * 60 # 9 minutes
@oauth_token[:user_id] = user_id
end
@oauth_token
end
def new_token(message)
uri = URI.parse(SAML_URL)
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri)
request['Authorization'] = %[OAuth oauth_consumer_key="#{@consumer_key}"]
request.set_form_data({:saml_assertion => message})
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
#http.set_debug_output($stdout)
response = http.request(request)
params = CGI::parse(response.body)
{key: params['oauth_token'][0], secret: params['oauth_token_secret'][0]}
end
def saml_message(user_id)
now = Time.now.utc
reference_id = SecureRandom.uuid.gsub('-', '')
assertion = %[#{@issuer_id}%%DIGEST%%%%SIGNATURE%%#{user_id}#{@issuer_id}urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified]
doc = Nokogiri::XML(assertion)
doc.xpath('//ds:Signature', 'ds' => 'http://www.w3.org/2000/09/xmldsig#').remove
doc.xpath('//text()[not(normalize-space())]').remove
digest = OpenSSL::Digest::SHA1.digest(doc.canonicalize(Nokogiri::XML::XML_C14N_1_1))
encoded_digest = Base64.encode64(digest).strip
signed_info = %[#{encoded_digest.strip}]
signature_value = Nokogiri::XML(signed_info).canonicalize
key = OpenSSL::PKey::RSA.new(File.read(@certificate_path))
encoded_signature_value = Base64.encode64(key.sign(OpenSSL::Digest::SHA1.new, signature_value)).gsub!(/\n/, '')
Base64.encode64(assertion.gsub(/%%DIGEST%%/, encoded_digest).gsub(/%%SIGNATURE%%/, encoded_signature_value))
end
def iso8601(time)
time.strftime(TIME_FORMAT)
end
def parse_xml(data)
@parser ||= Nori.new(:parser => :nokogiri,
:strip_namespaces => true,
:convert_tags_to => lambda { |tag| tag.snakecase.to_sym })
cleanup(@parser.parse(data))
end
def cleanup(hash)
hash.each do |k, v|
if IGNORE_KEYS.include?(k)
hash.delete(k)
elsif v.respond_to?(:keys)
cleanup(v)
end
end
hash
end
end
end