intuit_ids_aggcat 0.0.5
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.
- data/LICENSE.txt +202 -0
- data/README.markdown +103 -0
- data/lib/intuit_ids_aggcat/client/intuit_xml_mappings.rb +396 -0
- data/lib/intuit_ids_aggcat/client/saml.rb +126 -0
- data/lib/intuit_ids_aggcat/client/services.rb +213 -0
- data/lib/intuit_ids_aggcat/core/configuration.rb +135 -0
- data/lib/intuit_ids_aggcat/core.rb +4 -0
- data/lib/intuit_ids_aggcat/rails.rb +106 -0
- data/lib/intuit_ids_aggcat/version.rb +3 -0
- data/lib/intuit_ids_aggcat.rb +23 -0
- data/spec/config/intuit_ids_aggcat.yml +5 -0
- data/spec/config/test.crt +17 -0
- data/spec/configuration_spec.rb +23 -0
- data/spec/rails_spec.rb +15 -0
- data/spec/saml_spec.rb +25 -0
- data/spec/services_spec.rb +109 -0
- data/spec/spec_helper.rb +7 -0
- metadata +166 -0
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'oauth'
|
2
|
+
require 'rexml/document'
|
3
|
+
require 'xml/mapping'
|
4
|
+
require 'intuit_ids_aggcat/client/intuit_xml_mappings'
|
5
|
+
|
6
|
+
module IntuitIdsAggcat
|
7
|
+
|
8
|
+
module Client
|
9
|
+
|
10
|
+
class Services
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Gets all institutions supported by Intuit. If oauth_token_info isn't provided, new tokens are provisioned using "default" user
|
20
|
+
# consumer_key and consumer_secret will be retrieved from the Configuration class if not provided
|
21
|
+
def get_institutions oauth_token_info = IntuitIdsAggcat::Client::Saml.get_tokens("default"), consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret
|
22
|
+
response = oauth_get_request "https://financialdatafeed.platform.intuit.com/rest-war/v1/institutions", oauth_token_info, consumer_key, consumer_secret
|
23
|
+
institutions = Institutions.load_from_xml(response[:response_xml].root)
|
24
|
+
institutions.institutions
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Gets the institution details for id. If oauth_token_info isn't provided, new tokens are provisioned using "default" user
|
29
|
+
# consumer_key and consumer_secret will be retrieved from the Configuration class if not provided
|
30
|
+
def get_institution_detail id, oauth_token_info = IntuitIdsAggcat::Client::Saml.get_tokens("default"), consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret
|
31
|
+
response = oauth_get_request "https://financialdatafeed.platform.intuit.com/rest-war/v1/institutions/#{id}", oauth_token_info, consumer_key, consumer_secret
|
32
|
+
institutions = InstitutionDetail.load_from_xml(response[:response_xml].root)
|
33
|
+
institutions
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Deletes the customer's accounts from aggregation at Intuit.
|
38
|
+
# username must be provided, if no oauth_token_info is provided, new tokens will be provisioned using username
|
39
|
+
def delete_customer username, oauth_token_info = IntuitIdsAggcat::Client::Saml.get_tokens(username), consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret
|
40
|
+
url = "https://financialdatafeed.platform.intuit.com/rest-war/v1/customers/"
|
41
|
+
oauth_delete_request url, oauth_token_info
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Deletes the a specific account for a customer from aggregation at Intuit.
|
46
|
+
# username and account ID must be provided, if no oauth_token_info is provided, new tokens will be provisioned using username
|
47
|
+
def delete_account username, account_id, oauth_token_info = IntuitIdsAggcat::Client::Saml.get_tokens(username), consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret
|
48
|
+
puts "in gem, username = #{username}, account = #{account_id}."
|
49
|
+
url = "https://financialdatafeed.platform.intuit.com/rest-war/v1/accounts/#{account_id}"
|
50
|
+
oauth_delete_request url, oauth_token_info
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Discovers and adds accounts using credentials
|
55
|
+
# institution_id is the ID of the institution, username is the ID for this customer's accounts at Intuit and must be used for future requests,
|
56
|
+
# creds_hash is a hash object of key value pairs used for authentication
|
57
|
+
# If oauth_token is not provided, new tokens will be provisioned using the username provided
|
58
|
+
# Returns a hash produced by discover_account_data_to_hash with the following keys:
|
59
|
+
# discover_response : hash including the following keys:
|
60
|
+
# response_code: HTTP response code from Intuit
|
61
|
+
# response_xml : XML returned by Intuit
|
62
|
+
# accounts : Ruby hash with accounts if returned by discover call
|
63
|
+
# challenge_type : text description of the type of challenge requested, if applicable
|
64
|
+
# "none" | "choice" | "image" | "text"
|
65
|
+
# challenge : Ruby hash with the detail of the challenge if applicable
|
66
|
+
# challenge_session_id: challenge session ID to pass to challenge_response if this is a challenge
|
67
|
+
# challenge_node_id : challenge node ID to pass to challenge_response if this is a challenge
|
68
|
+
# description : text description of the result of the discover request
|
69
|
+
def discover_and_add_accounts_with_credentials institution_id, username, creds_hash, oauth_token_info = IntuitIdsAggcat::Client::Saml.get_tokens(username), consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret, timeout = 30
|
70
|
+
url = "https://financialdatafeed.platform.intuit.com/rest-war/v1/institutions/#{institution_id}/logins"
|
71
|
+
credentials_array = []
|
72
|
+
creds_hash.each do |k,v|
|
73
|
+
c = Credential.new
|
74
|
+
c.name = k
|
75
|
+
c.value = v
|
76
|
+
credentials_array.push c
|
77
|
+
end
|
78
|
+
creds = Credentials.new
|
79
|
+
creds.credential = credentials_array
|
80
|
+
il = InstitutionLogin.new
|
81
|
+
il.credentials = creds
|
82
|
+
daa = oauth_post_request url, il.save_to_xml.to_s, oauth_token_info
|
83
|
+
discover_account_data_to_hash daa
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Given a username, response text, challenge session ID and challenge node ID, passes the credentials to Intuit to begin aggregation
|
88
|
+
def challenge_response institution_id, username, response, challenge_session_id, challenge_node_id, oauth_token_info = IntuitIdsAggcat::Client::Saml.get_tokens(username), consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret
|
89
|
+
url = "https://financialdatafeed.platform.intuit.com/rest-war/v1/institutions/#{institution_id}/logins"
|
90
|
+
if !(response.kind_of?(Array) || response.respond_to?('each'))
|
91
|
+
response = [response]
|
92
|
+
end
|
93
|
+
|
94
|
+
cr = IntuitIdsAggcat::ChallengeResponses.new
|
95
|
+
cr.response = response
|
96
|
+
il = IntuitIdsAggcat::InstitutionLogin.new
|
97
|
+
il.challenge_responses = cr
|
98
|
+
daa = oauth_post_request url, il.save_to_xml.to_s, oauth_token_info, { "challengeSessionId" => challenge_session_id, "challengeNodeId" => challenge_node_id }
|
99
|
+
discover_account_data_to_hash daa
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Gets all accounts for a customer
|
104
|
+
def get_customer_accounts username, oauth_token_info = IntuitIdsAggcat::Client::Saml.get_tokens(username), consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret
|
105
|
+
url = "https://financialdatafeed.platform.intuit.com/rest-war/v1/accounts/"
|
106
|
+
response = oauth_get_request url, oauth_token_info
|
107
|
+
accounts = AccountList.load_from_xml(response[:response_xml].root)
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Get transactions for a specific account and timeframe
|
112
|
+
def get_account_transactions username, account_id, start_date, end_date = nil, oauth_token_info = IntuitIdsAggcat::Client::Saml.get_tokens(username), consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret
|
113
|
+
txn_start = start_date.strftime("%Y-%m-%d")
|
114
|
+
url = "https://financialdatafeed.platform.intuit.com/rest-war/v1/accounts/#{account_id}/transactions?txnStartDate=#{txn_start}"
|
115
|
+
if !end_date.nil?
|
116
|
+
txn_end = end_date.strftime("%Y-%m-%d")
|
117
|
+
url = "#{url}&txnEndDate=#{txn_end}"
|
118
|
+
end
|
119
|
+
response = oauth_get_request url, oauth_token_info
|
120
|
+
xml = REXML::Document.new response[:response_xml].to_s
|
121
|
+
tl = IntuitIdsAggcat::TransactionList.load_from_xml xml.root
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Helper method for parsing discover account response data
|
126
|
+
def discover_account_data_to_hash daa
|
127
|
+
challenge_type = "none"
|
128
|
+
if daa[:response_code] == "201"
|
129
|
+
# return account list
|
130
|
+
accounts = AccountList.load_from_xml(daa[:response_xml].root)
|
131
|
+
{ discover_response: daa, accounts: accounts, challenge_type: challenge_type, challenge: nil, description: "Account information retrieved." }
|
132
|
+
elsif daa[:response_code] == "401" && daa[:challenge_session_id]
|
133
|
+
# return challenge
|
134
|
+
challenge = Challenges.load_from_xml(daa[:response_xml].root)
|
135
|
+
challenge_type = "unknown"
|
136
|
+
if challenge.save_to_xml.to_s.include?("<choice>")
|
137
|
+
challenge_type = "choice"
|
138
|
+
elsif challenge.save_to_xml.to_s.include?("image")
|
139
|
+
challenge_type ="image"
|
140
|
+
else
|
141
|
+
challenge_type = "text"
|
142
|
+
end
|
143
|
+
{ discover_response: daa, accounts: nil, challenge_type: challenge_type, challenge: challenge, challenge_session_id: daa[:challenge_session_id], challenge_node_id: daa[:challenge_node_id], description: "Multi-factor authentication required to retrieve accounts." }
|
144
|
+
elsif daa[:response_code] == "404"
|
145
|
+
{ discover_response: daa, accounts: nil, challenge_type: challenge_type, challenge: nil, description: "Institution not found." }
|
146
|
+
elsif daa[:response_code] == "408"
|
147
|
+
{ discover_response: daa, accounts: nil, challenge_type: challenge_type, challenge: nil, description: "Multi-factor authentication session expired." }
|
148
|
+
elsif daa[:response_code] == "500"
|
149
|
+
{ discover_response: daa, accounts: nil, challenge_type: challenge_type, challenge: nil, description: "Internal server error." }
|
150
|
+
elsif daa[:response_code] == "503"
|
151
|
+
{ discover_response: daa, accounts: nil, challenge_type: challenge_type, challenge: nil, description: "Problem at the finanical institution." }
|
152
|
+
else
|
153
|
+
{ discover_response: daa, accounts: nil, challenge_type: challenge_type, challenge: nil, description: "Unknown error." }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# Helper method to issue post requests
|
159
|
+
def oauth_post_request url, body, oauth_token_info, headers = {}, consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret, timeout = 120
|
160
|
+
oauth_token = oauth_token_info[:oauth_token]
|
161
|
+
oauth_token_secret = oauth_token_info[:oauth_token_secret]
|
162
|
+
|
163
|
+
options = { :request_token_path => 'https://financialdatafeed.platform.intuit.com', :timeout => timeout }
|
164
|
+
options = options.merge({ :proxy => IntuitIdsAggcat.config.proxy}) if !IntuitIdsAggcat.config.proxy.nil?
|
165
|
+
consumer = OAuth::Consumer.new(consumer_key, consumer_secret, options)
|
166
|
+
access_token = OAuth::AccessToken.new(consumer, oauth_token, oauth_token_secret)
|
167
|
+
response = access_token.post(url, body, { "Content-Type"=>'application/xml', 'Host' => 'financialdatafeed.platform.intuit.com' }.merge(headers))
|
168
|
+
response_xml = REXML::Document.new response.body
|
169
|
+
|
170
|
+
# handle challenge responses from discoverAndAcccounts flow
|
171
|
+
challenge_session_id = challenge_node_id = nil
|
172
|
+
if !response["challengeSessionId"].nil?
|
173
|
+
challenge_session_id = response["challengeSessionId"]
|
174
|
+
challenge_node_id = response["challengeNodeId"]
|
175
|
+
end
|
176
|
+
|
177
|
+
{ :challenge_session_id => challenge_session_id, :challenge_node_id => challenge_node_id, :response_code => response.code, :response_xml => response_xml }
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Helper method to issue get requests
|
182
|
+
def oauth_get_request url, oauth_token_info, consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret, timeout = 120
|
183
|
+
oauth_token = oauth_token_info[:oauth_token]
|
184
|
+
oauth_token_secret = oauth_token_info[:oauth_token_secret]
|
185
|
+
|
186
|
+
options = { :request_token_path => 'https://financialdatafeed.platform.intuit.com', :timeout => timeout }
|
187
|
+
options = options.merge({ :proxy => IntuitIdsAggcat.config.proxy}) if !IntuitIdsAggcat.config.proxy.nil?
|
188
|
+
consumer = OAuth::Consumer.new(consumer_key, consumer_secret, options)
|
189
|
+
access_token = OAuth::AccessToken.new(consumer, oauth_token, oauth_token_secret)
|
190
|
+
response = access_token.get(url, { "Content-Type"=>'application/xml', 'Host' => 'financialdatafeed.platform.intuit.com' })
|
191
|
+
response_xml = REXML::Document.new response.body
|
192
|
+
{ :response_code => response.code, :response_xml => response_xml }
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Helper method to issue delete requests
|
197
|
+
def oauth_delete_request url, oauth_token_info, consumer_key = IntuitIdsAggcat.config.oauth_consumer_key, consumer_secret = IntuitIdsAggcat.config.oauth_consumer_secret, timeout = 120
|
198
|
+
oauth_token = oauth_token_info[:oauth_token]
|
199
|
+
oauth_token_secret = oauth_token_info[:oauth_token_secret]
|
200
|
+
|
201
|
+
options = { :request_token_path => 'https://financialdatafeed.platform.intuit.com', :timeout => timeout }
|
202
|
+
options = options.merge({ :proxy => IntuitIdsAggcat.config.proxy}) if !IntuitIdsAggcat.config.proxy.nil?
|
203
|
+
consumer = OAuth::Consumer.new(consumer_key, consumer_secret, options)
|
204
|
+
access_token = OAuth::AccessToken.new(consumer, oauth_token, oauth_token_secret)
|
205
|
+
response = access_token.delete(url, { "Content-Type"=>'application/xml', 'Host' => 'financialdatafeed.platform.intuit.com' })
|
206
|
+
response_xml = REXML::Document.new response.body
|
207
|
+
{ :response_code => response.code, :response_xml => response_xml }
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module IntuitIdsAggcat
|
5
|
+
module Core
|
6
|
+
|
7
|
+
# A configuration object for the Intuit interface.
|
8
|
+
#
|
9
|
+
# == Configuring Credentials
|
10
|
+
#
|
11
|
+
# In order to do anything with the AggCat services you will need to assign credentials.
|
12
|
+
# The simplest method is to assing your credentials into the default
|
13
|
+
# configuration:
|
14
|
+
#
|
15
|
+
# AWS.config(:access_key_id => 'KEY', :secret_access_key => 'SECRET')
|
16
|
+
#
|
17
|
+
# You can also export them into your environment and they will be picked up
|
18
|
+
# automatically:
|
19
|
+
#
|
20
|
+
# export AWS_ACCESS_KEY_ID='YOUR_KEY_ID_HERE'
|
21
|
+
# export AWS_SECRET_ACCESS_KEY='YOUR_SECRET_KEY_HERE'
|
22
|
+
#
|
23
|
+
|
24
|
+
class Configuration
|
25
|
+
|
26
|
+
# Creates a new Configuration object.
|
27
|
+
def initialize options = {}
|
28
|
+
|
29
|
+
@created = options.delete(:__created__) || {}
|
30
|
+
options.each_pair do |opt_name, value|
|
31
|
+
opt_name = opt_name.to_sym
|
32
|
+
if self.class.accepted_options.include?(opt_name)
|
33
|
+
supplied[opt_name] = value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Hash] Returns a hash with your configured credentials.
|
40
|
+
def credentials
|
41
|
+
credentials = {}
|
42
|
+
[:saml_idp_id, :user_id].each do |opt|
|
43
|
+
if value = credential_provider.send(opt)
|
44
|
+
credentials[opt] = value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
credentials
|
48
|
+
end
|
49
|
+
|
50
|
+
def with options = {}
|
51
|
+
|
52
|
+
# symbolize option keys
|
53
|
+
options = options.inject({}) {|h,kv| h[kv.first.to_sym] = kv.last; h }
|
54
|
+
|
55
|
+
values = supplied.merge(options)
|
56
|
+
|
57
|
+
if supplied == values
|
58
|
+
self # nothing changed
|
59
|
+
else
|
60
|
+
self.class.new(values.merge(:__created__ => @created.dup))
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Hash] Returns a hash of all configuration values.
|
66
|
+
def to_h
|
67
|
+
self.class.accepted_options.inject({}) do |h,k|
|
68
|
+
h.merge(k => send(k))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
alias_method :to_hash, :to_h
|
72
|
+
|
73
|
+
# @return [Boolean] Returns true if the two configuration objects have
|
74
|
+
# the same values.
|
75
|
+
def eql? other
|
76
|
+
other.is_a?(self.class) and self.supplied == other.supplied
|
77
|
+
end
|
78
|
+
alias_method :==, :eql?
|
79
|
+
|
80
|
+
# @private
|
81
|
+
def inspect
|
82
|
+
"<#{self.class.name}>"
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def supplied
|
88
|
+
@supplied ||= {}
|
89
|
+
end
|
90
|
+
|
91
|
+
class << self
|
92
|
+
|
93
|
+
# @private
|
94
|
+
def accepted_options
|
95
|
+
@options ||= Set.new
|
96
|
+
end
|
97
|
+
|
98
|
+
# @private
|
99
|
+
def add_option name, default_value = nil, options = {}, &transform
|
100
|
+
|
101
|
+
accepted_options << name
|
102
|
+
|
103
|
+
define_method(name) do |&default_override|
|
104
|
+
|
105
|
+
value =
|
106
|
+
if supplied.has_key?(name)
|
107
|
+
supplied[name]
|
108
|
+
elsif default_override
|
109
|
+
default_override.call
|
110
|
+
else
|
111
|
+
default_value
|
112
|
+
end
|
113
|
+
|
114
|
+
transform ? transform.call(self, value) : value
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
alias_method("#{name}?", name) if options[:boolean]
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
add_option :certificate_path
|
125
|
+
add_option :certificate_string
|
126
|
+
add_option :certificate_password
|
127
|
+
add_option :issuer_id
|
128
|
+
add_option :oauth_consumer_key
|
129
|
+
add_option :oauth_consumer_secret
|
130
|
+
add_option :oauth_token_info
|
131
|
+
add_option :proxy
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module IntuitIdsAggcat
|
4
|
+
|
5
|
+
if Object.const_defined?(:Rails) and Rails.const_defined?(:Railtie)
|
6
|
+
|
7
|
+
# @private
|
8
|
+
class Railtie < Rails::Railtie
|
9
|
+
|
10
|
+
# configure our plugin on boot. other extension points such
|
11
|
+
# as configuration, rake tasks, etc, are also available
|
12
|
+
initializer "intuit_ids_aggcat.initialize" do |app|
|
13
|
+
IntuitIdsAggcat::Rails.setup
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
# A handful of useful Rails integration methods.
|
20
|
+
#
|
21
|
+
# If you require this gem inside a Rails application (via config.gem
|
22
|
+
# for rails 2 and bundler for rails 3) then {setup} is called
|
23
|
+
# automatically.
|
24
|
+
module Rails
|
25
|
+
|
26
|
+
# Adds extra functionality to Rails.
|
27
|
+
#
|
28
|
+
# Normailly this method is invoked automatically when you require this
|
29
|
+
# gem in a Rails Application:
|
30
|
+
#
|
31
|
+
# Rails 3+ (RAILS_ROOT/Gemfile)
|
32
|
+
#
|
33
|
+
# gem 'intuit_ids_aggcat'
|
34
|
+
#
|
35
|
+
|
36
|
+
# @return [nil]
|
37
|
+
def self.setup
|
38
|
+
load_yaml_config
|
39
|
+
log_to_rails_logger
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# Loads Intuit IDS AggCat configuration options from +RAILS_ROOT/config/intuit_ids_aggcat.yml+.
|
44
|
+
#
|
45
|
+
# This configuration file is optional. You can omit this file and instead
|
46
|
+
# use ruby to configure the gem inside a configuration initialization script
|
47
|
+
# (e.g. RAILS_ROOT/config/intializers/intuit_ids_aggcat.rb).
|
48
|
+
#
|
49
|
+
# If you have a yaml configuration file it should be formatted like the
|
50
|
+
# standard +database.yml+ file in a Rails application. This means there
|
51
|
+
# should be one section for Rails environment:
|
52
|
+
#
|
53
|
+
# development:
|
54
|
+
# certificate_path: path to private key
|
55
|
+
# issuer_id: SAML issuer ID provided by intuit
|
56
|
+
# oauth_consumer_key: OAuth consumer key
|
57
|
+
# oauth_consumer_secret: OAuth consumer secret
|
58
|
+
#
|
59
|
+
# production:
|
60
|
+
# certificate_path: path to private key
|
61
|
+
# issuer_id: SAML issuer ID provided by intuit
|
62
|
+
# oauth_consumer_key: OAuth consumer key
|
63
|
+
# oauth_consumer_secret: OAuth consumer secret
|
64
|
+
|
65
|
+
def self.load_yaml_config
|
66
|
+
|
67
|
+
path = Pathname.new("#{rails_root}/config/intuit_ids_aggcat.yml")
|
68
|
+
|
69
|
+
if File.exists?(path)
|
70
|
+
cfg = YAML::load(ERB.new(File.read(path)).result)
|
71
|
+
unless cfg[rails_env]
|
72
|
+
raise "config/intuit_ids_aggcat.yml is missing a section for `#{rails_env}`"
|
73
|
+
end
|
74
|
+
IntuitIdsAggcat.config(cfg[rails_env])
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# Configures gem to log to the Rails default logger.
|
81
|
+
# @return [nil]
|
82
|
+
def self.log_to_rails_logger
|
83
|
+
AWS.config(:logger => rails_logger)
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# @private
|
88
|
+
protected
|
89
|
+
def self.rails_env
|
90
|
+
::Rails.respond_to?(:env) ? ::Rails.env : RAILS_ENV
|
91
|
+
end
|
92
|
+
|
93
|
+
# @private
|
94
|
+
protected
|
95
|
+
def self.rails_root
|
96
|
+
::Rails.respond_to?(:root) ? ::Rails.root.to_s : RAILS_ROOT
|
97
|
+
end
|
98
|
+
|
99
|
+
# @private
|
100
|
+
protected
|
101
|
+
def self.rails_logger
|
102
|
+
::Rails.respond_to?(:logger) ? ::Rails.logger : ::RAILS_DEFAULT_LOGGER
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "intuit_ids_aggcat/version"
|
2
|
+
require "intuit_ids_aggcat/core"
|
3
|
+
|
4
|
+
module IntuitIdsAggcat
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# @private
|
8
|
+
@@config = nil
|
9
|
+
@@client = nil
|
10
|
+
|
11
|
+
def config options = {}
|
12
|
+
@@config ||= Core::Configuration.new
|
13
|
+
@@config = @@config.with(options) unless options.empty?
|
14
|
+
@@config
|
15
|
+
end
|
16
|
+
|
17
|
+
def client
|
18
|
+
@@client ||= Client::Services.new
|
19
|
+
@@client
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIICvDCCAiWgAwIBAgIJAKqWuLF2VM4+MA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV
|
3
|
+
BAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTESMBAGA1UEBxMJQ2hhcmxv
|
4
|
+
dHRlMQ0wCwYDVQQKEwRUZXN0MB4XDTEyMTAyNjE4NDk1NloXDTEyMTEyNTE4NDk1
|
5
|
+
NlowSTELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRIwEAYD
|
6
|
+
VQQHEwlDaGFybG90dGUxDTALBgNVBAoTBFRlc3QwgZ8wDQYJKoZIhvcNAQEBBQAD
|
7
|
+
gY0AMIGJAoGBAL9zKDXCbw+Xc8IugYrK7ffJsHQYr8rZKr0hw3XksEYRHb/LqCSw
|
8
|
+
ZF5aUTY1ECGb0QlD9uYxYWTvmpqZcTRIJANQgqhr1CH4hpEXJAdTJVzdJMaxeCjO
|
9
|
+
F40cHTwyeMP5Ou5Vsq3gKWTnJkGvDkJXHCqRkwduLk6FiWOfat2I9CjjAgMBAAGj
|
10
|
+
gaswgagwHQYDVR0OBBYEFGdV8DGee/TMuZDgQW1wdZ1k0HuNMHkGA1UdIwRyMHCA
|
11
|
+
FGdV8DGee/TMuZDgQW1wdZ1k0HuNoU2kSzBJMQswCQYDVQQGEwJVUzEXMBUGA1UE
|
12
|
+
CBMOTm9ydGggQ2Fyb2xpbmExEjAQBgNVBAcTCUNoYXJsb3R0ZTENMAsGA1UEChME
|
13
|
+
VGVzdIIJAKqWuLF2VM4+MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA
|
14
|
+
IdnfNuBftWE97JyhPiADYGsV8l+2KE2mxgqQacwGTgorxiwjBph1l26WY8fb3LCK
|
15
|
+
xKNDrMfcDJfSxgI8xqw3AsjDTWSF3tOqKTS6ApIwKf7l6BIMCTD2Xc6PTTmgNV20
|
16
|
+
NlLkzu6mXIXIuzBrUXfHUSXIsSUly+dX6fHKxMlToU8=
|
17
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe IntuitIdsAggcat::Core::Configuration do
|
4
|
+
it 'should configure issuer id' do
|
5
|
+
IntuitIdsAggcat.config(:issuer_id => "test_issuer")
|
6
|
+
IntuitIdsAggcat.config.issuer_id.should == "test_issuer"
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should configure oauth consumer key' do
|
10
|
+
IntuitIdsAggcat.config(:oauth_consumer_key => "consumer_key")
|
11
|
+
IntuitIdsAggcat.config.oauth_consumer_key.should == "consumer_key"
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should configure oauth consumer secret' do
|
15
|
+
IntuitIdsAggcat.config(:oauth_consumer_secret => "secret")
|
16
|
+
IntuitIdsAggcat.config.oauth_consumer_secret.should == "secret"
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should configure certificate path' do
|
20
|
+
IntuitIdsAggcat.config(:certificate_path => "cert")
|
21
|
+
IntuitIdsAggcat.config.certificate_path.should == "cert"
|
22
|
+
end
|
23
|
+
end
|
data/spec/rails_spec.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe IntuitIdsAggcat::Rails do
|
4
|
+
it 'should load configuration from a YAML file' do
|
5
|
+
Rails = Object.new
|
6
|
+
::Rails.stub(:root).and_return("#{Dir.pwd}/spec")
|
7
|
+
::Rails.stub(:env).and_return("development")
|
8
|
+
IntuitIdsAggcat::Rails.load_yaml_config
|
9
|
+
IntuitIdsAggcat.config.issuer_id.should == "rails_test_id"
|
10
|
+
IntuitIdsAggcat.config.certificate_path.should == "test.key"
|
11
|
+
IntuitIdsAggcat.config.oauth_consumer_key.should == "rails_test_key"
|
12
|
+
IntuitIdsAggcat.config.oauth_consumer_secret.should == "rails_test_secret"
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
data/spec/saml_spec.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe IntuitIdsAggcat::Client::Saml do
|
4
|
+
it 'should generate a SAML assertion' do
|
5
|
+
SecureRandom.stub(:uuid).and_return("6bf05546-89ee-4ec1-8cf7-e90c438cb147")
|
6
|
+
IntuitIdsAggcat::Client::Saml.get_saml_assertion_xml("rails_test_id", "test", "spec/config/test.key", nil, nil, Time.new(2008,6,21, 13,30,0, "+09:00")).should ==
|
7
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_6bf0554689ee4ec18cf7e90c438cb147\" IssueInstant=\"2008-06-21T04:30:00.000Z\" Version=\"2.0\"><saml2:Issuer>rails_test_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=\"#_6bf0554689ee4ec18cf7e90c438cb147\"><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>nzk4auzjFeqgAeWIKFAvtjxHKqc=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>Yv5BHxMttOlN+N+LQnEYXDvzCpTTpqWegDJQTnJtqyohH5WEllszOrDad0ZugO4NToa179aJkb0bhSHRlUZ83BAR2WqZjTG8a9tSEd/PGAbUkGmzNiGF8kYTuXAq//PmED6HYAO/PAXzaK9kubMLO+ZlOnyD0eW7+t913A0W0VY=</ds:SignatureValue></ds:Signature><saml2:Subject><saml2:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test</saml2:NameID><saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"/></saml2:Subject><saml2:Conditions NotBefore=\"2008-06-21T04:25:00.000Z\" NotOnOrAfter=\"2008-06-21T04:40:00.000Z\"><saml2:AudienceRestriction><saml2:Audience>rails_test_id</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AuthnStatement AuthnInstant=\"2008-06-21T04:30:00.000Z\" SessionIndex=\"_6bf0554689ee4ec18cf7e90c438cb147\"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement></saml2:Assertion>\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return signed XML" do
|
11
|
+
IntuitIdsAggcat::Client::Saml.get_signed_info_xml("123", "test").should ==
|
12
|
+
"<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=\"#123\"><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>test</ds:DigestValue></ds:Reference></ds:SignedInfo>"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return tokens" do
|
16
|
+
path = Pathname.new("spec/config/real_config.yml")
|
17
|
+
config = YAML::load(ERB.new(File.read(path)).result)
|
18
|
+
IntuitIdsAggcat.config(config)
|
19
|
+
tokens = IntuitIdsAggcat::Client::Saml.get_tokens "test"
|
20
|
+
tokens[:oauth_token_secret].should_not be_nil
|
21
|
+
tokens[:oauth_token].should_not be_nil
|
22
|
+
tokens[:token_expiry].should_not be_nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|