google-ads-common 0.8.2 → 0.9.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 +5 -0
- data/README +1 -2
- data/lib/ads_common/api.rb +10 -10
- data/lib/ads_common/auth/base_handler.rb +1 -1
- data/lib/ads_common/auth/client_login_handler.rb +1 -1
- data/lib/ads_common/auth/oauth2_handler.rb +57 -63
- data/lib/ads_common/auth/oauth2_jwt_handler.rb +201 -0
- data/lib/ads_common/errors.rb +0 -9
- data/lib/ads_common/savon_headers/oauth_header_handler.rb +1 -2
- data/lib/ads_common/utils.rb +18 -0
- data/lib/ads_common/version.rb +1 -1
- data/test/test_utils.rb +21 -0
- metadata +6 -23
- data/lib/ads_common/auth/oauth_handler.rb +0 -257
- data/lib/ads_common/savon_headers/httpi_request_proxy.rb +0 -51
data/ChangeLog
CHANGED
data/README
CHANGED
data/lib/ads_common/api.rb
CHANGED
@@ -25,8 +25,8 @@ require 'ads_common/config'
|
|
25
25
|
require 'ads_common/errors'
|
26
26
|
require 'ads_common/utils'
|
27
27
|
require 'ads_common/auth/client_login_handler'
|
28
|
-
require 'ads_common/auth/oauth_handler'
|
29
28
|
require 'ads_common/auth/oauth2_handler'
|
29
|
+
require 'ads_common/auth/oauth2_jwt_handler'
|
30
30
|
|
31
31
|
module AdsCommon
|
32
32
|
class Api
|
@@ -111,15 +111,12 @@ module AdsCommon
|
|
111
111
|
begin
|
112
112
|
credentials = @credential_handler.credentials
|
113
113
|
token = @auth_handler.get_token(credentials)
|
114
|
-
rescue AdsCommon::Errors::
|
115
|
-
AdsCommon::Errors::OAuth2VerificationRequired => e
|
114
|
+
rescue AdsCommon::Errors::OAuth2VerificationRequired => e
|
116
115
|
verification_code = (block_given?) ? yield(e.oauth_url) : nil
|
117
116
|
# Retry with verification code if one provided.
|
118
117
|
if verification_code
|
119
|
-
|
120
|
-
|
121
|
-
:oauth_verification_code : :oauth2_verification_code
|
122
|
-
@credential_handler.set_credential(code_symbol, verification_code)
|
118
|
+
@credential_handler.set_credential(
|
119
|
+
:oauth2_verification_code, verification_code)
|
123
120
|
retry
|
124
121
|
else
|
125
122
|
raise e
|
@@ -195,16 +192,19 @@ module AdsCommon
|
|
195
192
|
api_config.client_login_config(:LOGIN_SERVICE_NAME)
|
196
193
|
)
|
197
194
|
when :OAUTH
|
195
|
+
raise AdsCommon::Errors::Error,
|
196
|
+
'OAuth authorization method is deprecated, use OAuth2 instead.'
|
197
|
+
when :OAUTH2
|
198
198
|
environment = @config.read('service.environment',
|
199
199
|
api_config.default_environment())
|
200
|
-
AdsCommon::Auth::
|
200
|
+
AdsCommon::Auth::OAuth2Handler.new(
|
201
201
|
@config,
|
202
202
|
api_config.environment_config(environment, :oauth_scope)
|
203
203
|
)
|
204
|
-
when :
|
204
|
+
when :OAUTH2_JWT
|
205
205
|
environment = @config.read('service.environment',
|
206
206
|
api_config.default_environment())
|
207
|
-
AdsCommon::Auth::
|
207
|
+
AdsCommon::Auth::OAuth2JwtHandler.new(
|
208
208
|
@config,
|
209
209
|
api_config.environment_config(environment, :oauth_scope)
|
210
210
|
)
|
@@ -19,7 +19,8 @@
|
|
19
19
|
#
|
20
20
|
# This module manages OAuth2.0 authentication.
|
21
21
|
|
22
|
-
require '
|
22
|
+
require 'faraday'
|
23
|
+
require 'signet/oauth_2/client'
|
23
24
|
|
24
25
|
require 'ads_common/auth/base_handler'
|
25
26
|
require 'ads_common/errors'
|
@@ -30,11 +31,11 @@ module AdsCommon
|
|
30
31
|
# Credentials class to handle OAuth2.0 authentication.
|
31
32
|
class OAuth2Handler < AdsCommon::Auth::BaseHandler
|
32
33
|
OAUTH2_CONFIG = {
|
33
|
-
:
|
34
|
-
|
35
|
-
:
|
34
|
+
:authorization_uri =>
|
35
|
+
'https://accounts.google.com/o/oauth2/auth',
|
36
|
+
:token_credential_uri =>
|
37
|
+
'https://accounts.google.com/o/oauth2/token'
|
36
38
|
}
|
37
|
-
OAUTH2_HEADER = 'Bearer %s'
|
38
39
|
DEFAULT_CALLBACK = 'urn:ietf:wg:oauth:2.0:oob'
|
39
40
|
|
40
41
|
# Initializes the OAuthHandler2 with all the necessary details.
|
@@ -45,7 +46,7 @@ module AdsCommon
|
|
45
46
|
#
|
46
47
|
def initialize(config, scope)
|
47
48
|
super(config)
|
48
|
-
@scope = scope
|
49
|
+
@scope, @client = scope, nil
|
49
50
|
end
|
50
51
|
|
51
52
|
# Invalidates the stored token if the required credential has changed.
|
@@ -62,40 +63,37 @@ module AdsCommon
|
|
62
63
|
raise error
|
63
64
|
end
|
64
65
|
|
65
|
-
#
|
66
|
-
|
67
|
-
|
66
|
+
# Generates auth string for OAuth2.0 method of authentication.
|
67
|
+
#
|
68
|
+
# Args:
|
69
|
+
# - credentials: credentials set for authorization
|
70
|
+
#
|
71
|
+
# Returns:
|
72
|
+
# - Authentication string
|
73
|
+
#
|
74
|
+
def auth_string(credentials)
|
75
|
+
token = get_token(credentials)
|
76
|
+
return ::Signet::OAuth2.generate_bearer_authorization_header(
|
77
|
+
token[:access_token])
|
68
78
|
end
|
69
79
|
|
70
80
|
# Overrides base get_token method to account for the token expiration.
|
71
81
|
def get_token(credentials = nil)
|
72
82
|
token = super(credentials)
|
73
|
-
token = refresh_token! if
|
74
|
-
return
|
83
|
+
token = refresh_token! if !@client.nil? && @client.expired?
|
84
|
+
return token
|
75
85
|
end
|
76
86
|
|
77
87
|
# Refreshes access token from refresh token.
|
78
88
|
def refresh_token!()
|
79
|
-
return nil if @token.nil? or @token
|
80
|
-
@
|
89
|
+
return nil if @token.nil? or @token[:refresh_token].nil?
|
90
|
+
@client.refresh!
|
91
|
+
@token = token_from_client(@client)
|
81
92
|
return @token
|
82
93
|
end
|
83
94
|
|
84
95
|
private
|
85
96
|
|
86
|
-
# Generates auth string for OAuth2.0 method of authentication.
|
87
|
-
#
|
88
|
-
# Args:
|
89
|
-
# - credentials: credentials set for authorization
|
90
|
-
#
|
91
|
-
# Returns:
|
92
|
-
# - Authentication string
|
93
|
-
#
|
94
|
-
def generate_oauth2_parameters_string(credentials)
|
95
|
-
token = get_token(credentials)
|
96
|
-
return OAUTH2_HEADER % token[:access_token]
|
97
|
-
end
|
98
|
-
|
99
97
|
# Auxiliary method to validate the credentials for token generation.
|
100
98
|
#
|
101
99
|
# Args:
|
@@ -154,15 +152,14 @@ module AdsCommon
|
|
154
152
|
end
|
155
153
|
|
156
154
|
def create_client(credentials)
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
return client
|
155
|
+
oauth_options = OAUTH2_CONFIG.merge({
|
156
|
+
:client_id => credentials[:oauth2_client_id],
|
157
|
+
:client_secret => credentials[:oauth2_client_secret],
|
158
|
+
:scope => @scope,
|
159
|
+
:redirect_uri => credentials[:oauth2_callback] || DEFAULT_CALLBACK,
|
160
|
+
:state => credentials[:oauth2_state]
|
161
|
+
}).reject {|k, v| v.nil?}
|
162
|
+
return Signet::OAuth2::Client.new(oauth_options)
|
166
163
|
end
|
167
164
|
|
168
165
|
# Creates access token based on data from credentials.
|
@@ -177,13 +174,12 @@ module AdsCommon
|
|
177
174
|
# - The auth token for the account (as an AccessToken)
|
178
175
|
#
|
179
176
|
def create_token_from_credentials(credentials, client)
|
180
|
-
access_token = nil
|
181
177
|
oauth2_token_hash = credentials[:oauth2_token]
|
182
178
|
if !oauth2_token_hash.nil? && oauth2_token_hash.kind_of?(Hash)
|
183
|
-
token_data =
|
184
|
-
|
179
|
+
token_data = AdsCommon::Utils.hash_keys_to_str(oauth2_token_hash)
|
180
|
+
client.update_token!(token_data)
|
185
181
|
end
|
186
|
-
return
|
182
|
+
return token_from_client(client)
|
187
183
|
end
|
188
184
|
|
189
185
|
# Generates new request tokens and authorizes it to get access token.
|
@@ -194,45 +190,43 @@ module AdsCommon
|
|
194
190
|
# - client: OAuth2 client for the current configuration
|
195
191
|
#
|
196
192
|
# Returns:
|
197
|
-
# - The auth token for the account (as
|
193
|
+
# - The auth token for the account (as Hash)
|
198
194
|
#
|
199
195
|
def generate_access_token(credentials, client)
|
200
196
|
token = nil
|
201
197
|
begin
|
202
|
-
callback = credentials[:oauth2_callback] || DEFAULT_CALLBACK
|
203
198
|
verification_code = credentials[:oauth2_verification_code]
|
204
199
|
if verification_code.nil? || verification_code.empty?
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
:state => credentials[:oauth2_state],
|
209
|
-
:access_type => credentials[:oauth2_access_type],
|
210
|
-
:approval_prompt => credentials[:oauth2_approval_prompt]
|
200
|
+
uri_options = {
|
201
|
+
:access_type => credentials[:oauth2_access_type],
|
202
|
+
:approval_prompt => credentials[:oauth2_approval_prompt]
|
211
203
|
}.reject {|k, v| v.nil?}
|
212
|
-
|
213
|
-
raise AdsCommon::Errors::OAuth2VerificationRequired.new(
|
204
|
+
oauth_url = client.authorization_uri(uri_options)
|
205
|
+
raise AdsCommon::Errors::OAuth2VerificationRequired.new(oauth_url)
|
214
206
|
else
|
215
|
-
|
216
|
-
|
207
|
+
client.code = verification_code
|
208
|
+
proxy = @config.read('connection.proxy')
|
209
|
+
connection = (proxy.nil?) ? nil : Faraday.new(:proxy => proxy)
|
210
|
+
token = AdsCommon::Utils.hash_keys_to_sym(
|
211
|
+
client.fetch_access_token!(:connection => connection))
|
217
212
|
end
|
218
|
-
rescue
|
213
|
+
rescue Signet::AuthorizationError => e
|
219
214
|
raise AdsCommon::Errors::AuthError,
|
220
215
|
'Authorization error occured: %s' % e
|
221
216
|
end
|
222
217
|
return token
|
223
218
|
end
|
224
219
|
|
225
|
-
#
|
226
|
-
def
|
227
|
-
return
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
}
|
220
|
+
# Create a token Hash from a client.
|
221
|
+
def token_from_client(client)
|
222
|
+
return nil if client.refresh_token.nil? && client.access_token.nil?
|
223
|
+
return {
|
224
|
+
:access_token => client.access_token,
|
225
|
+
:refresh_token => client.refresh_token,
|
226
|
+
:issued_at => client.issued_at,
|
227
|
+
:expires_in => client.expires_in,
|
228
|
+
:id_token => client.id_token
|
229
|
+
}
|
236
230
|
end
|
237
231
|
end
|
238
232
|
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
#
|
3
|
+
# Authors:: api.dklimkin@gmail.com (Danial Klimkin)
|
4
|
+
#
|
5
|
+
# Copyright:: Copyright 2012, Google Inc. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# License:: Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
16
|
+
# implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
#
|
20
|
+
# This module manages OAuth2.0 JWT authentication.
|
21
|
+
|
22
|
+
require 'faraday'
|
23
|
+
require 'signet/oauth_2/client'
|
24
|
+
|
25
|
+
require 'ads_common/auth/base_handler'
|
26
|
+
require 'ads_common/errors'
|
27
|
+
|
28
|
+
module AdsCommon
|
29
|
+
module Auth
|
30
|
+
|
31
|
+
# Credentials class to handle OAuth2.0 authentication.
|
32
|
+
class OAuth2JwtHandler < AdsCommon::Auth::BaseHandler
|
33
|
+
|
34
|
+
OAUTH2_CONFIG = {
|
35
|
+
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
|
36
|
+
:audience => 'https://accounts.google.com/o/oauth2/token'
|
37
|
+
}
|
38
|
+
|
39
|
+
# Initializes the OAuthHandler2 with all the necessary details.
|
40
|
+
#
|
41
|
+
# Args:
|
42
|
+
# - config: Config object with library configuration
|
43
|
+
# - scope: OAuth authorization scope
|
44
|
+
#
|
45
|
+
def initialize(config, scope)
|
46
|
+
super(config)
|
47
|
+
@scope, @client = scope, nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Invalidates the stored token if the required credential has changed.
|
51
|
+
def property_changed(prop, value)
|
52
|
+
oauth2_keys =
|
53
|
+
[:oauth2_issuer, :oauth2_secret, :oauth2_keyfile, :oauth2_key]
|
54
|
+
@client = nil if oauth2_keys.include?(prop)
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle_error(error)
|
58
|
+
# TODO: Add support.
|
59
|
+
get_logger().error(error)
|
60
|
+
raise error
|
61
|
+
end
|
62
|
+
|
63
|
+
# Generates auth string for OAuth2.0 JWT method of authentication.
|
64
|
+
#
|
65
|
+
# Args:
|
66
|
+
# - credentials: credentials set for authorization
|
67
|
+
#
|
68
|
+
# Returns:
|
69
|
+
# - Authentication string
|
70
|
+
#
|
71
|
+
def auth_string(credentials)
|
72
|
+
token = get_token(credentials)
|
73
|
+
return ::Signet::OAuth2.generate_bearer_authorization_header(
|
74
|
+
token[:access_token])
|
75
|
+
end
|
76
|
+
|
77
|
+
# Overrides base get_token method to account for the token expiration.
|
78
|
+
def get_token(credentials = nil)
|
79
|
+
token = super(credentials)
|
80
|
+
token = refresh_token! if !@client.nil? && @client.expired?
|
81
|
+
return token
|
82
|
+
end
|
83
|
+
|
84
|
+
# Refreshes access token from refresh token.
|
85
|
+
def refresh_token!()
|
86
|
+
return nil if @token.nil? or @token[:refresh_token].nil?
|
87
|
+
@client.refresh!
|
88
|
+
@token = token_from_client(@client)
|
89
|
+
return @token
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Auxiliary method to validate the credentials for JWT authentication.
|
95
|
+
#
|
96
|
+
# Args:
|
97
|
+
# - credentials: a hash with the credentials for the account being
|
98
|
+
# accessed
|
99
|
+
#
|
100
|
+
# Raises:
|
101
|
+
# - AdsCommon::Errors::AuthError if validation fails
|
102
|
+
#
|
103
|
+
def validate_credentials(credentials)
|
104
|
+
if @scope.nil?
|
105
|
+
raise AdsCommon::Errors::AuthError, 'Scope is not specified.'
|
106
|
+
end
|
107
|
+
|
108
|
+
if credentials.nil?
|
109
|
+
raise AdsCommon::Errors::AuthError, 'No credentials supplied.'
|
110
|
+
end
|
111
|
+
|
112
|
+
if credentials[:oauth2_issuer].nil?
|
113
|
+
raise AdsCommon::Errors::AuthError,
|
114
|
+
'Issuer is not included in the credentials.'
|
115
|
+
end
|
116
|
+
|
117
|
+
if credentials[:oauth2_secret].nil?
|
118
|
+
raise AdsCommon::Errors::AuthError,
|
119
|
+
'Key secret is not included in the credentials.'
|
120
|
+
end
|
121
|
+
|
122
|
+
if credentials[:oauth2_key].nil? && credentials[:oauth2_keyfile].nil?
|
123
|
+
raise AdsCommon::Errors::AuthError,
|
124
|
+
'Either key or key file must be provided for OAuth2 JWT.'
|
125
|
+
end
|
126
|
+
|
127
|
+
if credentials[:oauth2_key] && credentials[:oauth2_keyfile]
|
128
|
+
raise AdsCommon::Errors::AuthError,
|
129
|
+
'Both JWT key and key file provided, only one can be used.'
|
130
|
+
end
|
131
|
+
|
132
|
+
if credentials[:oauth2_key] &&
|
133
|
+
!credentials[:oauth2_key].kind_of?(OpenSSL::PKey::RSA)
|
134
|
+
raise AdsCommon::Errors::AuthError,
|
135
|
+
'OAuth2 JWT key provided must be of type OpenSSL::PKey::RSA.'
|
136
|
+
end
|
137
|
+
|
138
|
+
if credentials[:oauth2_keyfile] &&
|
139
|
+
!File.file?(credentials[:oauth2_keyfile])
|
140
|
+
raise AdsCommon::Errors::AuthError,
|
141
|
+
"Key file '%s' does not exist or not a file."
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Auxiliary method to generate an authentication token for logging via
|
146
|
+
# the OAuth2.0 API.
|
147
|
+
#
|
148
|
+
# Args:
|
149
|
+
# - credentials: a hash with the credentials for the account being
|
150
|
+
# accessed
|
151
|
+
#
|
152
|
+
# Returns:
|
153
|
+
# - The auth token for the account (as an AccessToken)
|
154
|
+
#
|
155
|
+
# Raises:
|
156
|
+
# - AdsCommon::Errors::AuthError if authentication fails
|
157
|
+
# - AdsCommon::Errors::OAuthVerificationRequired if OAuth verification
|
158
|
+
# code required
|
159
|
+
#
|
160
|
+
def create_token(credentials)
|
161
|
+
validate_credentials(credentials)
|
162
|
+
@client ||= create_client(credentials)
|
163
|
+
@client.fetch_access_token!()
|
164
|
+
return token_from_client(@client)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Creates a Signet client based on credentials.
|
168
|
+
def create_client(credentials)
|
169
|
+
credentials = load_oauth2_jwt_credentials(credentials)
|
170
|
+
oauth_options = OAUTH2_CONFIG.merge({
|
171
|
+
:issuer => credentials[:oauth2_issuer],
|
172
|
+
:signing_key => credentials[:oauth2_key],
|
173
|
+
:scope => @scope,
|
174
|
+
})
|
175
|
+
return Signet::OAuth2::Client.new(oauth_options)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Loads JWT key if configured with a filename.
|
179
|
+
def load_oauth2_jwt_credentials(credentials)
|
180
|
+
return credentials unless credentials.include?(:oauth2_keyfile)
|
181
|
+
key_file = File.read(credentials[:oauth2_keyfile])
|
182
|
+
key_secret = credentials[:oauth2_secret]
|
183
|
+
key = OpenSSL::PKCS12.new(key_file, key_secret).key
|
184
|
+
result = credentials.merge({:oauth2_key => key})
|
185
|
+
result.delete(:oauth2_keyfile)
|
186
|
+
return result
|
187
|
+
end
|
188
|
+
|
189
|
+
# Create a token Hash from a client.
|
190
|
+
def token_from_client(client)
|
191
|
+
return nil if client.access_token.nil?
|
192
|
+
return {
|
193
|
+
:access_token => client.access_token,
|
194
|
+
:issued_at => client.issued_at,
|
195
|
+
:expires_in => client.expires_in,
|
196
|
+
:id_token => client.id_token
|
197
|
+
}
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
data/lib/ads_common/errors.rb
CHANGED
@@ -39,15 +39,6 @@ module AdsCommon
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
# Raised when OAuth1.0a access token is required.
|
43
|
-
class OAuthVerificationRequired < AuthError
|
44
|
-
attr_reader :oauth_url, :request_token
|
45
|
-
def initialize(oauth_url, request_token)
|
46
|
-
super()
|
47
|
-
@oauth_url, @request_token = oauth_url, request_token
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
42
|
# Raised when OAuth2.0 access token is required.
|
52
43
|
class OAuth2VerificationRequired < AuthError
|
53
44
|
attr_reader :oauth_url
|
@@ -20,7 +20,6 @@
|
|
20
20
|
# Handles SOAP headers and namespaces definition for OAuth type header.
|
21
21
|
|
22
22
|
require 'ads_common/savon_headers/base_header_handler'
|
23
|
-
require 'ads_common/savon_headers/httpi_request_proxy'
|
24
23
|
|
25
24
|
module AdsCommon
|
26
25
|
module SavonHeaders
|
@@ -43,7 +42,7 @@ module AdsCommon
|
|
43
42
|
credentials = @credential_handler.credentials
|
44
43
|
request.url = soap.endpoint
|
45
44
|
request.headers['Authorization'] =
|
46
|
-
@auth_handler.auth_string(credentials
|
45
|
+
@auth_handler.auth_string(credentials)
|
47
46
|
end
|
48
47
|
end
|
49
48
|
end
|
data/lib/ads_common/utils.rb
CHANGED
@@ -31,6 +31,24 @@ module AdsCommon
|
|
31
31
|
return result
|
32
32
|
end
|
33
33
|
end
|
34
|
+
|
35
|
+
# Converts all hash keys to strings.
|
36
|
+
def self.hash_keys_to_str(data)
|
37
|
+
return nil if data.nil?
|
38
|
+
return data.inject({}) do |result, (k, v)|
|
39
|
+
result[k.to_s] = v
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Converts all hash keys to symbols.
|
45
|
+
def self.hash_keys_to_sym(data)
|
46
|
+
return nil if data.nil?
|
47
|
+
return data.inject({}) do |result, (k, v)|
|
48
|
+
result[k.to_sym] = v
|
49
|
+
result
|
50
|
+
end
|
51
|
+
end
|
34
52
|
end
|
35
53
|
end
|
36
54
|
|
data/lib/ads_common/version.rb
CHANGED
data/test/test_utils.rb
CHANGED
@@ -54,4 +54,25 @@ class TestUtils < Test::Unit::TestCase
|
|
54
54
|
assert_not_same(str, result)
|
55
55
|
assert_equal('str_snake', str)
|
56
56
|
end
|
57
|
+
|
58
|
+
def test_hash_keys_to_str()
|
59
|
+
data = {:a => 'aa', :b5 => 43, 'xyz' => :abc}
|
60
|
+
result = AdsCommon::Utils.hash_keys_to_str(data)
|
61
|
+
assert_equal('aa', result['a'])
|
62
|
+
assert_equal(43, result['b5'])
|
63
|
+
assert_equal(:abc, result['xyz'])
|
64
|
+
assert_equal(3, result.size)
|
65
|
+
assert_not_same(result, data)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_hash_keys_to_sym()
|
69
|
+
data = {:a => 'aa', :b5 => 43, 'xyz' => :abc, 'f5' => :xyz}
|
70
|
+
result = AdsCommon::Utils.hash_keys_to_sym(data)
|
71
|
+
assert_equal('aa', result[:a])
|
72
|
+
assert_equal(43, result[:b5])
|
73
|
+
assert_equal(:abc, result[:xyz])
|
74
|
+
assert_equal(:xyz, result[:f5])
|
75
|
+
assert_equal(4, result.size)
|
76
|
+
assert_not_same(result, data)
|
77
|
+
end
|
57
78
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: google-ads-common
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2013-01-08 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: savon
|
@@ -45,13 +45,13 @@ dependencies:
|
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: 1.1.0
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
|
-
name:
|
48
|
+
name: signet
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.4.
|
54
|
+
version: 0.4.4
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
57
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -59,23 +59,7 @@ dependencies:
|
|
59
59
|
requirements:
|
60
60
|
- - ~>
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0.4.
|
63
|
-
- !ruby/object:Gem::Dependency
|
64
|
-
name: oauth2
|
65
|
-
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
|
-
requirements:
|
68
|
-
- - ~>
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
version: 0.8.0
|
71
|
-
type: :runtime
|
72
|
-
prerelease: false
|
73
|
-
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
|
-
requirements:
|
76
|
-
- - ~>
|
77
|
-
- !ruby/object:Gem::Version
|
78
|
-
version: 0.8.0
|
62
|
+
version: 0.4.4
|
79
63
|
description: Essential utilities shared by all Ads Ruby client libraries
|
80
64
|
email:
|
81
65
|
- api.dklimkin@gmail.com
|
@@ -91,14 +75,13 @@ files:
|
|
91
75
|
- lib/ads_common/api_config.rb
|
92
76
|
- lib/ads_common/auth/client_login_handler.rb
|
93
77
|
- lib/ads_common/auth/oauth2_handler.rb
|
94
|
-
- lib/ads_common/auth/oauth_handler.rb
|
95
78
|
- lib/ads_common/auth/base_handler.rb
|
79
|
+
- lib/ads_common/auth/oauth2_jwt_handler.rb
|
96
80
|
- lib/ads_common/http.rb
|
97
81
|
- lib/ads_common/errors.rb
|
98
82
|
- lib/ads_common/parameters_validator.rb
|
99
83
|
- lib/ads_common/savon_headers/oauth_header_handler.rb
|
100
84
|
- lib/ads_common/savon_headers/base_header_handler.rb
|
101
|
-
- lib/ads_common/savon_headers/httpi_request_proxy.rb
|
102
85
|
- lib/ads_common/results_extractor.rb
|
103
86
|
- lib/ads_common/build/savon_abstract_generator.rb
|
104
87
|
- lib/ads_common/build/savon_service_generator.rb
|
@@ -1,257 +0,0 @@
|
|
1
|
-
# Encoding: utf-8
|
2
|
-
#
|
3
|
-
# Authors:: api.dklimkin@gmail.com (Danial Klimkin)
|
4
|
-
#
|
5
|
-
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
|
6
|
-
#
|
7
|
-
# License:: Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
-
# you may not use this file except in compliance with the License.
|
9
|
-
# You may obtain a copy of the License at
|
10
|
-
#
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
-
#
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
16
|
-
# implied.
|
17
|
-
# See the License for the specific language governing permissions and
|
18
|
-
# limitations under the License.
|
19
|
-
#
|
20
|
-
# This module manages OAuth1.0a authentication.
|
21
|
-
|
22
|
-
require 'oauth'
|
23
|
-
|
24
|
-
require 'ads_common/auth/base_handler'
|
25
|
-
require 'ads_common/errors'
|
26
|
-
|
27
|
-
module AdsCommon
|
28
|
-
module Auth
|
29
|
-
|
30
|
-
# Credentials class to handle OAuth authentication.
|
31
|
-
class OAuthHandler < AdsCommon::Auth::BaseHandler
|
32
|
-
OAUTH_CONFIG = {
|
33
|
-
:site => 'https://www.google.com',
|
34
|
-
:request_token_path => '/accounts/OAuthGetRequestToken',
|
35
|
-
:access_token_path => '/accounts/OAuthGetAccessToken',
|
36
|
-
:authorize_path => '/accounts/OAuthAuthorizeToken'
|
37
|
-
}
|
38
|
-
|
39
|
-
DEFAULT_CALLBACK = 'oob'
|
40
|
-
DEFAULT_METHOD = 'HMAC-SHA1'
|
41
|
-
|
42
|
-
# Initializes the OAuthHandler with all the necessary details.
|
43
|
-
#
|
44
|
-
# Args:
|
45
|
-
# - config: Config object with library configuration
|
46
|
-
# - scope: OAuth authorization scope
|
47
|
-
#
|
48
|
-
def initialize(config, scope)
|
49
|
-
super(config)
|
50
|
-
@scope = scope
|
51
|
-
end
|
52
|
-
|
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
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def handle_error(error)
|
61
|
-
# TODO: Add support.
|
62
|
-
get_logger().error(error)
|
63
|
-
raise error
|
64
|
-
end
|
65
|
-
|
66
|
-
# Returns authorization string.
|
67
|
-
def auth_string(credentials, request)
|
68
|
-
if request.nil?
|
69
|
-
raise AdsCommon::Errors::AuthError,
|
70
|
-
'Request is required for OAuth generator.'
|
71
|
-
end
|
72
|
-
return generate_oauth_parameters_string(credentials, request)
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
# Generates auth string for OAuth method of authentication.
|
78
|
-
#
|
79
|
-
# Args:
|
80
|
-
# - credentials: credentials set for authorization
|
81
|
-
# - request: a HTTPI Request to generate headers for
|
82
|
-
#
|
83
|
-
# Returns:
|
84
|
-
# - Authentication string
|
85
|
-
#
|
86
|
-
def generate_oauth_parameters_string(credentials, request)
|
87
|
-
# get_token() ensures @consumer is initialized.
|
88
|
-
token = get_token(credentials)
|
89
|
-
oauth_params = {
|
90
|
-
:consumer => @consumer,
|
91
|
-
:token => token
|
92
|
-
}
|
93
|
-
oauth_helper = OAuth::Client::Helper.new(request, oauth_params)
|
94
|
-
return oauth_helper.header
|
95
|
-
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
# Auxiliary method to validate the credentials for token generation.
|
100
|
-
#
|
101
|
-
# Args:
|
102
|
-
# - credentials: a hash with the credentials for the account being
|
103
|
-
# accessed
|
104
|
-
#
|
105
|
-
# Raises:
|
106
|
-
# - AdsCommon::Errors::AuthError if validation fails
|
107
|
-
#
|
108
|
-
def validate_credentials(credentials)
|
109
|
-
if @scope.nil?
|
110
|
-
raise AdsCommon::Errors::AuthError, 'Scope is not specified.'
|
111
|
-
end
|
112
|
-
|
113
|
-
if credentials.nil?
|
114
|
-
raise AdsCommon::Errors::AuthError, 'No credentials supplied.'
|
115
|
-
end
|
116
|
-
|
117
|
-
if credentials[:oauth_consumer_key].nil?
|
118
|
-
raise AdsCommon::Errors::AuthError,
|
119
|
-
'Consumer key not included in credentials.'
|
120
|
-
end
|
121
|
-
|
122
|
-
if credentials[:oauth_consumer_secret].nil?
|
123
|
-
raise AdsCommon::Errors::AuthError,
|
124
|
-
'Consumer secret not included in credentials.'
|
125
|
-
end
|
126
|
-
|
127
|
-
# TODO: add checks for both methods.
|
128
|
-
end
|
129
|
-
|
130
|
-
# Auxiliary method to generate an authentication token for logging via
|
131
|
-
# the OAuth API.
|
132
|
-
#
|
133
|
-
# Args:
|
134
|
-
# - credentials: a hash with the credentials for the account being
|
135
|
-
# accessed
|
136
|
-
#
|
137
|
-
# Returns:
|
138
|
-
# - The auth token for the account (as an AccessToken)
|
139
|
-
#
|
140
|
-
# Raises:
|
141
|
-
# - AdsCommon::Errors::AuthError if authentication fails
|
142
|
-
# - AdsCommon::Errors::OAuthVerificationRequired if OAuth verification
|
143
|
-
# code required
|
144
|
-
#
|
145
|
-
def create_token(credentials)
|
146
|
-
validate_credentials(credentials)
|
147
|
-
@consumer ||= create_consumer(credentials)
|
148
|
-
return create_token_from_credentials(credentials, @consumer) ||
|
149
|
-
generate_access_token(credentials, @consumer)
|
150
|
-
end
|
151
|
-
|
152
|
-
def create_consumer(credentials)
|
153
|
-
oauth_config = OAUTH_CONFIG.merge({:scope => @scope})
|
154
|
-
proxy = @config.read('connection.proxy')
|
155
|
-
oauth_config[:proxy] = proxy unless proxy.nil?
|
156
|
-
return OAuth::Consumer.new(
|
157
|
-
credentials[:oauth_consumer_key],
|
158
|
-
credentials[:oauth_consumer_secret],
|
159
|
-
oauth_config)
|
160
|
-
end
|
161
|
-
|
162
|
-
# Creates access token based on data from credentials.
|
163
|
-
#
|
164
|
-
# Args:
|
165
|
-
# - credentials: a hash with the credentials for the account being
|
166
|
-
# accessed
|
167
|
-
# - consumer: OAuth consumer for the current configuration
|
168
|
-
#
|
169
|
-
# Returns:
|
170
|
-
# - The auth token for the account (as an AccessToken)
|
171
|
-
#
|
172
|
-
def create_token_from_credentials(credentials, consumer)
|
173
|
-
token = credentials[:oauth_token]
|
174
|
-
if token.nil? or token.empty?
|
175
|
-
return nil
|
176
|
-
end
|
177
|
-
|
178
|
-
method = credentials[:oauth_method] || DEFAULT_METHOD
|
179
|
-
access_token = case method
|
180
|
-
when 'RSA-SHA1'
|
181
|
-
OAuth::AccessToken.from_hash(consumer, {:oauth_token => token})
|
182
|
-
when 'HMAC-SHA1'
|
183
|
-
token_secret = credentials[:oauth_token_secret]
|
184
|
-
if token_secret.nil? or token_secret.empty?
|
185
|
-
get_logger().warn(("The 'token' specified for method %s but " +
|
186
|
-
"'token secret' is not available, ignoring token") % method)
|
187
|
-
nil
|
188
|
-
else
|
189
|
-
OAuth::AccessToken.from_hash(consumer, {
|
190
|
-
:oauth_token => token, :oauth_token_secret => token_secret})
|
191
|
-
end
|
192
|
-
end
|
193
|
-
return access_token
|
194
|
-
end
|
195
|
-
|
196
|
-
# Generates new request tokens and authorizes it to get access token.
|
197
|
-
#
|
198
|
-
# Args:
|
199
|
-
# - credentials: a hash with the credentials for the account being
|
200
|
-
# accessed
|
201
|
-
# - consumer: OAuth consumer for the current configuration
|
202
|
-
#
|
203
|
-
# Returns:
|
204
|
-
# - The auth token for the account (as an AccessToken)
|
205
|
-
#
|
206
|
-
def generate_access_token(credentials, consumer)
|
207
|
-
token = nil
|
208
|
-
callback = credentials[:oauth_callback] || DEFAULT_CALLBACK
|
209
|
-
begin
|
210
|
-
if @request_token.nil?
|
211
|
-
@request_token = credentials[:oauth_request_token] ||
|
212
|
-
consumer.get_request_token(
|
213
|
-
{:oauth_callback => callback},
|
214
|
-
{:scope => @scope}
|
215
|
-
)
|
216
|
-
end
|
217
|
-
verification_code = credentials[:oauth_verification_code]
|
218
|
-
if verification_code.nil? || verification_code.empty?
|
219
|
-
raise_oauth_verification_error(@request_token, callback)
|
220
|
-
else
|
221
|
-
token = @request_token.get_access_token(
|
222
|
-
{:oauth_verifier => verification_code})
|
223
|
-
@request_token = nil
|
224
|
-
end
|
225
|
-
rescue OAuth::Unauthorized => e
|
226
|
-
if @request_token
|
227
|
-
raise_oauth_verification_error(@request_token, callback)
|
228
|
-
else
|
229
|
-
raise AdsCommon::Errors::AuthError,
|
230
|
-
"Authorization error occured: %s" % e
|
231
|
-
end
|
232
|
-
end
|
233
|
-
return token
|
234
|
-
end
|
235
|
-
|
236
|
-
# Raises a OAuthVerificationRequired error with auth URL for given
|
237
|
-
# request token.
|
238
|
-
#
|
239
|
-
# Args:
|
240
|
-
# - request_token: an initialized OAuth request token
|
241
|
-
# - callback: OAuth callback URL
|
242
|
-
#
|
243
|
-
# Returns:
|
244
|
-
# - never returns
|
245
|
-
#
|
246
|
-
# Raises:
|
247
|
-
# - AdsCommon::Errors::OAuthVerificationRequired in all cases
|
248
|
-
#
|
249
|
-
def raise_oauth_verification_error(request_token, callback)
|
250
|
-
oauth_url = request_token.authorize_url({:oauth_callback => callback})
|
251
|
-
error = AdsCommon::Errors::OAuthVerificationRequired.new(
|
252
|
-
oauth_url, request_token)
|
253
|
-
raise error
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|
257
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# Encoding: utf-8
|
2
|
-
#
|
3
|
-
# Authors:: api.dklimkin@gmail.com (Danial Klimkin)
|
4
|
-
#
|
5
|
-
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
|
6
|
-
#
|
7
|
-
# License:: Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
-
# you may not use this file except in compliance with the License.
|
9
|
-
# You may obtain a copy of the License at
|
10
|
-
#
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
-
#
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
16
|
-
# implied.
|
17
|
-
# See the License for the specific language governing permissions and
|
18
|
-
# limitations under the License.
|
19
|
-
#
|
20
|
-
# OAuth request proxy for HTTPI::Request.
|
21
|
-
|
22
|
-
require 'oauth/request_proxy/base'
|
23
|
-
require 'httpi/request'
|
24
|
-
|
25
|
-
module OAuth
|
26
|
-
module RequestProxy
|
27
|
-
class HTTPIRequest < OAuth::RequestProxy::Base
|
28
|
-
proxies HTTPI::Request
|
29
|
-
|
30
|
-
# HTTP method to use.
|
31
|
-
def method
|
32
|
-
return 'POST'
|
33
|
-
end
|
34
|
-
|
35
|
-
# Request URL.
|
36
|
-
def uri
|
37
|
-
request.url.to_s
|
38
|
-
end
|
39
|
-
|
40
|
-
# Query parameters.
|
41
|
-
def parameters
|
42
|
-
options[:parameters]
|
43
|
-
end
|
44
|
-
|
45
|
-
# Request body.
|
46
|
-
def body
|
47
|
-
request.body
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|