lib/adwords4r/credentials.rb in adwords4r-16.0.0 vs lib/adwords4r/credentials.rb in adwords4r-17.0.0

- old
+ new

@@ -1,85 +1,131 @@ #!/usr/bin/ruby # -# Copyright 2009, Google Inc. All Rights Reserved. +# Authors:: sgomes@google.com (Sérgio Gomes) +# jeffy@google.com (Jeffrey Posnick) +# chanezon@google.com (Patrick Chanezon) +# leavengood@gmail.com (Ryan Leavengood) # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Copyright:: Copyright 2009, Google Inc. All Rights Reserved. # -# http://www.apache.org/licenses/LICENSE-2.0 +# License:: Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# http://www.apache.org/licenses/LICENSE-2.0 # -# Handles credentials for the SOAP requests, for both <= v13 and >= v200902. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Handles credentials for the SOAP requests, for both <= v13 and >= v2009. require 'soap/header/simplehandler' require 'adwords4r/authtoken' module AdWords - # Handles the headers used in v200906 requests. - class V200906HeaderHandler < SOAP::Header::SimpleHandler + HEADER_NAMESPACE = 'https://adwords.google.com/api/adwords/cm/' - # Constructor for V200906HeaderHandler. + # Handles the headers used in v2009 requests. + class V2009HeaderHandler < SOAP::Header::SimpleHandler + + # Constructor for V2009HeaderHandler. # # Args: # - parent: AdWordsCredentials object containing the login credentials. # - def initialize(parent) - namespace = 'https://adwords.google.com/api/adwords/cm/v200906' + def initialize(parent, namespace, version) super(XSD::QName.new(namespace, 'RequestHeader')) @parent = parent + @version = version end # Handles callback. def on_simple_outbound - return { - :authToken => @parent.auth_token, - :clientEmail => @parent.credentials['clientEmail'], - :userAgent => @parent.credentials['userAgent'], - :applicationToken => @parent.credentials['applicationToken'], - :developerToken => @parent.credentials['developerToken'], - } + ns = HEADER_NAMESPACE + ns += 'v' if @version.is_a? Integer + ns += @version.to_s + header = SOAP::SOAPElement.new(nil) + auth_token = SOAP::SOAPElement.new(XSD::QName.new(ns, 'authToken'), + @parent.auth_token) + user_agent = SOAP::SOAPElement.new(XSD::QName.new(ns, 'userAgent'), + @parent.credentials['userAgent']) + app_token = SOAP::SOAPElement.new(XSD::QName.new(ns, 'applicationToken'), + @parent.credentials['applicationToken']) + dev_token = SOAP::SOAPElement.new(XSD::QName.new(ns, 'developerToken'), + @parent.credentials['developerToken']) + header.add(auth_token) + header.add(user_agent) + header.add(app_token) + header.add(dev_token) + + client_email_value = @parent.client_email + client_cid_value = @parent.client_customer_id + if client_email_value and client_email_value.length > 0 + client_email = SOAP::SOAPElement.new(XSD::QName.new(ns, 'clientEmail'), + client_email_value) + header.add(client_email) + end + if client_cid_value and client_cid_value.length > 0 + client_cid = SOAP::SOAPElement.new( + XSD::QName.new(ns, 'clientCustomerId'), client_cid_value) + header.add(client_cid) + end + if @parent.validate_only + validate_only = SOAP::SOAPElement.new( + XSD::QName.new(ns, 'validateOnly'), 'true') + header.add(validate_only) + end + return header end end # Handles the headers used in <= v13 (both sandbox and production) - class Pre200902HeaderHandler < SOAP::Header::SimpleHandler + class Pre2009HeaderHandler < SOAP::Header::SimpleHandler attr_reader :tag - attr_writer :value + attr_writer :credentials - # Constructor for Pre200902HeaderHandler. + # Constructor for Pre2009HeaderHandler. # # Args: # - tag: XML tag name to be handled (email, password, etc.) # - value: value to be sent inside tag on outgoing requests # - def initialize(tag, value) + def initialize(tag, credentials) super(XSD::QName.new(nil, tag)) @tag = tag - @value = value + @credentials = credentials end # Handles callback. def on_simple_outbound - @value + if @tag == 'clientEmail' + return @credentials.client_email + elsif @tag == 'clientCustomerId' + return @credentials.client_customer_id + else + @credentials.credentials[@tag] + end end end # Generic credentials class, used for any API version. class AdWordsCredentials # Hash of credentials (credential key to value) attr_reader :credentials # The environment being used (production, sandbox) attr_reader :environment + # Whether we're making MCC-level requests + attr_accessor :use_mcc + # Whether we're making validate-only requests + attr_accessor :validate_only public # Constructor for AdWordsCredentials. # @@ -98,15 +144,17 @@ def initialize(credentials=nil) @credentials = {} @environment = nil @auth_token = nil @handlers = [] + @use_mcc = false + @validate_only = false credentials = get_defaults() if credentials.nil? credentials.each do |key, value| # 'environment' shouldn't go in the credentials array, and we'll ignore # 'alternateUrl' to avoid errors on upgraders' apps. - if key =~ /^alternateUrl/ + if (key =~ /^alternateUrl/) && (credentials["environment"].nil?) raise AdWords::Error::Error, "'alternateUrl' is no longer supported. Please consult the " + "Readme on how to use 'environment' instead." elsif !(key =~ /^environment/) @credentials[key] = value @@ -128,14 +176,20 @@ if @credentials['clientEmail'].nil? and @credentials['clientId'] and @credentials['clientId'].include?('@') @credentials['clientEmail'] = @credentials['clientId'] end + # Normalize 'token' to 'developerToken' + if @credentials['developerToken'].nil? and @credentials['token'] + @credentials['developerToken'] = @credentials['token'] + @credentials.delete('token') + end + # Set environment if credentials['environment'].nil? - # First environment is default - @environment = Service.get_environments[0] + # Get default environment + @environment = AdWords::Service.get_default_environment elsif !(Service.get_environments.include?(credentials['environment'])) raise AdWords::Error::Error, "Unknown environment: #{credentials['environment']}" else @environment = credentials['environment'] @@ -143,43 +197,76 @@ # Fix potential problems with changing clientEmail, by forcing it to be # created @credentials['clientEmail'] = '' if @credentials['clientEmail'].nil? + # Check for environment mismatches. + validate_headers_for_server + @credentials.each do |key, value| - @handlers << Pre200902HeaderHandler.new(key, value) + @handlers << Pre2009HeaderHandler.new(key, self) end end + # Returns the client email currently being used (dependent on whether + # MCC-level requests are enabled or disabled) + # + # Returns: + # Client email if use_mcc is false, empty string otherwise + # + def client_email + if @use_mcc + return '' + else + return @credentials['clientEmail'] + end + end + + # Returns the client customer ID currently being used (dependent on whether + # MCC-level requests are enabled or disabled) + # + # Returns: + # Client customer ID if use_mcc is false, empty string otherwise + # + def client_customer_id + if @use_mcc + return '' + else + return @credentials['clientCustomerId'] + end + end + # Return a list of handlers to be inserted into the driver's handler list. # # Args: # - version: API version being used. Must be an integer. + # - driver: the driver for the service being handled # # Returns: # The list of handlers (subclasses of SOAP::Header::SimpleHandler) # - def get_handlers(version) - if version <= 13 then + def get_handlers(version, driver) + if version.is_a? Integer and version <= 13 then return @handlers - elsif version == 200906 then - return [V200906HeaderHandler.new(self)] + else + namespace = AdWords::Service.get_namespace_v2009(driver) + return [V2009HeaderHandler.new(self, namespace, version)] end end - # Returns the authentication token used with >= v200902 requests. + # Returns the authentication token used with >= v2009 requests. # Generates it if there's no valid token already generated. # # Returns: # The auth token (as a string). # def auth_token generate_auth_token if @auth_token.nil? return @auth_token end - # Generates a new authentication token used with >= v200902 requests. + # Generates a new authentication token used with >= v2009 requests. # The generated token is stored and can later be accessed with auth_token. # It should only be necessary for user code to invoke this if the first # token expires. # # Returns: @@ -196,11 +283,13 @@ if password.nil? raise AdWords::Error::AuthError, 'Password not included in credentials.' end - @auth_token = AdWords::AuthToken::get_token(email, password) + hostname, port, use_ssl = AdWords::Service.get_auth_server(@environment) + @auth_token = AdWords::AuthToken::get_token(email, password, hostname, + port, use_ssl) return @auth_token end # Change one of the authentication headers. # @@ -215,16 +304,10 @@ @credentials.each_key do |key| if key == header then @credentials[key] = value end end - - @handlers.each do |handler| - if handler.tag == header then - handler.value = value - end - end end # Overloads the 'dup' method for AdWordsCredentials to correctly duplicate # objects of this class. # @@ -250,13 +333,47 @@ # def get_defaults cred = Hash.new IO.foreach(File.join(ENV['HOME'], 'adwords.properties')) do |line| unless line =~ /^#/ - split_line = line.split('=') - cred[split_line[0].strip] = split_line[1].strip + split_line = line.split('=', 2) + if (split_line.length == 2) + cred[split_line[0].strip] = split_line[1].strip + end end end return cred + end + + # Validates that the right credentials are being used for the chosen + # environment. + # + # Raises: + # AdWords::Error::EnvironmentMismatchError if sandbox credentials are being + # used for production or vice-versa. + # + def validate_headers_for_server + email = @credentials['email'] + token = @credentials['developerToken'] + client = self.client_email + + sandbox_token = (token =~ /#{Regexp.escape(email)}\+\+[a-zA-Z]{3}/) + sandbox_client = (client =~ /client_[1-5]\+#{Regexp.escape(email)}/) + + # Only check the token, because 'client_n+x@y.tld' may be a valid client + # email for some customers. + if @environment == 'PRODUCTION' and sandbox_token + raise AdWords::Error::EnvironmentMismatchError, + 'Attempting to connect to production with sandbox credentials.' + # Check if either the token or client email do not follow the correct + # format. Client email may not exist, though. + elsif @environment == 'SANDBOX' and (!sandbox_token or + (client.length > 0 and !sandbox_client)) + raise AdWords::Error::EnvironmentMismatchError, + 'Attempting to connect to the sandbox with malformatted ' + + 'credentials. Please check ' + + 'http://code.google.com/apis/adwords/docs/developer/' + + 'adwords_api_sandbox.html/#requestheaders for details.' + end end end end