google-ads-common 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|