google-ads-common 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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