google-ads-common 0.6.4 → 0.7.0
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/ChangeLog +4 -0
- data/README +1 -1
- data/lib/ads_common/api.rb +66 -86
- data/lib/ads_common/api_config.rb +37 -64
- data/lib/ads_common/auth/base_handler.rb +9 -19
- data/lib/ads_common/auth/client_login_handler.rb +43 -44
- data/lib/ads_common/auth/oauth_handler.rb +63 -77
- data/lib/ads_common/build/savon_generator.rb +1 -5
- data/lib/ads_common/build/savon_service_generator.rb +3 -16
- data/lib/ads_common/credential_handler.rb +31 -9
- data/lib/ads_common/http.rb +20 -21
- data/lib/ads_common/parameters_validator.rb +2 -1
- data/lib/ads_common/results_extractor.rb +183 -0
- data/lib/ads_common/savon_headers/base_header_handler.rb +26 -21
- data/lib/ads_common/savon_headers/oauth_header_handler.rb +4 -29
- data/lib/ads_common/savon_service.rb +28 -174
- data/lib/ads_common/version.rb +1 -1
- data/test/coverage.rb +35 -0
- data/test/suite_unittests.rb +30 -0
- data/test/test_client_login_handler.rb +41 -8
- data/test/test_config.rb +3 -4
- data/test/test_credential_handler.rb +55 -0
- data/test/test_parameters_validator.rb +24 -1
- data/test/test_results_extractor.rb +165 -0
- data/test/test_savon_service.rb +14 -167
- metadata +38 -25
- data/lib/ads_common/savon_headers/simple_header_handler.rb +0 -63
@@ -1,6 +1,6 @@
|
|
1
1
|
# Encoding: utf-8
|
2
2
|
#
|
3
|
-
# Authors:: api.
|
3
|
+
# Authors:: api.dklimkin@gmail.com (Danial Klimkin)
|
4
4
|
#
|
5
5
|
# Copyright:: Copyright 2010, Google Inc. All Rights Reserved.
|
6
6
|
#
|
@@ -24,10 +24,10 @@
|
|
24
24
|
module AdsCommon
|
25
25
|
module Auth
|
26
26
|
class BaseHandler
|
27
|
+
|
27
28
|
# Default initializer.
|
28
29
|
def initialize(config)
|
29
30
|
@config = config
|
30
|
-
@logger = @config.read('library.logger')
|
31
31
|
@token = nil
|
32
32
|
end
|
33
33
|
|
@@ -44,18 +44,6 @@ module AdsCommon
|
|
44
44
|
raise error
|
45
45
|
end
|
46
46
|
|
47
|
-
# This method returns the set of fields to be included in the header.
|
48
|
-
# The generic method simply returns everything passed to it.
|
49
|
-
def header_list(credentials)
|
50
|
-
return credentials.keys.dup()
|
51
|
-
end
|
52
|
-
|
53
|
-
# This method returns the key value pairs to be included in the header.
|
54
|
-
# The generic method simply returns everything passed to it.
|
55
|
-
def headers(credentials)
|
56
|
-
return credentials.dup()
|
57
|
-
end
|
58
|
-
|
59
47
|
# Returns authorization token of some kind. Attempts to create a new one
|
60
48
|
# if the token has not yet been created and credentials present.
|
61
49
|
def get_token(credentials = nil)
|
@@ -63,15 +51,17 @@ module AdsCommon
|
|
63
51
|
return @token
|
64
52
|
end
|
65
53
|
|
66
|
-
# Creates authorization token. Needs to be overridden.
|
67
|
-
def create_token(credentials)
|
68
|
-
raise NotImplementedError, 'create_token not overridden.'
|
69
|
-
end
|
70
|
-
|
71
54
|
# Returns authorization string. Needs to be overridden.
|
72
55
|
def auth_string(credentials, request)
|
73
56
|
raise NotImplementedError, 'auth_string not overridden.'
|
74
57
|
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Creates authorization token. Needs to be overridden.
|
62
|
+
def create_token(credentials)
|
63
|
+
raise NotImplementedError, 'create_token not overridden.'
|
64
|
+
end
|
75
65
|
end
|
76
66
|
end
|
77
67
|
end
|
@@ -33,53 +33,38 @@ module AdsCommon
|
|
33
33
|
class ClientLoginHandler < AdsCommon::Auth::BaseHandler
|
34
34
|
ACCOUNT_TYPE = 'GOOGLE'
|
35
35
|
AUTH_PATH = '/accounts/ClientLogin'
|
36
|
+
AUTH_PREFIX = 'GoogleLogin auth='
|
36
37
|
CAPTCHA_PATH = '/accounts/'
|
37
|
-
IGNORED_FIELDS = [:email, :password, :auth_token]
|
38
38
|
|
39
39
|
# Initializes the ClientLoginHandler with all the necessary details.
|
40
|
-
def initialize(config,
|
40
|
+
def initialize(config, auth_server, service_name)
|
41
41
|
super(config)
|
42
|
-
@server =
|
42
|
+
@server = auth_server
|
43
43
|
@service_name = service_name
|
44
44
|
end
|
45
45
|
|
46
46
|
# Invalidates the stored token if the email, password or provided auth
|
47
47
|
# token have changed.
|
48
48
|
def property_changed(prop, value)
|
49
|
-
if [:
|
49
|
+
if [:email, :password].include?(prop)
|
50
50
|
@token = nil
|
51
51
|
end
|
52
|
+
if :auth_token.eql?(prop)
|
53
|
+
@token = create_token_from_string(value)
|
54
|
+
end
|
52
55
|
end
|
53
56
|
|
54
57
|
# Handle specific ClientLogin errors.
|
55
58
|
def handle_error(error)
|
56
59
|
# TODO: Add support for automatically regenerating auth tokens when they
|
57
60
|
# expire.
|
61
|
+
get_logger().error(error)
|
58
62
|
raise error
|
59
63
|
end
|
60
64
|
|
61
|
-
# Returns all of the fields that this auth handler will fill.
|
62
|
-
def header_list(credentials)
|
63
|
-
result = credentials.keys.map.reject do |field|
|
64
|
-
IGNORED_FIELDS.include?(field)
|
65
|
-
end
|
66
|
-
result << :authToken
|
67
|
-
return result
|
68
|
-
end
|
69
|
-
|
70
|
-
# Returns all of the credentials received from the CredentialHandler,
|
71
|
-
# except for ignored fields.
|
72
|
-
def headers(credentials)
|
73
|
-
result = credentials.reject do |field, value|
|
74
|
-
IGNORED_FIELDS.include?(field)
|
75
|
-
end
|
76
|
-
result[:authToken] = get_token(credentials)
|
77
|
-
return result
|
78
|
-
end
|
79
|
-
|
80
65
|
# Returns authorization string.
|
81
66
|
def auth_string(credentials, request)
|
82
|
-
return
|
67
|
+
return [AUTH_PREFIX, get_token(credentials)].join
|
83
68
|
end
|
84
69
|
|
85
70
|
private
|
@@ -95,18 +80,23 @@ module AdsCommon
|
|
95
80
|
#
|
96
81
|
def validate_credentials(credentials)
|
97
82
|
if credentials.nil?
|
98
|
-
raise AdsCommon::Errors::AuthError,
|
99
|
-
'No credentials supplied.'
|
100
|
-
end
|
101
|
-
|
102
|
-
if credentials[:email].nil?
|
103
|
-
raise AdsCommon::Errors::AuthError,
|
104
|
-
'Email address not included in credentials.'
|
83
|
+
raise AdsCommon::Errors::AuthError, 'No credentials supplied.'
|
105
84
|
end
|
106
85
|
|
107
|
-
if credentials[:
|
108
|
-
|
109
|
-
|
86
|
+
if credentials[:auth_token].nil?
|
87
|
+
if credentials[:email].nil?
|
88
|
+
raise AdsCommon::Errors::AuthError,
|
89
|
+
'Email address not included in credentials.'
|
90
|
+
end
|
91
|
+
if credentials[:password].nil?
|
92
|
+
raise AdsCommon::Errors::AuthError,
|
93
|
+
'Password not included in credentials.'
|
94
|
+
end
|
95
|
+
else
|
96
|
+
if credentials[:email] and credentials[:password]
|
97
|
+
get_logger().warn('Both auth_token and login credentials present' +
|
98
|
+
', preferring auth_token.')
|
99
|
+
end
|
110
100
|
end
|
111
101
|
end
|
112
102
|
|
@@ -118,35 +108,44 @@ module AdsCommon
|
|
118
108
|
# accessed
|
119
109
|
#
|
120
110
|
# Returns:
|
121
|
-
# - The auth token for the account
|
111
|
+
# - The auth token for the account
|
122
112
|
#
|
123
113
|
# Raises:
|
124
114
|
# - AdsCommon::Errors::AuthError if authentication fails
|
125
115
|
#
|
126
116
|
def create_token(credentials)
|
127
|
-
token =
|
117
|
+
token = credentials.include?(:auth_token) ?
|
118
|
+
create_token_from_string(credentials[:auth_token]) :
|
128
119
|
generate_token(credentials)
|
129
120
|
return token
|
130
121
|
end
|
131
122
|
|
132
|
-
#
|
133
|
-
def
|
134
|
-
|
123
|
+
# Creates token for provided auth string. Trivial for this handler.
|
124
|
+
def create_token_from_string(token_string)
|
125
|
+
return token_string
|
126
|
+
end
|
135
127
|
|
128
|
+
# Prepares POST data for ClientLogin request.
|
129
|
+
def get_login_data(credentials)
|
136
130
|
email = CGI.escape(credentials[:email])
|
137
131
|
password = CGI.escape(credentials[:password])
|
138
|
-
|
139
|
-
url = @server + AUTH_PATH
|
140
|
-
|
132
|
+
service_name = @service_name
|
141
133
|
data = "accountType=%s&Email=%s&Passwd=%s&service=%s" %
|
142
|
-
[ACCOUNT_TYPE, email, password,
|
143
|
-
|
134
|
+
[ACCOUNT_TYPE, email, password, service_name]
|
144
135
|
if credentials[:logintoken] and credentials[:logincaptcha]
|
145
136
|
data += "&logintoken=%s&logincaptcha=%s" %
|
146
137
|
[CGI.escape(credentials[:logintoken]),
|
147
138
|
CGI.escape(credentials[:logincaptcha])]
|
148
139
|
end
|
140
|
+
return data
|
141
|
+
end
|
142
|
+
|
143
|
+
# Generates new client login token based on credentials.
|
144
|
+
def generate_token(credentials)
|
145
|
+
validate_credentials(credentials)
|
149
146
|
|
147
|
+
url = @server + AUTH_PATH
|
148
|
+
data = get_login_data(credentials)
|
150
149
|
headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
151
150
|
|
152
151
|
response = AdsCommon::Http.post_response(url, data, @config, headers)
|
@@ -29,17 +29,11 @@ module AdsCommon
|
|
29
29
|
|
30
30
|
# Credentials class to handle OAuth authentication.
|
31
31
|
class OAuthHandler < AdsCommon::Auth::BaseHandler
|
32
|
-
IGNORED_FIELDS = [
|
33
|
-
:email, :password, :auth_token,
|
34
|
-
:oauth_verification_code, :oauth_consumer_secret, :oauth_consumer_key,
|
35
|
-
:oauth_token_secret, :oauth_token
|
36
|
-
]
|
37
|
-
|
38
32
|
OAUTH_CONFIG = {
|
39
|
-
:site =>
|
40
|
-
:request_token_path =>
|
41
|
-
:access_token_path =>
|
42
|
-
:authorize_path =>
|
33
|
+
:site => 'https://www.google.com',
|
34
|
+
:request_token_path => '/accounts/OAuthGetRequestToken',
|
35
|
+
:access_token_path => '/accounts/OAuthGetAccessToken',
|
36
|
+
:authorize_path => '/accounts/OAuthAuthorizeToken'
|
43
37
|
}
|
44
38
|
|
45
39
|
DEFAULT_CALLBACK = 'oob'
|
@@ -56,42 +50,17 @@ module AdsCommon
|
|
56
50
|
@scope = scope
|
57
51
|
end
|
58
52
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# Returns all of the fields that this auth handler will fill.
|
65
|
-
#
|
66
|
-
# Args:
|
67
|
-
# - credentials: request credentials
|
68
|
-
#
|
69
|
-
# Returns:
|
70
|
-
# - array with header names
|
71
|
-
#
|
72
|
-
def header_list(credentials)
|
73
|
-
result = credentials.keys.map.reject do |field|
|
74
|
-
IGNORED_FIELDS.include?(field)
|
53
|
+
# Invalidates the stored token if the required credential has changed.
|
54
|
+
def property_changed(prop, value)
|
55
|
+
if [:oauth_consumer_key, :oauth_consumer_secret].include?(prop)
|
56
|
+
@consumer, @token, @request_token = nil, nil, nil
|
75
57
|
end
|
76
|
-
result << :access_token
|
77
|
-
return result
|
78
58
|
end
|
79
59
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
# - credentials: request credentials
|
85
|
-
#
|
86
|
-
# Returns:
|
87
|
-
# - hash with header names and values
|
88
|
-
#
|
89
|
-
def headers(credentials)
|
90
|
-
result = credentials.reject do |field, value|
|
91
|
-
IGNORED_FIELDS.include?(field)
|
92
|
-
end
|
93
|
-
result[:access_token] = get_token(credentials)
|
94
|
-
return result
|
60
|
+
def handle_error(error)
|
61
|
+
# TODO: Add support.
|
62
|
+
get_logger().error(error)
|
63
|
+
raise error
|
95
64
|
end
|
96
65
|
|
97
66
|
# Returns OAuth-specific Consumer object.
|
@@ -108,6 +77,8 @@ module AdsCommon
|
|
108
77
|
return generate_oauth_parameters_string(credentials, request)
|
109
78
|
end
|
110
79
|
|
80
|
+
private
|
81
|
+
|
111
82
|
# Generates auth string for OAuth method of authentication.
|
112
83
|
#
|
113
84
|
# Args:
|
@@ -118,10 +89,11 @@ module AdsCommon
|
|
118
89
|
# - Authentication string
|
119
90
|
#
|
120
91
|
def generate_oauth_parameters_string(credentials, request)
|
92
|
+
# get_token() ensures @consumer is initialized.
|
93
|
+
token = get_token(credentials)
|
121
94
|
oauth_params = {
|
122
|
-
|
123
|
-
:token =>
|
124
|
-
:consumer => @consumer
|
95
|
+
:consumer => @consumer,
|
96
|
+
:token => token
|
125
97
|
}
|
126
98
|
oauth_helper = OAuth::Client::Helper.new(request, oauth_params)
|
127
99
|
return oauth_helper.header
|
@@ -139,6 +111,10 @@ module AdsCommon
|
|
139
111
|
# - AdsCommon::Errors::AuthError if validation fails
|
140
112
|
#
|
141
113
|
def validate_credentials(credentials)
|
114
|
+
if @scope.nil?
|
115
|
+
raise AdsCommon::Errors::AuthError, 'Scope is not specified.'
|
116
|
+
end
|
117
|
+
|
142
118
|
if credentials.nil?
|
143
119
|
raise AdsCommon::Errors::AuthError, 'No credentials supplied.'
|
144
120
|
end
|
@@ -152,6 +128,8 @@ module AdsCommon
|
|
152
128
|
raise AdsCommon::Errors::AuthError,
|
153
129
|
'Consumer secret not included in credentials.'
|
154
130
|
end
|
131
|
+
|
132
|
+
# TODO: add checks for both methods.
|
155
133
|
end
|
156
134
|
|
157
135
|
# Auxiliary method to generate an authentication token for logging via
|
@@ -171,15 +149,19 @@ module AdsCommon
|
|
171
149
|
#
|
172
150
|
def create_token(credentials)
|
173
151
|
validate_credentials(credentials)
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
152
|
+
@consumer ||= create_consumer(credentials)
|
153
|
+
return create_token_from_credentials(credentials, @consumer) ||
|
154
|
+
generate_access_token(credentials, @consumer)
|
155
|
+
end
|
156
|
+
|
157
|
+
def create_consumer(credentials)
|
158
|
+
oauth_config = OAUTH_CONFIG.merge({:scope => @scope})
|
159
|
+
proxy = @config.read('connection.proxy')
|
160
|
+
oauth_config[:proxy] = proxy unless proxy.nil?
|
161
|
+
return OAuth::Consumer.new(
|
162
|
+
credentials[:oauth_consumer_key],
|
163
|
+
credentials[:oauth_consumer_secret],
|
164
|
+
oauth_config)
|
183
165
|
end
|
184
166
|
|
185
167
|
# Creates access token based on data from credentials.
|
@@ -187,30 +169,31 @@ module AdsCommon
|
|
187
169
|
# Args:
|
188
170
|
# - credentials: a hash with the credentials for the account being
|
189
171
|
# accessed
|
172
|
+
# - consumer: OAuth consumer for the current configuration
|
190
173
|
#
|
191
174
|
# Returns:
|
192
175
|
# - The auth token for the account (as an AccessToken)
|
193
176
|
#
|
194
|
-
def create_token_from_credentials(credentials)
|
195
|
-
access_token = nil
|
196
|
-
|
177
|
+
def create_token_from_credentials(credentials, consumer)
|
197
178
|
token = credentials[:oauth_token]
|
198
|
-
if
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
179
|
+
if token.nil? or token.empty?
|
180
|
+
return nil
|
181
|
+
end
|
182
|
+
|
183
|
+
method = credentials[:oauth_method] || DEFAULT_METHOD
|
184
|
+
access_token = case method
|
185
|
+
when 'RSA-SHA1'
|
186
|
+
OAuth::AccessToken.from_hash(consumer, {:oauth_token => token})
|
187
|
+
when 'HMAC-SHA1'
|
188
|
+
token_secret = credentials[:oauth_token_secret]
|
189
|
+
if token_secret.nil? or token_secret.empty?
|
190
|
+
get_logger().warn(("The 'token' specified for method %s but " +
|
191
|
+
"'token secret' is not available, ignoring token") % method)
|
192
|
+
nil
|
193
|
+
else
|
194
|
+
OAuth::AccessToken.from_hash(consumer, {
|
195
|
+
:oauth_token => token, :oauth_token_secret => token_secret})
|
196
|
+
end
|
214
197
|
end
|
215
198
|
return access_token
|
216
199
|
end
|
@@ -220,18 +203,21 @@ module AdsCommon
|
|
220
203
|
# Args:
|
221
204
|
# - credentials: a hash with the credentials for the account being
|
222
205
|
# accessed
|
206
|
+
# - consumer: OAuth consumer for the current configuration
|
223
207
|
#
|
224
208
|
# Returns:
|
225
209
|
# - The auth token for the account (as an AccessToken)
|
226
210
|
#
|
227
|
-
def generate_access_token(credentials)
|
211
|
+
def generate_access_token(credentials, consumer)
|
228
212
|
token = nil
|
229
213
|
callback = credentials[:oauth_callback] || DEFAULT_CALLBACK
|
230
214
|
begin
|
231
215
|
if @request_token.nil?
|
232
216
|
@request_token = credentials[:oauth_request_token] ||
|
233
|
-
|
234
|
-
{:
|
217
|
+
consumer.get_request_token(
|
218
|
+
{:oauth_callback => callback},
|
219
|
+
{:scope => @scope}
|
220
|
+
)
|
235
221
|
end
|
236
222
|
verification_code = credentials[:oauth_verification_code]
|
237
223
|
if verification_code.nil? || verification_code.empty?
|
@@ -41,13 +41,10 @@ module AdsCommon
|
|
41
41
|
# - api_name an API name to generate for
|
42
42
|
# - version a version of the service
|
43
43
|
# - service_name a service name to generate stubs for
|
44
|
-
# - extensions an optional list of extensions to include
|
45
44
|
#
|
46
|
-
def initialize(wsdl_url, code_path, api_name, version, service_name
|
47
|
-
extensions = [])
|
45
|
+
def initialize(wsdl_url, code_path, api_name, version, service_name)
|
48
46
|
@wsdl_url = wsdl_url
|
49
47
|
@code_path = code_path
|
50
|
-
@extensions = extensions
|
51
48
|
@generator_args = {
|
52
49
|
:api_name => api_name,
|
53
50
|
:version => version,
|
@@ -112,7 +109,6 @@ module AdsCommon
|
|
112
109
|
wrapper_file = create_new_file(file_name)
|
113
110
|
generator = SavonServiceGenerator.new(@generator_args)
|
114
111
|
generator.add_actions(wsdl.soap_actions.dup)
|
115
|
-
generator.add_extensions(@extensions)
|
116
112
|
wrapper_file.write(generator.generate_code())
|
117
113
|
wrapper_file.close
|
118
114
|
end
|