google-ads-common 0.2.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.
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Authors:: api.sgomes@gmail.com (Sérgio Gomes)
4
+ #
5
+ # Copyright:: Copyright 2010, 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
+ # Base class for authentication handlers, providing the basic mechanism for
21
+ # different authentication methods to be able to handle the details sent to
22
+ # the server.
23
+
24
+ module AdsCommon
25
+ module Auth
26
+ class BaseHandler
27
+ # Callback to be used by CredentialHandlers to notify the auth handler of
28
+ # a change in one of the credentials. Useful for e.g. invalidating a
29
+ # token. The generic method does nothing.
30
+ def property_changed(credential, value)
31
+ end
32
+
33
+ # This method handles an error according to the specifics of an
34
+ # authentication mechanism (to regenerate tokens, for example). The
35
+ # generic method simply re-raises the error.
36
+ def handle_error(error)
37
+ raise error
38
+ end
39
+
40
+ # This method returns the set of fields to be included in the header.
41
+ # The generic method simply returns everything passed to it.
42
+ def header_list(credentials)
43
+ return credentials.keys
44
+ end
45
+
46
+ # This method returns the key value pairs to be included in the header.
47
+ # The generic method simply returns everything passed to it.
48
+ def headers(credentials)
49
+ return credentials
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Authors:: api.sgomes@gmail.com (Sérgio Gomes)
4
+ # api.dklimkin@gmail.com (Danial Klimkin)
5
+ #
6
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
7
+ #
8
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
17
+ # implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+ # This module manages ClientLogin authentication. It either uses a user-provided
22
+ # auth token, or automatically connects to Google's ClientLogin service and
23
+ # generates an auth token that can be used to login to an API.
24
+
25
+ require 'cgi'
26
+ require 'ads_common/http'
27
+ require 'ads_common/auth/base_handler'
28
+ require 'ads_common/errors'
29
+
30
+ module AdsCommon
31
+ module Auth
32
+
33
+ # Credentials class to handle ClientLogin authentication.
34
+ class ClientLoginHandler < AdsCommon::Auth::BaseHandler
35
+
36
+ ACCOUNT_TYPE = 'GOOGLE'
37
+ AUTH_PATH = '/accounts/ClientLogin'
38
+ IGNORED_FIELDS = [:email, :password, :auth_token]
39
+
40
+ # Initializes the ClientLoginHandler with all the necessary details.
41
+ def initialize(config, server, service_name)
42
+ @token = config.read('authentication.auth_token')
43
+ @config = config
44
+ @server = server
45
+ @service_name = service_name
46
+ end
47
+
48
+ # Invalidates the stored token if the email, password or provided auth
49
+ # token have changed.
50
+ def property_changed(prop, value)
51
+ case prop
52
+ when :auth_token
53
+ @token = config.read('authentication.auth_token')
54
+ when :email, :password
55
+ @token = nil
56
+ end
57
+ end
58
+
59
+ # Handle specific ClientLogin errors.
60
+ def handle_error(error)
61
+ # TODO: Add support for automatically regenerating auth tokens when they
62
+ # expire.
63
+ raise error
64
+ end
65
+
66
+ # Returns all of the fields that this auth handler will fill.
67
+ def header_list(credentials)
68
+ result = []
69
+ result << :authToken
70
+ credentials.each do |p, v|
71
+ result << p unless IGNORED_FIELDS.include?(p)
72
+ end
73
+ return result
74
+ end
75
+
76
+ # Returns all of the credentials received from the CredentialHandler,
77
+ # except for email and password.
78
+ def headers(credentials)
79
+ @token = generate_token(credentials) if @token == nil
80
+ result = {}
81
+ result[:authToken] = @token
82
+ credentials.each do |p, v|
83
+ result[p] = v unless IGNORED_FIELDS.include?(p)
84
+ end
85
+ return result
86
+ end
87
+
88
+ private
89
+
90
+ # Auxiliary method to validate the credentials for token generation.
91
+ #
92
+ # Args:
93
+ # - credentials: a hash with the credentials for the account being
94
+ # accessed
95
+ #
96
+ #
97
+ # Raises:
98
+ # AdsCommon::Errors::AuthError if validation fails.
99
+ #
100
+ def validate_credentials(credentials)
101
+ if credentials.nil?
102
+ raise AdsCommon::Errors::AuthError,
103
+ 'No credentials supplied.'
104
+ end
105
+
106
+ if credentials[:email].nil?
107
+ raise AdsCommon::Errors::AuthError,
108
+ 'Email address not included in credentials.'
109
+ end
110
+
111
+ if credentials[:password].nil?
112
+ raise AdsCommon::Errors::AuthError,
113
+ 'Password not included in credentials.'
114
+ end
115
+ end
116
+
117
+ # Auxiliary method to generate an authentication token for logging via
118
+ # the ClientLogin API.
119
+ #
120
+ # Args:
121
+ # - credentials: a hash with the credentials for the account being
122
+ # accessed
123
+ #
124
+ # Returns:
125
+ # The auth token for the account (as a string).
126
+ #
127
+ # Raises:
128
+ # AdsCommon::Errors::AuthError if authentication fails.
129
+ #
130
+ def generate_token(credentials)
131
+ validate_credentials(credentials)
132
+
133
+ email = CGI.escape(credentials[:email])
134
+ password = CGI.escape(credentials[:password])
135
+
136
+ url = @server + AUTH_PATH
137
+
138
+ data = "accountType=%s&Email=%s&Passwd=%s&service=%s" %
139
+ [ACCOUNT_TYPE, email, password, @service_name]
140
+ headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
141
+
142
+ response = AdsCommon::Http.post_response(url, data, @config, headers)
143
+ results = parse_token_text(response.body)
144
+
145
+ if response.code == 200 and results.include?(:Auth)
146
+ return results[:Auth]
147
+ else
148
+ error_message = "Login failed for email %s: HTTP code %d." %
149
+ [credentials[:email], response.code]
150
+ if results.include?(:Error)
151
+ error_message += " Error: %s." % results[:Error]
152
+ else
153
+ error_message += " Raw error: %s." % response.body
154
+ end
155
+ if results.include?(:Info)
156
+ error_message += " Info: %s." % results[:Info]
157
+ end
158
+ raise AdsCommon::Errors::AuthError, error_message
159
+ end
160
+ end
161
+
162
+ # Extracts key-value pairs from ClientLogin server response.
163
+ #
164
+ # Args:
165
+ # - text: server response string.
166
+ #
167
+ # Returns:
168
+ # Hash of key-value pairs.
169
+ #
170
+ def parse_token_text(text)
171
+ result = {}
172
+ text.split("\n").each do |line|
173
+ key, value = line.split("=")
174
+ result[key.to_sym] = value
175
+ end
176
+ return result
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Authors:: api.sgomes@gmail.com (Sérgio Gomes)
4
+ # api.jeffy@gmail.com (Jeffrey Posnick)
5
+ # chanezon@google.com (Patrick Chanezon)
6
+ # leavengood@gmail.com (Ryan Leavengood)
7
+ #
8
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
9
+ #
10
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
11
+ # you may not use this file except in compliance with the License.
12
+ # You may obtain a copy of the License at
13
+ #
14
+ # http://www.apache.org/licenses/LICENSE-2.0
15
+ #
16
+ # Unless required by applicable law or agreed to in writing, software
17
+ # distributed under the License is distributed on an "AS IS" BASIS,
18
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
19
+ # implied.
20
+ # See the License for the specific language governing permissions and
21
+ # limitations under the License.
22
+ #
23
+ # Common Rake tasks for Ads client libraries.
24
+
25
+ require 'fileutils'
26
+ require 'logger'
27
+ require 'rubygems'
28
+ gem 'rake'
29
+ require 'rake/gempackagetask'
30
+ require 'rake/rdoctask'
31
+ require 'rake/clean'
32
+ require 'rake/testtask'
33
+ gem 'soap4r', '=1.5.8'
34
+ require 'wsdl/soap/wsdl2ruby'
35
+ require 'xsd/codegen/classdef'
36
+ require 'ads_common/soap4r_patches'
37
+ require 'ads_common/http'
38
+
39
+ $API_CONFIG = $MODULE::ApiConfig
40
+
41
+ $WSDLDIR = 'wsdl'
42
+ $LIBDIR = 'lib'
43
+ $DOCDIR = 'doc'
44
+ $TESTDIR = 'test'
45
+ $GENDIR = File.join($LIBDIR, $PROJECT_NAME)
46
+ $PKG_VERSION = ENV['REL'] ? ENV['REL'] : $CURRENT_VERSION
47
+
48
+ SRC_RB = FileList["#{$LIBDIR}/**/*.rb"]
49
+
50
+ logger = Logger.new(STDERR)
51
+
52
+ CLEAN.include($WSDLDIR)
53
+ CLEAN.include($DOCDIR)
54
+ $API_CONFIG.versions.each do |version|
55
+ CLEAN.include(File.join($GENDIR, version.to_s))
56
+ end
57
+
58
+ CLOBBER.include('pkg')
59
+
60
+ # ====================================================================
61
+ # Create a default task to prepare library for usage.
62
+ desc "gets the wsdl and generates the classes"
63
+ task :default => [:getwsdl, :generate]
64
+
65
+ # ====================================================================
66
+ # Create a task to retrieve the WSDL files for the services.
67
+ desc "gets the wsdl files for API services"
68
+ task :getwsdl do
69
+ $API_CONFIG.versions.each do |version|
70
+ urls = $API_CONFIG.get_wsdls(version)
71
+ mkdir_p File.join($WSDLDIR, version.to_s)
72
+ urls.each do |service, url|
73
+ puts "getting #{url}"
74
+ save(AdsCommon::Http.get(url, Generator.config),
75
+ get_wsdl_file_name(version.to_s, service.to_s))
76
+ end
77
+ end
78
+ end
79
+
80
+ # Return the full file name of the WSDL file for a given version and service
81
+ def get_wsdl_file_name(version, service)
82
+ File.join($WSDLDIR, version.to_s, service.to_s) + '.wsdl'
83
+ end
84
+
85
+ # Apply fixes to the WSDL content in order to make it understandable for the
86
+ # soap4r code generator. The fixes are applied to the original object.
87
+ def fix_wsdl!(wsdl)
88
+ ['type', 'base'].each do |name|
89
+ ['long', 'string', 'date', 'int', 'boolean'].each do |type|
90
+ # Fix this attribute over the entire document
91
+ wsdl.gsub!(Regexp.new("#{name}=\"#{type}\""), "#{name}=\"xsd:#{type}\"")
92
+ end
93
+ end
94
+ schema_ns = "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
95
+ if wsdl !~ Regexp.new(schema_ns)
96
+ wsdl.gsub!(/(<wsdl:definitions[^>]*)>/, '\1 ' + schema_ns + '>')
97
+ end
98
+ return wsdl
99
+ end
100
+
101
+ # Saves this document to the specified path.
102
+ # Doesn't create the file if a 404 error page is returned.
103
+ def save(content, path)
104
+ if content !~ /<H2>Error 404<\/H2>/
105
+ File::open(path, 'w') {|f| f.write(fix_wsdl!(content))}
106
+ end
107
+ end
108
+
109
+ # ====================================================================
110
+ # Set the default generation task to use soap4r (can be overriden for client
111
+ # libraries).
112
+ desc "generates API classes from the wsdl files"
113
+ task :generate => [:soap4r_generate]
114
+
115
+ # ====================================================================
116
+ # Create a task to generate all the service classes with soap4r.
117
+ desc "generates API classes from the wsdl files using soap4r"
118
+ task :soap4r_generate do
119
+ $API_CONFIG.versions.each do |version|
120
+ gendir = File.join($LIBDIR, $PRODDIR, version.to_s)
121
+ mkdir_p gendir
122
+ $API_CONFIG.services(version).each do |service|
123
+ # Generate SOAP classes with soap4r
124
+ service_name = service.to_s
125
+ worker = WSDL::SOAP::WSDL2Ruby.new
126
+ worker.logger = logger
127
+ worker.location = get_wsdl_file_name(version, service)
128
+ worker.basedir = gendir
129
+ worker.opt.update(get_wsdl_opt(version, service_name))
130
+ worker.run
131
+
132
+ # Fix the "require" statements so that they work in the client library's
133
+ # directory structure
134
+ fix_import(version, File.join(gendir, "#{service_name}Driver.rb"))
135
+ fix_import(version,
136
+ File.join(gendir, "#{service_name}MappingRegistry.rb"))
137
+ fix_import(version, File.join(gendir, "#{service_name}.rb"))
138
+
139
+ # Fix the comments in the file so that we get better-looking RDoc, and
140
+ # only for the things we want
141
+ fix_rdoc(File.join(gendir, "#{service_name}.rb"))
142
+
143
+ # Generate the wrapper files
144
+ eval("require '#{File.join(gendir, "#{service_name}Driver.rb")}'")
145
+ wrapper_file = File.join(gendir, "#{service_name}Wrapper.rb")
146
+ File.open(wrapper_file, 'w') do |file|
147
+ file.write(Generator.generate_wrapper_class(version, service))
148
+ end
149
+ puts "Generated #{version} #{service_name} wrapper: #{wrapper_file}"
150
+ end
151
+ end
152
+ end
153
+
154
+ # Fix "require" statements for client lib usage
155
+ def fix_import(version, file)
156
+ tempfile = file + '.tmp'
157
+ outfile = File.new(tempfile, 'w')
158
+ File.open(file, 'r') do |infile|
159
+ infile.each do |line|
160
+ if (line =~ /require.*Service.*\.rb/)
161
+ outfile << line.gsub(/require '(.*)Service(.*)\.rb'/,
162
+ "require '#{$PRODDIR}/#{version.to_s}/\\1Service\\2'")
163
+ else
164
+ outfile << line
165
+ end
166
+ end
167
+ end
168
+ outfile.close
169
+ File.rename(tempfile, file)
170
+ end
171
+
172
+ # Fix RDoc comments in the generated *Service.rb files
173
+ def fix_rdoc(file)
174
+ tempfile = file + '.tmp'
175
+ outfile = File.new(tempfile, 'w')
176
+ should_doc = true
177
+ File.open(file, 'r') do |infile|
178
+ infile.each do |line|
179
+ if (line =~ /# \{.*\}[A-Z]\w*/)
180
+ # This is a regular class. Document and clean up how it's displayed.
181
+ should_doc = true
182
+ outfile << line.gsub(/\{.*\}(.*)/, "\\1")
183
+ elsif (line =~ /# \{.*\}[a-z]\w*/)
184
+ # This is a method wrapping class. Do not document, but still clean up
185
+ # its comment.
186
+ should_doc = false
187
+ outfile << line.gsub(/\{.*\}(.*)/, "\\1")
188
+ elsif (line =~ /# \w+/)
189
+ # Itemize member variables
190
+ outfile << line.gsub(/# (.*)/, "# - \\1")
191
+ elsif (line =~ /class [A-Z].*/)
192
+ if should_doc
193
+ outfile << line
194
+ else
195
+ # Avoid documenting the method classes, since they're made invisible
196
+ # thanks to our service wrappers
197
+ outfile << line.gsub(/(.*)(\w)/, "\\1\\2 #:nodoc: all")
198
+ end
199
+ else
200
+ outfile << line
201
+ end
202
+ end
203
+ end
204
+ outfile.close
205
+ File.rename(tempfile, file)
206
+ end
207
+
208
+ # Create options to be used for class generation from WSDL
209
+ def get_wsdl_opt(version, service_name)
210
+ optcmd= {}
211
+ optcmd['classdef'] = service_name
212
+ optcmd['force'] = true
213
+ optcmd['mapping_registry'] = true
214
+ optcmd['driver'] = nil
215
+
216
+ # Causes soap4r to wrap the classes it outputs into the given modules
217
+ optcmd['module_path'] = [$MODULE.name, version.to_s.capitalize, service_name]
218
+ return optcmd
219
+ end
220
+
221
+ # ====================================================================
222
+ # Create a task to build the RDOC documentation tree.
223
+ Rake::RDocTask.new("rdoc") do |rdoc|
224
+ # Try to use SDoc to generate the docs
225
+ begin
226
+ require 'sdoc'
227
+ rdoc.options << '--fmt' << 'shtml'
228
+ rdoc.template = 'direct'
229
+ rescue LoadError
230
+ # Do nothing, give up on SDoc and continue with whatever is the default.
231
+ end
232
+ rdoc.rdoc_dir = $DOCDIR
233
+ rdoc.title = "#{$PROJECT_NAME} -- Client library for the #{$API_NAME}"
234
+ rdoc.main = 'README'
235
+ rdoc.rdoc_files.include('README', 'COPYING', 'ChangeLog')
236
+ rdoc.rdoc_files.include("#{$LIBDIR}/*.rb", "#{$LIBDIR}/#{$PRODDIR}/*.rb")
237
+ rdoc.rdoc_files.include("#{$LIBDIR}/#{$PRODDIR}/v*/*Wrapper.rb")
238
+ rdoc.rdoc_files.include("#{$LIBDIR}/#{$PRODDIR}/v*/*Service.rb")
239
+ rdoc.rdoc_files.exclude("#{$LIBDIR}/#{$PRODDIR}/soap4rpatches.rb")
240
+ end
241
+
242
+ # ====================================================================
243
+ # Create a task to perform the unit testing.
244
+ Rake::TestTask.new("test") do |test|
245
+ test.libs << $TESTDIR
246
+ test.pattern = "#{$TESTDIR}/**/test_*.rb"
247
+ test.verbose = true
248
+ end
249
+
250
+ # ====================================================================
251
+ # Create a task that will package the Rake software into distributable
252
+ # gem files.
253
+
254
+ # Utility method to create readable version ranges. May not cover a few edge
255
+ # cases, but this is a best effort approach.
256
+ def readable_version_range(version_string)
257
+ return '' if version_string.nil?
258
+
259
+ version_string = version_string.to_s.strip
260
+ pattern = /^(>|<|>=|<=|~>)(\s*)(\d[\d\.]*)$/
261
+ result = pattern.match(version_string)
262
+
263
+ return version_string unless result
264
+
265
+ case result[1]
266
+ when '>'
267
+ return 'greater than version ' + result[3]
268
+ when '<'
269
+ return 'lower than version ' + result[3]
270
+ when '>='
271
+ return 'version ' + result[3] + ' or later'
272
+ when '<='
273
+ return 'version ' + result[3] + ' or earlier'
274
+ when '~>'
275
+ version_pattern = /^(\d[\d\.]*)(\d)$/
276
+ version_result = version_pattern.match(result[3])
277
+ if version_result
278
+ return 'version ' + version_result[1] + 'x'
279
+ else
280
+ return version_string
281
+ end
282
+ else
283
+ return version_string
284
+ end
285
+ end
286
+
287
+ PKG_FILES = FileList[
288
+ '*.*',
289
+ 'Rakefile',
290
+ "#{$LIBDIR}/**/*.rb",
291
+ "#{$LIBDIR}/#{$PRODDIR}/data/*.*",
292
+ 'examples/**/*.rb',
293
+ "#{$DOCDIR}/**/*.*",
294
+ "#{$TESTDIR}/**/*.*"
295
+ ]
296
+
297
+ PKG_FILES.exclude(/\._/)
298
+
299
+ if ! defined?(Gem)
300
+ puts "Package Target requires RubyGems"
301
+ else
302
+ spec = Gem::Specification.new do |s|
303
+
304
+ # Basic information
305
+ s.name = $GEM_NAME
306
+ s.version = $PKG_VERSION
307
+ s.summary = $GEM_SUMMARY
308
+ s.description = $GEM_DESCRIPTION
309
+
310
+ # Files and dependencies
311
+ dependencies = [['soap4r', '= 1.5.8'],
312
+ ['httpclient', '>= 2.1.2'],
313
+ ['google-ads-common', $ADS_COMMON_VERSION]]
314
+ dependencies += $EXTRA_DEPENDENCIES if $EXTRA_DEPENDENCIES
315
+ s.files = PKG_FILES.to_a
316
+ s.require_path = $LIBDIR
317
+ dependencies.each do |dep|
318
+ s.add_dependency(dep[0], dep[1])
319
+ end
320
+
321
+ # RDoc information
322
+ s.has_rdoc = true
323
+ s.extra_rdoc_files = ['README']
324
+ s.rdoc_options << '--main' << 'README'
325
+
326
+ # Metadata
327
+ s.authors = $GEM_AUTHORS
328
+ s.email = $GEM_EMAIL
329
+ s.homepage = $GEM_HOMEPAGE
330
+ s.rubyforge_project = $PROJECT_NAME
331
+ dependencies.each do |dep|
332
+ s.requirements << dep[0] + ' ' + readable_version_range(dep[1])
333
+ end
334
+ end
335
+
336
+ Rake::GemPackageTask.new(spec) do |t|
337
+ t.need_tar = true
338
+ end
339
+ end