aggcat 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'oauth', '~> 0.4'
4
+ gem 'nori', '~> 2.0'
5
+ gem 'nokogiri', '~> 1.5'
6
+ gem 'active_support', '~> 3.0'
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Gene Drabkin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,71 @@
1
+ # Aggcat
2
+
3
+ Intuit Customer Account Data API client
4
+
5
+ ## Installation
6
+
7
+ Aggcat is available through [Rubygems](http://rubygems.org/gems/aggcat) and can be installed via:
8
+
9
+ ```
10
+ $ gem install aggcat
11
+ ```
12
+
13
+ or add it to your Gemfile like this:
14
+
15
+ ```
16
+ gem 'aggcat'
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require 'aggcat'
23
+
24
+ # configure Aggcat
25
+ Aggcat.configure do |config|
26
+ config.issuer_id = YOUR_ISSUER_ID
27
+ config.consumer_key = YOUR_CONSUMER_KEY
28
+ config.consumer_secret = YOUR_CONSUMER_SECRET
29
+ config.certificate_path = '/path/to/your/certificate/key'
30
+ end
31
+
32
+ # create client
33
+ client = Aggcat.client
34
+
35
+ # get all supported financial institutions
36
+ client.institutions
37
+ => {:response_code=>"200", :response=>{:institutions=>{:institution=>[{:institution_id=>"8860", :institution_name=>"Carolina Foothills FCU Credit Card", :home_url=>"http://www.cffcu.org/index.html", :phone_number=>"1-864-585-6838", :virtual=>false},
38
+
39
+ # get details for Bank of America
40
+ client.institution(14007)
41
+ => {:response_code=>"200", :response=>{:institution_detail=>{:institution_id=>"14007", :institution_name=>"Bank of America", :home_url=>"https://www.bankofamerica.com/", :phone_number=>"1-800-792-0808", :address=>{:address1=>"307 S. MAIN", :city=>"Charlotte", :state=>"NC", :postal_code=>"28255", :country=>"USA"}, :email_address=>"https://www.bankofamerica.com/contact/", :special_text=>"Please enter your Bank of America Online ID and Passcode required for login.", :currency_code=>"USD", :keys=>{:key=>[{:name=>"TAX_AGGR_ENABLED", :val=>"FALSE", :status=>"Active", :display_flag=>false, :display_order=>"20", :mask=>false}, {:name=>"passcode", :status=>"Active", :value_length_max=>"20", :display_flag=>true, :display_order=>"2", :mask=>true, :description=>"Passcode"}, {:name=>"onlineID", :status=>"Active", :value_length_max=>"32", :display_flag=>true, :display_order=>"1", :mask=>false, :description=>"Online ID"}]}}}}
42
+
43
+ # add new financial account to aggregate from Bank of America
44
+ client.discover_and_add_accounts(14007, username, password)
45
+
46
+ # get one financial account
47
+ client.account(account_id)
48
+
49
+ # get all aggregated accounts
50
+ client.accounts
51
+
52
+ # delete account
53
+ client.delete_account(account_id)
54
+
55
+ # get account transactions
56
+ client.account_transactions(account_id, start_date, end_date)
57
+
58
+ # delete all aggregated customers
59
+ client.delete_customers
60
+
61
+ ```
62
+
63
+ ## Documentation
64
+
65
+ Please make sure to read Intuit's [Account Data API](http://docs.developer.intuit.com/0020_Aggregation_Categorization_Apps/AggCat_API/0020_API_Documentation) docs.
66
+
67
+ ## Copyright
68
+ Copyright (c) 2013 Gene Drabkin.
69
+ See [LICENSE][] for details.
70
+
71
+ [license]: LICENSE.md
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'aggcat/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'aggcat'
8
+ spec.version = Aggcat::VERSION
9
+ spec.authors = ['Gene Drabkin']
10
+ spec.email = ['gene.drabkin@gmail.com']
11
+ spec.description = %q{Intuit Customer Account Data API client}
12
+ spec.summary = %q{Intuit Customer Account Data API client}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'oauth', '~> 0.4'
22
+ spec.add_development_dependency 'nori', '~> 2.0'
23
+ spec.add_development_dependency 'nokogiri', '~> 1.5'
24
+ spec.add_development_dependency 'active_support', '~> 3.0'
25
+ spec.add_development_dependency 'bundler', '~> 1.3'
26
+ spec.add_development_dependency 'rake'
27
+
28
+ end
@@ -0,0 +1,22 @@
1
+ require 'aggcat/version'
2
+ require 'aggcat/configurable'
3
+ require 'aggcat/base'
4
+ require 'aggcat/client'
5
+
6
+ module Aggcat
7
+ class << self
8
+ include Aggcat::Configurable
9
+
10
+ def client
11
+ @client ||= Aggcat::Client.new(options)
12
+ end
13
+
14
+ private
15
+
16
+ def method_missing(method_name, *args, &block)
17
+ return super unless client.respond_to?(method_name)
18
+ client.send(method_name, *args, &block)
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,96 @@
1
+ require 'active_support'
2
+ require 'active_support/builder'
3
+ require 'base64'
4
+ require 'cgi'
5
+ require 'net/https'
6
+ require 'nokogiri'
7
+ require 'nori'
8
+ require 'oauth'
9
+ require 'openssl'
10
+ require 'securerandom'
11
+ require 'uri'
12
+
13
+ module Aggcat
14
+ class Base
15
+
16
+ SAML_URL = 'https://oauth.intuit.com/oauth/v1/get_access_token_by_saml'
17
+
18
+ NAMESPACE = 'http://schema.intuit.com/platform/fdatafeed/institutionlogin/v1'
19
+ TIME_FORMAT = '%Y-%m-%dT%T.%LZ'
20
+ DATE_FORMAT = '%Y-%m-%d'
21
+
22
+ TIMEOUT = 120
23
+
24
+ DELETE_KEYS = {:'@xmlns' => nil, :'@xmlns:ns2' => nil, :'@xmlns:ns3' => nil, :'@xmlns:ns4' => nil, :'@xmlns:ns5' => nil, :'@xmlns:ns6' => nil, :'@xmlns:ns7' => nil, :'@xmlns:ns8' => nil}
25
+
26
+ protected
27
+
28
+ def access_token(consumer_id)
29
+ token = oauth_token(consumer_id)
30
+ consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, {:timeout => TIMEOUT})
31
+ OAuth::AccessToken.new(consumer, token[:key], token[:secret])
32
+ end
33
+
34
+ def oauth_token(user_id)
35
+ now = Time.now.utc
36
+ if @oauth_token.nil? || @oauth_token[:expire_at] <= now || @oauth_token[:user_id] != user_id
37
+ @oauth_token = new_token(saml_message(user_id))
38
+ @oauth_token[:expire_at] = now + 9 * 60 # 9 minutes
39
+ @oauth_token[:user_id] = user_id
40
+ end
41
+ @oauth_token
42
+ end
43
+
44
+ def new_token(message)
45
+ uri = URI.parse(SAML_URL)
46
+ http = Net::HTTP.new(uri.host, uri.port)
47
+ request = Net::HTTP::Post.new(uri.request_uri)
48
+ request['Authorization'] = %[OAuth oauth_consumer_key="#{@consumer_key}"]
49
+ request.set_form_data({:saml_assertion => message})
50
+ http.use_ssl = true
51
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
52
+ #http.set_debug_output($stdout)
53
+ response = http.request(request)
54
+ params = CGI::parse(response.body)
55
+ {key: params['oauth_token'][0], secret: params['oauth_token_secret'][0]}
56
+ end
57
+
58
+ def saml_message(user_id)
59
+ now = Time.now.utc
60
+ reference_id = SecureRandom.uuid.gsub('-', '')
61
+ assertion = %[<?xml version="1.0" encoding="UTF-8"?><saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_#{reference_id}" IssueInstant="#{iso8601(now)}" Version="2.0"><saml2:Issuer>#{@issuer_id}</saml2:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><ds:Reference URI="#_#{reference_id}"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>%%DIGEST%%</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>%%SIGNATURE%%</ds:SignatureValue></ds:Signature><saml2:Subject><saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">#{user_id}</saml2:NameID><saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"/></saml2:Subject><saml2:Conditions NotBefore="#{iso8601(now-5*60)}" NotOnOrAfter="#{iso8601(now+10*60)}"><saml2:AudienceRestriction><saml2:Audience>#{@issuer_id}</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AuthnStatement AuthnInstant="#{iso8601(now)}" SessionIndex="_#{reference_id}"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement></saml2:Assertion>]
62
+ doc = Nokogiri::XML(assertion)
63
+ doc.xpath('//ds:Signature', 'ds' => 'http://www.w3.org/2000/09/xmldsig#').remove
64
+ doc.xpath('//text()[not(normalize-space())]').remove
65
+ digest = OpenSSL::Digest::SHA1.digest(doc.canonicalize(Nokogiri::XML::XML_C14N_1_1))
66
+ encoded_digest = Base64.encode64(digest).strip
67
+ signed_info = %[<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><ds:Reference URI="#_#{reference_id}"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>#{encoded_digest.strip}</ds:DigestValue></ds:Reference></ds:SignedInfo>]
68
+ signature_value = Nokogiri::XML(signed_info).canonicalize
69
+ key = OpenSSL::PKey::RSA.new(File.read(@certificate_path))
70
+ encoded_signature_value = Base64.encode64(key.sign(OpenSSL::Digest::SHA1.new, signature_value)).gsub!(/\n/, '')
71
+ Base64.encode64(assertion.gsub(/%%DIGEST%%/, encoded_digest).gsub(/%%SIGNATURE%%/, encoded_signature_value))
72
+ end
73
+
74
+ def iso8601(time)
75
+ time.strftime(TIME_FORMAT)
76
+ end
77
+
78
+ def parse_xml(data)
79
+ @parser ||= Nori.new(:parser => :nokogiri,
80
+ :strip_namespaces => true,
81
+ :convert_tags_to => lambda { |tag| tag.snakecase.to_sym })
82
+ cleanup(@parser.parse(data))
83
+ end
84
+
85
+ def cleanup(hash)
86
+ hash.each do |k, v|
87
+ if DELETE_KEYS.has_key?(k)
88
+ hash.delete(k)
89
+ elsif v.respond_to?(:keys)
90
+ cleanup(v)
91
+ end
92
+ end
93
+ hash
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,98 @@
1
+ module Aggcat
2
+ class Client < Aggcat::Base
3
+
4
+ BASE_URL = 'https://financialdatafeed.platform.intuit.com/rest-war/v1'
5
+
6
+ def initialize(options={})
7
+ Aggcat::Configurable::KEYS.each do |key|
8
+ instance_variable_set(:"@#{key}", options[key] || Aggcat.instance_variable_get(:"@#{key}"))
9
+ end
10
+ end
11
+
12
+ def institutions
13
+ get('/institutions')
14
+ end
15
+
16
+ def institution(institution_id)
17
+ get("/institutions/#{institution_id}")
18
+ end
19
+
20
+ def discover_and_add_accounts(institution_id, username, password)
21
+ body = credentials(institution_id, username, password)
22
+ post("/institutions/#{institution_id}/logins", body, {user_id: "#{institution_id}-#{username}"})
23
+ end
24
+
25
+ def accounts
26
+ get('/accounts')
27
+ end
28
+
29
+ def account(account_id)
30
+ get("/accounts/#{account_id}")
31
+ end
32
+
33
+ def account_transactions(account_id, start_date = nil, end_date = nil)
34
+ uri = "/accounts/#{account_id}/transactions"
35
+ if start_date
36
+ uri += "?txnStartDate=#{start_date.strftime(DATE_FORMAT)}"
37
+ if end_date
38
+ uri += "&txnEndDate=#{end_date.strftime(DATE_FORMAT)}"
39
+ end
40
+ end
41
+ get(uri)
42
+ end
43
+
44
+ def delete_account(account_id)
45
+ delete("/accounts/#{account_id}")
46
+ end
47
+
48
+ def delete_customers
49
+ delete('/customers')
50
+ end
51
+
52
+ protected
53
+
54
+ def get(uri, options = {:user_id => 'default'})
55
+ response = access_token(options[:user_id]).get("#{BASE_URL}#{uri}")
56
+ {:response_code => response.code, :response => parse_xml(response.body)}
57
+ end
58
+
59
+ def post(uri, message, options = {})
60
+ response = access_token(options[:user_id]).post("#{BASE_URL}#{uri}", message, {'Content-Type' => 'application/xml'})
61
+ result = {:response_code => response.code, :response => parse_xml(response.body)}
62
+ if response['challengeSessionId']
63
+ result[:challenge_session_id] = response['challengeSessionId']
64
+ result[:challenge_node_id] = response['challengeNodeId']
65
+ end
66
+ result
67
+ end
68
+
69
+ def delete(uri, options = {:user_id => 'default'})
70
+ response = access_token(options[:user_id]).delete("#{BASE_URL}#{uri}")
71
+ {:response_code => response.code, :response => parse_xml(response.body)}
72
+ end
73
+
74
+ private
75
+
76
+ def credentials(institution_id, username, password)
77
+ institution = institution(institution_id)
78
+ keys = institution[:response][:institution_detail][:keys][:key].sort { |a, b| a[:display_order] <=> b[:display_order] }
79
+ hash = {
80
+ keys[0][:name] => username,
81
+ keys[1][:name] => password
82
+ }
83
+
84
+ xml = Builder::XmlMarkup.new
85
+ xml.InstitutionLogin('xmlns' => NAMESPACE) do |login|
86
+ login.credentials('xmlns:ns1' => NAMESPACE) do
87
+ hash.each do |key, value|
88
+ xml.tag!('ns1:credential', {'xmlns:ns2' => NAMESPACE}) do
89
+ xml.tag!('ns2:name', key)
90
+ xml.tag!('ns2:value', value)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,20 @@
1
+ module Aggcat
2
+ module Configurable
3
+
4
+ attr_writer :issuer_id, :consumer_key, :consumer_secret, :certificate_path
5
+
6
+ KEYS = [:issuer_id, :consumer_key, :consumer_secret, :certificate_path]
7
+
8
+ def configure
9
+ yield self
10
+ self
11
+ end
12
+
13
+ private
14
+
15
+ def options
16
+ Aggcat::Configurable::KEYS.inject({}) { |hash, key| hash[key] = instance_variable_get(:"@#{key}"); hash }
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Aggcat
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aggcat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Gene Drabkin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: oauth
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.4'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.4'
30
+ - !ruby/object:Gem::Dependency
31
+ name: nori
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: nokogiri
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.5'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.5'
62
+ - !ruby/object:Gem::Dependency
63
+ name: active_support
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '3.0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: bundler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '1.3'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '1.3'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Intuit Customer Account Data API client
111
+ email:
112
+ - gene.drabkin@gmail.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - Gemfile
119
+ - LICENSE.txt
120
+ - README.md
121
+ - Rakefile
122
+ - aggcat.gemspec
123
+ - lib/aggcat.rb
124
+ - lib/aggcat/base.rb
125
+ - lib/aggcat/client.rb
126
+ - lib/aggcat/configurable.rb
127
+ - lib/aggcat/version.rb
128
+ homepage: ''
129
+ licenses:
130
+ - MIT
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ none: false
143
+ requirements:
144
+ - - ! '>='
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubyforge_project:
149
+ rubygems_version: 1.8.25
150
+ signing_key:
151
+ specification_version: 3
152
+ summary: Intuit Customer Account Data API client
153
+ test_files: []