Professional Documents
Culture Documents
Util
Util
#
# 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
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
import gzip
import io
import logging
import re
import sys
import threading
import urllib2
import suds
_COMMON_FILTER = None
_SUDS_CLIENT_FILTER = None
_SUDS_MX_CORE_FILTER = None
_SUDS_MX_LITERAL_FILTER = None
_SUDS_TRANSPORT_FILTER = None
LOGGER_FORMAT = '[%(asctime)s - %(name)s - %(levelname)s] %(message)s'
def GetGoogleAdsCommonFilter():
global _COMMON_FILTER
if not _COMMON_FILTER:
_COMMON_FILTER = _GoogleAdsCommonFilter()
return _COMMON_FILTER
def GetSudsClientFilter():
global _SUDS_CLIENT_FILTER
if not _SUDS_CLIENT_FILTER:
_SUDS_CLIENT_FILTER = _SudsClientFilter()
return _SUDS_CLIENT_FILTER
def GetSudsMXCoreFilter():
global _SUDS_MX_CORE_FILTER
if not _SUDS_MX_CORE_FILTER:
_SUDS_MX_CORE_FILTER = _SudsMXCoreFilter()
return _SUDS_MX_CORE_FILTER
def GetSudsMXLiteralFilter():
global _SUDS_MX_LITERAL_FILTER
if not _SUDS_MX_LITERAL_FILTER:
_SUDS_MX_LITERAL_FILTER = _SudsMXLiteralFilter()
return _SUDS_MX_LITERAL_FILTER
def GetSudsTransportFilter():
global _SUDS_TRANSPORT_FILTER
if not _SUDS_TRANSPORT_FILTER:
_SUDS_TRANSPORT_FILTER = _SudsTransportFilter()
return _SUDS_TRANSPORT_FILTER
class PatchHelper(object):
"""Utility that applies patches on behalf of the Google Ads Client Library."""
def Apply(self):
"""Apply patches used by the Google Ads Client Library."""
self._ApplySudsJurkoAppenderPatch()
self._ApplySudsJurkoSendPatch()
def _ApplySudsJurkoAppenderPatch(self):
"""Appends a Monkey Patch to the suds.mx.appender module.
This resolves an issue where empty objects are ignored and stripped from the
request output. More details can be found on the suds-jurko issue tracker:
https://goo.gl/uyYw0C
"""
def PatchedAppend(self, parent, content):
obj = content.value
child = self.node(content)
parent.append(child)
for item in obj:
cont = suds.mx.Content(tag=item[0], value=item[1])
suds.mx.appender.Appender.append(self, child, cont)
suds.mx.appender.ObjectAppender.append = PatchedAppend
def _ApplySudsJurkoSendPatch(self):
"""Appends a Monkey Patch to the suds.transport.http module.
This allows the suds library to decompress the SOAP body when compression is
enabled. For more details on SOAP Compression, see:
https://developers.google.com/adwords/api/docs/guides/bestpractices?
hl=en#use_compression
"""
def GetInflateStream(msg):
stream = io.BytesIO()
stream.write(msg)
stream.flush()
stream.seek(0)
return gzip.GzipFile(fileobj=stream, mode='rb')
self.getcookies(fp, u2request)
headers = (fp.headers.dict if sys.version_info < (3, 0) else fp.headers)
result = suds.transport.Reply(200, headers, fp.read())
if result.headers.get('content-encoding') == 'gzip':
# If gzip encoding is used, decompress here.
result.message = GetInflateStream(result.message).read()
suds.transport.http.log.debug('received:\n%s', result)
return result
suds.transport.http.HttpTransport.send = PatchedHttpTransportSend
class _AbstractDevTokenSOAPFilter(logging.Filter):
"""Interface for sanitizing logs containing SOAP request/response data."""
_AUTHORIZATION_HEADER = 'Authorization'
_DEVELOPER_TOKEN_SUB = re.compile(
r'(?<=\<tns:developerToken\>).*?(?=\</tns:developerToken\>)')
_REDACTED = 'REDACTED'
class _GoogleAdsCommonFilter(_AbstractDevTokenSOAPFilter):
"""Removes sensitive data from logs generated by googleads.common."""
class _SudsClientFilter(_AbstractDevTokenSOAPFilter):
"""Removes sensitive data from logs generated by suds.client."""
_SUDS_CLIENT_SOAP_MSG = 'sending to (%s)\nmessage:\n%s'
_SUDS_CLIENT_HEADERS_MSG = 'headers = %s'
def filter(self, record):
args = record.args
if record.msg == self._SUDS_CLIENT_SOAP_MSG:
# If the original suds.sax.document.Document is modified, that will also
# modify the request itself. Instead, replace it with its sanitized text.
record.args = (args[0], self._DEVELOPER_TOKEN_SUB.sub(
self._REDACTED, args[1].str()))
elif record.msg == self._SUDS_CLIENT_HEADERS_MSG:
sanitized_headers = record.args.copy()
if self._AUTHORIZATION_HEADER in sanitized_headers:
sanitized_headers[self._AUTHORIZATION_HEADER] = self._REDACTED
record.args = sanitized_headers
return True
class _SudsMXCoreFilter(_AbstractDevTokenSOAPFilter):
"""Removes sensitive data from logs generated by suds.mx.core."""
_DEVELOPER_TOKEN = 'developerToken'
_REQUEST_HEADER = 'RequestHeader'
if isinstance(arg, suds.mx.Content):
if arg.tag == self._REQUEST_HEADER:
# Rather than modifying the argument directly, sets args to a modified
# copy so that we don't overwrite the headers to be sent in the actual
# request.
d = dict(arg.value)
if self._DEVELOPER_TOKEN in d:
d[self._DEVELOPER_TOKEN] = self._REDACTED
record.args = list(record.args)
record.args[i] = suds.mx.Content(tag=self._REQUEST_HEADER, value=d)
break
if arg.tag == self._DEVELOPER_TOKEN:
# Rather than modifying the argument directly, sets args to a modified
# copy so that we don't overwrite the headers to be sent in the actual
# request.
record.args = list(record.args)
record.args[i] = suds.mx.Content(
tag=self._DEVELOPER_TOKEN, value=self._REDACTED)
break
elif isinstance(arg, suds.sax.element.Element):
if arg.name == self._REQUEST_HEADER:
# Only produce a modified copy of the RequestHeader element if it
# contains a developer token.
if arg.getChild(self._DEVELOPER_TOKEN) is not None:
request_header = suds.sax.element.Element(
arg.name, parent=arg.parent)
if child.name == self._DEVELOPER_TOKEN:
copied_child.text = suds.sax.text.Text(self._REDACTED)
else:
copied_child.text = child.text
request_header.append(copied_child)
return True
class _SudsMXLiteralFilter(_AbstractDevTokenSOAPFilter):
"""Removes sensitive data from logs generated by suds.mx.literal."""
_DEVELOPER_TOKEN = 'developerToken'
_REQUEST_HEADER = 'RequestHeader'
if isinstance(arg, suds.mx.Content):
if arg.tag == self._REQUEST_HEADER:
# Rather than modifying the argument directly, sets args to a modified
# copy so that we don't overwrite the headers to be sent in the actual
# request.
d = dict(arg.value)
if self._DEVELOPER_TOKEN in d:
d[self._DEVELOPER_TOKEN] = self._REDACTED
record.args = list(record.args)
record.args[i] = suds.mx.Content(tag=self._REQUEST_HEADER, value=d)
break
if arg.tag == self._DEVELOPER_TOKEN:
# Rather than modifying the argument directly, sets args to a modified
# copy so that we don't overwrite the headers to be sent in the actual
# request.
record.args = list(record.args)
record.args[i] = suds.mx.Content(tag=self._DEVELOPER_TOKEN,
value=self._REDACTED)
break
return True
class _SudsTransportFilter(_AbstractDevTokenSOAPFilter):
"""Removes sensitive data from logs generated by suds.transport."""
return True
class UtilityRegistry(object):
"""Utility that registers product utilities used in generating a request."""
def __init__(self):
self._enabled = True
self._registry = set()
self._lock = threading.Lock()
def __iter__(self):
with self._lock:
return iter(self._registry.copy())
def __len__(self):
with self._lock:
return len(self._registry)
def Clear(self):
with self._lock:
self._registry.clear()