mirror of
https://github.com/GAM-team/GAM.git
synced 2026-07-05 05:11:35 +00:00
GData: KILL IT WITH FIRE.
This commit is contained in:
@@ -108,8 +108,5 @@ fixable = [] # Don't auto-fix — we want to review changes manually
|
||||
"src/gam/util/**" = ["E402", "F821"]
|
||||
# gamlib modules
|
||||
"src/gam/gamlib/**" = ["F821"]
|
||||
# gdata/ and atom/ are vendored third-party code — don't lint them
|
||||
"src/gam/gdata/**" = ["F", "E", "UP"]
|
||||
"src/gam/atom/**" = ["F", "E", "UP"]
|
||||
# Test files can be more relaxed
|
||||
"tests/**" = ["F401", "F811"]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,354 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
"""HttpClients in this module use httplib to make HTTP requests.
|
||||
|
||||
This module make HTTP requests based on httplib, but there are environments
|
||||
in which an httplib based approach will not work (if running in Google App
|
||||
Engine for example). In those cases, higher level classes (like AtomService
|
||||
and GDataService) can swap out the HttpClient to transparently use a
|
||||
different mechanism for making HTTP requests.
|
||||
|
||||
HttpClient: Contains a request method which performs an HTTP call to the
|
||||
server.
|
||||
|
||||
ProxiedHttpClient: Contains a request method which connects to a proxy using
|
||||
settings stored in operating system environment variables then
|
||||
performs an HTTP call to the endpoint server.
|
||||
"""
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import base64
|
||||
import http.client
|
||||
import os
|
||||
import socket
|
||||
|
||||
import atom.http_core
|
||||
import atom.http_interface
|
||||
import atom.url
|
||||
|
||||
ssl_imported = False
|
||||
ssl = None
|
||||
try:
|
||||
import ssl
|
||||
|
||||
ssl_imported = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class ProxyError(atom.http_interface.Error):
|
||||
pass
|
||||
|
||||
|
||||
class TestConfigurationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
DEFAULT_CONTENT_TYPE = 'application/atom+xml'
|
||||
|
||||
|
||||
class HttpClient(atom.http_interface.GenericHttpClient):
|
||||
# Added to allow old v1 HttpClient objects to use the new
|
||||
# http_code.HttpClient. Used in unit tests to inject a mock client.
|
||||
v2_http_client = None
|
||||
|
||||
def __init__(self, headers=None):
|
||||
self.debug = False
|
||||
self.headers = headers or {}
|
||||
|
||||
def request(self, operation, url, data=None, headers=None):
|
||||
"""Performs an HTTP call to the server, supports GET, POST, PUT, and
|
||||
DELETE.
|
||||
|
||||
Usage example, perform and HTTP GET on http://www.google.com/:
|
||||
import atom.http
|
||||
client = atom.http.HttpClient()
|
||||
http_response = client.request('GET', 'http://www.google.com/')
|
||||
|
||||
Args:
|
||||
operation: str The HTTP operation to be performed. This is usually one
|
||||
of 'GET', 'POST', 'PUT', or 'DELETE'
|
||||
data: filestream, list of parts, or other object which can be converted
|
||||
to a string. Should be set to None when performing a GET or DELETE.
|
||||
If data is a file-like object which can be read, this method will
|
||||
read a chunk of 100K bytes at a time and send them.
|
||||
If the data is a list of parts to be sent, each part will be
|
||||
evaluated and sent.
|
||||
url: The full URL to which the request should be sent. Can be a string
|
||||
or atom.url.Url.
|
||||
headers: dict of strings. HTTP headers which should be sent
|
||||
in the request.
|
||||
"""
|
||||
all_headers = self.headers.copy()
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
|
||||
# If the list of headers does not include a Content-Length, attempt to
|
||||
# calculate it based on the data object.
|
||||
if data and 'Content-Length' not in all_headers:
|
||||
if isinstance(data, (str,)):
|
||||
all_headers['Content-Length'] = str(len(data))
|
||||
else:
|
||||
raise atom.http_interface.ContentLengthRequired('Unable to calculate '
|
||||
'the length of the data parameter. Specify a value for '
|
||||
'Content-Length')
|
||||
|
||||
# Set the content type to the default value if none was set.
|
||||
if 'Content-Type' not in all_headers:
|
||||
all_headers['Content-Type'] = DEFAULT_CONTENT_TYPE
|
||||
|
||||
if self.v2_http_client is not None:
|
||||
http_request = atom.http_core.HttpRequest(method=operation)
|
||||
atom.http_core.Uri.parse_uri(str(url)).modify_request(http_request)
|
||||
http_request.headers = all_headers
|
||||
if data:
|
||||
http_request._body_parts.append(data)
|
||||
return self.v2_http_client.request(http_request=http_request)
|
||||
|
||||
if not isinstance(url, atom.url.Url):
|
||||
if isinstance(url, str):
|
||||
url = atom.url.parse_url(url)
|
||||
else:
|
||||
raise atom.http_interface.UnparsableUrlObject('Unable to parse url parameter because it was not a string or atom.url.Url')
|
||||
|
||||
connection = self._prepare_connection(url, all_headers)
|
||||
|
||||
if self.debug:
|
||||
connection.debuglevel = 1
|
||||
|
||||
connection.putrequest(operation, self._get_access_url(url), skip_host=True)
|
||||
|
||||
if url.port is not None:
|
||||
connection.putheader('Host', '%s:%s' % (url.host, url.port))
|
||||
else:
|
||||
connection.putheader('Host', url.host)
|
||||
|
||||
# Overcome a bug in Python 2.4 and 2.5
|
||||
# httplib.HTTPConnection.putrequest adding
|
||||
# HTTP request header 'Host: www.google.com:443' instead of
|
||||
# 'Host: www.google.com', and thus resulting the error message
|
||||
# 'Token invalid - AuthSub token has wrong scope' in the HTTP response.
|
||||
if (url.protocol == 'https' and int(url.port or 443) == 443 and
|
||||
hasattr(connection, '_buffer') and
|
||||
isinstance(connection._buffer, list)):
|
||||
header_line = 'Host: %s:443' % url.host
|
||||
replacement_header_line = 'Host: %s' % url.host
|
||||
try:
|
||||
connection._buffer[connection._buffer.index(header_line)] = (
|
||||
replacement_header_line)
|
||||
except ValueError: # header_line missing from connection._buffer
|
||||
pass
|
||||
|
||||
# Send the HTTP headers.
|
||||
for header_name in all_headers:
|
||||
connection.putheader(header_name, all_headers[header_name])
|
||||
connection.endheaders()
|
||||
|
||||
# If there is data, send it in the request.
|
||||
if data:
|
||||
if isinstance(data, list):
|
||||
for data_part in data:
|
||||
_send_data_part(data_part, connection)
|
||||
else:
|
||||
_send_data_part(data, connection)
|
||||
|
||||
# Return the HTTP Response from the server.
|
||||
return connection.getresponse()
|
||||
|
||||
def _prepare_connection(self, url, headers):
|
||||
if not isinstance(url, atom.url.Url):
|
||||
if isinstance(url, (str,)):
|
||||
url = atom.url.parse_url(url)
|
||||
else:
|
||||
raise atom.http_interface.UnparsableUrlObject('Unable to parse url '
|
||||
'parameter because it was not a string or atom.url.Url')
|
||||
if url.protocol == 'https':
|
||||
if not url.port:
|
||||
return http.client.HTTPSConnection(url.host)
|
||||
return http.client.HTTPSConnection(url.host, int(url.port))
|
||||
else:
|
||||
if not url.port:
|
||||
return http.client.HTTPConnection(url.host)
|
||||
return http.client.HTTPConnection(url.host, int(url.port))
|
||||
|
||||
def _get_access_url(self, url):
|
||||
return url.to_string()
|
||||
|
||||
|
||||
class ProxiedHttpClient(HttpClient):
|
||||
"""Performs an HTTP request through a proxy.
|
||||
|
||||
The proxy settings are obtained from enviroment variables. The URL of the
|
||||
proxy server is assumed to be stored in the environment variables
|
||||
'https_proxy' and 'http_proxy' respectively. If the proxy server requires
|
||||
a Basic Auth authorization header, the username and password are expected to
|
||||
be in the 'proxy-username' or 'proxy_username' variable and the
|
||||
'proxy-password' or 'proxy_password' variable, or in 'http_proxy' or
|
||||
'https_proxy' as "protocol://[username:password@]host:port".
|
||||
|
||||
After connecting to the proxy server, the request is completed as in
|
||||
HttpClient.request.
|
||||
"""
|
||||
|
||||
def _prepare_connection(self, url, headers):
|
||||
proxy_settings = os.environ.get('%s_proxy' % url.protocol)
|
||||
if not proxy_settings:
|
||||
# The request was HTTP or HTTPS, but there was no appropriate proxy set.
|
||||
return HttpClient._prepare_connection(self, url, headers)
|
||||
else:
|
||||
proxy_auth = _get_proxy_auth(proxy_settings)
|
||||
proxy_netloc = _get_proxy_net_location(proxy_settings)
|
||||
if url.protocol == 'https':
|
||||
# Set any proxy auth headers
|
||||
if proxy_auth:
|
||||
proxy_auth = 'Proxy-Authorization: %s' % proxy_auth
|
||||
|
||||
# Construct the proxy connect command.
|
||||
port = url.port
|
||||
if not port:
|
||||
port = '443'
|
||||
proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (url.host, port)
|
||||
|
||||
# Set the user agent to send to the proxy
|
||||
if headers and 'User-Agent' in headers:
|
||||
user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent'])
|
||||
else:
|
||||
user_agent = 'User-Agent: python\r\n'
|
||||
|
||||
proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent)
|
||||
|
||||
# Find the proxy host and port.
|
||||
proxy_url = atom.url.parse_url(proxy_netloc)
|
||||
if not proxy_url.port:
|
||||
proxy_url.port = '80'
|
||||
|
||||
# Connect to the proxy server, very simple recv and error checking
|
||||
p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
p_sock.connect((proxy_url.host, int(proxy_url.port)))
|
||||
#p_sock.sendall(proxy_pieces)
|
||||
p_sock.sendall(proxy_pieces.encode('utf-8'))
|
||||
response = ''
|
||||
|
||||
# Wait for the full response.
|
||||
while response.find("\r\n\r\n") == -1:
|
||||
#response += p_sock.recv(8192)
|
||||
response += p_sock.recv(8192).decode('utf-8')
|
||||
|
||||
p_status = response.split()[1]
|
||||
if p_status != str(200):
|
||||
raise ProxyError('Error status=%s' % str(p_status))
|
||||
|
||||
# Trivial setup for ssl socket.
|
||||
sslobj = None
|
||||
if ssl_imported:
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
|
||||
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
sslobj = context.wrap_socket(p_sock, server_hostname=url.host)
|
||||
else:
|
||||
sock_ssl = socket.ssl(p_sock, None, None)
|
||||
sslobj = http.client.FakeSocket(p_sock, sock_ssl)
|
||||
|
||||
# Initalize httplib and replace with the proxy socket.
|
||||
connection = http.client.HTTPConnection(proxy_url.host)
|
||||
connection.sock = sslobj
|
||||
return connection
|
||||
else:
|
||||
# If protocol was not https.
|
||||
# Find the proxy host and port.
|
||||
proxy_url = atom.url.parse_url(proxy_netloc)
|
||||
if not proxy_url.port:
|
||||
proxy_url.port = '80'
|
||||
|
||||
if proxy_auth:
|
||||
headers['Proxy-Authorization'] = proxy_auth.strip()
|
||||
|
||||
return http.client.HTTPConnection(proxy_url.host, int(proxy_url.port))
|
||||
|
||||
def _get_access_url(self, url):
|
||||
return url.to_string()
|
||||
|
||||
|
||||
def _get_proxy_auth(proxy_settings):
|
||||
"""Returns proxy authentication string for header.
|
||||
|
||||
Will check environment variables for proxy authentication info, starting with
|
||||
proxy(_/-)username and proxy(_/-)password before checking the given
|
||||
proxy_settings for a [protocol://]username:password@host[:port] string.
|
||||
|
||||
Args:
|
||||
proxy_settings: String from http_proxy or https_proxy environment variable.
|
||||
|
||||
Returns:
|
||||
Authentication string for proxy, or empty string if no proxy username was
|
||||
found.
|
||||
"""
|
||||
proxy_username = None
|
||||
proxy_password = None
|
||||
|
||||
proxy_username = os.environ.get('proxy-username')
|
||||
if not proxy_username:
|
||||
proxy_username = os.environ.get('proxy_username')
|
||||
proxy_password = os.environ.get('proxy-password')
|
||||
if not proxy_password:
|
||||
proxy_password = os.environ.get('proxy_password')
|
||||
|
||||
if not proxy_username:
|
||||
if '@' in proxy_settings:
|
||||
protocol_and_proxy_auth = proxy_settings.split('@')[0].split(':')
|
||||
if len(protocol_and_proxy_auth) == 3:
|
||||
# 3 elements means we have [<protocol>, //<user>, <password>]
|
||||
proxy_username = protocol_and_proxy_auth[1].lstrip('/')
|
||||
proxy_password = protocol_and_proxy_auth[2]
|
||||
elif len(protocol_and_proxy_auth) == 2:
|
||||
# 2 elements means we have [<user>, <password>]
|
||||
proxy_username = protocol_and_proxy_auth[0]
|
||||
proxy_password = protocol_and_proxy_auth[1]
|
||||
if proxy_username:
|
||||
user_auth = base64.b64encode(('%s:%s' % (proxy_username, proxy_password)).encode('utf-8'))
|
||||
return 'Basic %s\r\n' % (user_auth.strip().decode('utf-8'))
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def _get_proxy_net_location(proxy_settings):
|
||||
"""Returns proxy host and port.
|
||||
|
||||
Args:
|
||||
proxy_settings: String from http_proxy or https_proxy environment variable.
|
||||
Must be in the form of protocol://[username:password@]host:port
|
||||
|
||||
Returns:
|
||||
String in the form of protocol://host:port
|
||||
"""
|
||||
if '@' in proxy_settings:
|
||||
protocol = proxy_settings.split(':')[0]
|
||||
netloc = proxy_settings.split('@')[1]
|
||||
return '%s://%s' % (protocol, netloc)
|
||||
else:
|
||||
return proxy_settings
|
||||
|
||||
|
||||
def _send_data_part(data, connection):
|
||||
if isinstance(data, (str,)):
|
||||
connection.send(data)
|
||||
return
|
||||
# Check to see if data is a file-like object that has a read method.
|
||||
elif hasattr(data, 'read'):
|
||||
# Read the file and send it a chunk at a time.
|
||||
while 1:
|
||||
binarydata = data.read(100000)
|
||||
if binarydata == b'': break
|
||||
connection.send(binarydata)
|
||||
return
|
||||
else:
|
||||
# The data object was not a file.
|
||||
# Try to convert to a string and send the data.
|
||||
#connection.send(str(data))
|
||||
connection.send(str(data).encode('utf-8'))
|
||||
return
|
||||
@@ -1,599 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2009 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
# 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.
|
||||
|
||||
|
||||
# This module is used for version 2 of the Google Data APIs.
|
||||
# TODO: add proxy handling.
|
||||
|
||||
|
||||
# __author__ = 'j.s@google.com (Jeff Scudder)'
|
||||
|
||||
import http.client
|
||||
import io
|
||||
import os
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
ssl = None
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownSize(Error):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyError(Error):
|
||||
pass
|
||||
|
||||
|
||||
MIME_BOUNDARY = 'END_OF_PART'
|
||||
|
||||
|
||||
def get_headers(http_response):
|
||||
"""Retrieves all HTTP headers from an HTTP response from the server.
|
||||
|
||||
This method is provided for backwards compatibility for Python2.2 and 2.3.
|
||||
The httplib.HTTPResponse object in 2.2 and 2.3 does not have a getheaders
|
||||
method so this function will use getheaders if available, but if not it
|
||||
will retrieve a few using getheader.
|
||||
"""
|
||||
if hasattr(http_response, 'getheaders'):
|
||||
return http_response.getheaders()
|
||||
else:
|
||||
headers = []
|
||||
for header in (
|
||||
'location', 'content-type', 'content-length', 'age', 'allow',
|
||||
'cache-control', 'content-location', 'content-encoding', 'date',
|
||||
'etag', 'expires', 'last-modified', 'pragma', 'server',
|
||||
'set-cookie', 'transfer-encoding', 'vary', 'via', 'warning',
|
||||
'www-authenticate', 'gdata-version'):
|
||||
value = http_response.getheader(header, None)
|
||||
if value is not None:
|
||||
headers.append((header, value))
|
||||
return headers
|
||||
|
||||
|
||||
class HttpRequest(object):
|
||||
"""Contains all of the parameters for an HTTP 1.1 request.
|
||||
|
||||
The HTTP headers are represented by a dictionary, and it is the
|
||||
responsibility of the user to ensure that duplicate field names are combined
|
||||
into one header value according to the rules in section 4.2 of RFC 2616.
|
||||
"""
|
||||
method = None
|
||||
uri = None
|
||||
|
||||
def __init__(self, uri=None, method=None, headers=None):
|
||||
"""Construct an HTTP request.
|
||||
|
||||
Args:
|
||||
uri: The full path or partial path as a Uri object or a string.
|
||||
method: The HTTP method for the request, examples include 'GET', 'POST',
|
||||
etc.
|
||||
headers: dict of strings The HTTP headers to include in the request.
|
||||
"""
|
||||
self.headers = headers or {}
|
||||
self._body_parts = []
|
||||
if method is not None:
|
||||
self.method = method
|
||||
if isinstance(uri, str):
|
||||
uri = Uri.parse_uri(uri)
|
||||
self.uri = uri or Uri()
|
||||
|
||||
def add_body_part(self, data, mime_type, size=None):
|
||||
"""Adds data to the HTTP request body.
|
||||
|
||||
If more than one part is added, this is assumed to be a mime-multipart
|
||||
request. This method is designed to create MIME 1.0 requests as specified
|
||||
in RFC 1341.
|
||||
|
||||
Args:
|
||||
data: str or a file-like object containing a part of the request body.
|
||||
mime_type: str The MIME type describing the data
|
||||
size: int Required if the data is a file like object. If the data is a
|
||||
string, the size is calculated so this parameter is ignored.
|
||||
"""
|
||||
if hasattr(data, '__len__'):
|
||||
size = len(data)
|
||||
if size is None:
|
||||
# TODO: support chunked transfer if some of the body is of unknown size.
|
||||
raise UnknownSize('Each part of the body must have a known size.')
|
||||
if 'Content-Length' in self.headers:
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
else:
|
||||
content_length = 0
|
||||
# If this is the first part added to the body, then this is not a multipart
|
||||
# request.
|
||||
if len(self._body_parts) == 0:
|
||||
self.headers['Content-Type'] = mime_type
|
||||
content_length = size
|
||||
self._body_parts.append(data)
|
||||
elif len(self._body_parts) == 1:
|
||||
# This is the first member in a mime-multipart request, so change the
|
||||
# _body_parts list to indicate a multipart payload.
|
||||
self._body_parts.insert(0, 'Media multipart posting')
|
||||
boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,)
|
||||
content_length += len(boundary_string) + size
|
||||
self._body_parts.insert(1, boundary_string)
|
||||
content_length += len('Media multipart posting')
|
||||
# Put the content type of the first part of the body into the multipart
|
||||
# payload.
|
||||
original_type_string = 'Content-Type: %s\r\n\r\n' % (
|
||||
self.headers['Content-Type'],)
|
||||
self._body_parts.insert(2, original_type_string)
|
||||
content_length += len(original_type_string)
|
||||
boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,)
|
||||
self._body_parts.append(boundary_string)
|
||||
content_length += len(boundary_string)
|
||||
# Change the headers to indicate this is now a mime multipart request.
|
||||
self.headers['Content-Type'] = 'multipart/related; boundary="%s"' % (
|
||||
MIME_BOUNDARY,)
|
||||
self.headers['MIME-version'] = '1.0'
|
||||
# Include the mime type of this part.
|
||||
type_string = 'Content-Type: %s\r\n\r\n' % (mime_type)
|
||||
self._body_parts.append(type_string)
|
||||
content_length += len(type_string)
|
||||
self._body_parts.append(data)
|
||||
ending_boundary_string = '\r\n--%s--' % (MIME_BOUNDARY,)
|
||||
self._body_parts.append(ending_boundary_string)
|
||||
content_length += len(ending_boundary_string)
|
||||
else:
|
||||
# This is a mime multipart request.
|
||||
boundary_string = '\r\n--%s\r\n' % (MIME_BOUNDARY,)
|
||||
self._body_parts.insert(-1, boundary_string)
|
||||
content_length += len(boundary_string) + size
|
||||
# Include the mime type of this part.
|
||||
type_string = 'Content-Type: %s\r\n\r\n' % (mime_type)
|
||||
self._body_parts.insert(-1, type_string)
|
||||
content_length += len(type_string)
|
||||
self._body_parts.insert(-1, data)
|
||||
self.headers['Content-Length'] = str(content_length)
|
||||
|
||||
# I could add an "append_to_body_part" method as well.
|
||||
|
||||
AddBodyPart = add_body_part
|
||||
|
||||
def add_form_inputs(self, form_data,
|
||||
mime_type='application/x-www-form-urlencoded'):
|
||||
"""Form-encodes and adds data to the request body.
|
||||
|
||||
Args:
|
||||
form_data: dict or sequnce or two member tuples which contains the
|
||||
form keys and values.
|
||||
mime_type: str The MIME type of the form data being sent. Defaults
|
||||
to 'application/x-www-form-urlencoded'.
|
||||
"""
|
||||
body = urllib.parse.urlencode(form_data)
|
||||
self.add_body_part(body, bytes(mime_type, 'ascii'))
|
||||
|
||||
AddFormInputs = add_form_inputs
|
||||
|
||||
def _copy(self):
|
||||
"""Creates a deep copy of this request."""
|
||||
copied_uri = Uri(self.uri.scheme, self.uri.host, self.uri.port,
|
||||
self.uri.path, self.uri.query.copy())
|
||||
new_request = HttpRequest(uri=copied_uri, method=self.method,
|
||||
headers=self.headers.copy())
|
||||
new_request._body_parts = self._body_parts[:]
|
||||
return new_request
|
||||
|
||||
def _dump(self):
|
||||
"""Converts to a printable string for debugging purposes.
|
||||
|
||||
In order to preserve the request, it does not read from file-like objects
|
||||
in the body.
|
||||
"""
|
||||
output = 'HTTP Request\n method: %s\n url: %s\n headers:\n' % (
|
||||
self.method, str(self.uri))
|
||||
for header, value in self.headers.items():
|
||||
output += ' %s: %s\n' % (header, value)
|
||||
output += ' body sections:\n'
|
||||
i = 0
|
||||
for part in self._body_parts:
|
||||
if isinstance(part, str):
|
||||
output += ' %s: %s\n' % (i, part)
|
||||
else:
|
||||
output += ' %s: <file like object>\n' % i
|
||||
i += 1
|
||||
return output
|
||||
|
||||
|
||||
def _apply_defaults(http_request):
|
||||
if http_request.uri.scheme is None:
|
||||
if http_request.uri.port == 443:
|
||||
http_request.uri.scheme = 'https'
|
||||
else:
|
||||
http_request.uri.scheme = 'http'
|
||||
|
||||
|
||||
class Uri(object):
|
||||
"""A URI as used in HTTP 1.1"""
|
||||
scheme = None
|
||||
host = None
|
||||
port = None
|
||||
path = None
|
||||
|
||||
def __init__(self, scheme=None, host=None, port=None, path=None, query=None):
|
||||
"""Constructor for a URI.
|
||||
|
||||
Args:
|
||||
scheme: str This is usually 'http' or 'https'.
|
||||
host: str The host name or IP address of the desired server.
|
||||
post: int The server's port number.
|
||||
path: str The path of the resource following the host. This begins with
|
||||
a /, example: '/calendar/feeds/default/allcalendars/full'
|
||||
query: dict of strings The URL query parameters. The keys and values are
|
||||
both escaped so this dict should contain the unescaped values.
|
||||
For example {'my key': 'val', 'second': '!!!'} will become
|
||||
'?my+key=val&second=%21%21%21' which is appended to the path.
|
||||
"""
|
||||
self.query = query or {}
|
||||
if scheme is not None:
|
||||
self.scheme = scheme
|
||||
if host is not None:
|
||||
self.host = host
|
||||
if port is not None:
|
||||
self.port = port
|
||||
if path:
|
||||
self.path = path
|
||||
|
||||
def _get_query_string(self):
|
||||
param_pairs = []
|
||||
for key, value in self.query.items():
|
||||
quoted_key = urllib.parse.quote_plus(str(key))
|
||||
if value is None:
|
||||
param_pairs.append(quoted_key)
|
||||
else:
|
||||
quoted_value = urllib.parse.quote_plus(str(value))
|
||||
param_pairs.append('%s=%s' % (quoted_key, quoted_value))
|
||||
return '&'.join(param_pairs)
|
||||
|
||||
def _get_relative_path(self):
|
||||
"""Returns the path with the query parameters escaped and appended."""
|
||||
param_string = self._get_query_string()
|
||||
if self.path is None:
|
||||
path = '/'
|
||||
else:
|
||||
path = self.path
|
||||
if param_string:
|
||||
return '?'.join([path, param_string])
|
||||
else:
|
||||
return path
|
||||
|
||||
def _to_string(self):
|
||||
if self.scheme is None and self.port == 443:
|
||||
scheme = 'https'
|
||||
elif self.scheme is None:
|
||||
scheme = 'http'
|
||||
else:
|
||||
scheme = self.scheme
|
||||
if self.path is None:
|
||||
path = '/'
|
||||
else:
|
||||
path = self.path
|
||||
if self.port is None:
|
||||
return '%s://%s%s' % (scheme, self.host, self._get_relative_path())
|
||||
else:
|
||||
return '%s://%s:%s%s' % (scheme, self.host, str(self.port),
|
||||
self._get_relative_path())
|
||||
|
||||
def __str__(self):
|
||||
return self._to_string()
|
||||
|
||||
def modify_request(self, http_request=None):
|
||||
"""Sets HTTP request components based on the URI."""
|
||||
if http_request is None:
|
||||
http_request = HttpRequest()
|
||||
if http_request.uri is None:
|
||||
http_request.uri = Uri()
|
||||
# Determine the correct scheme.
|
||||
if self.scheme:
|
||||
http_request.uri.scheme = self.scheme
|
||||
if self.port:
|
||||
http_request.uri.port = self.port
|
||||
if self.host:
|
||||
http_request.uri.host = self.host
|
||||
# Set the relative uri path
|
||||
if self.path:
|
||||
http_request.uri.path = self.path
|
||||
if self.query:
|
||||
http_request.uri.query = self.query.copy()
|
||||
return http_request
|
||||
|
||||
ModifyRequest = modify_request
|
||||
|
||||
def parse_uri(uri_string):
|
||||
"""Creates a Uri object which corresponds to the URI string.
|
||||
|
||||
This method can accept partial URIs, but it will leave missing
|
||||
members of the Uri unset.
|
||||
"""
|
||||
parts = urllib.parse.urlparse(uri_string)
|
||||
uri = Uri()
|
||||
if parts[0]:
|
||||
uri.scheme = parts[0]
|
||||
if parts[1]:
|
||||
host_parts = parts[1].split(':')
|
||||
if host_parts[0]:
|
||||
uri.host = host_parts[0]
|
||||
if len(host_parts) > 1:
|
||||
uri.port = int(host_parts[1])
|
||||
if parts[2]:
|
||||
uri.path = parts[2]
|
||||
if parts[4]:
|
||||
param_pairs = parts[4].split('&')
|
||||
for pair in param_pairs:
|
||||
pair_parts = pair.split('=')
|
||||
if len(pair_parts) > 1:
|
||||
uri.query[urllib.parse.unquote_plus(pair_parts[0])] = (
|
||||
urllib.parse.unquote_plus(pair_parts[1]))
|
||||
elif len(pair_parts) == 1:
|
||||
uri.query[urllib.parse.unquote_plus(pair_parts[0])] = None
|
||||
return uri
|
||||
|
||||
parse_uri = staticmethod(parse_uri)
|
||||
|
||||
ParseUri = parse_uri
|
||||
|
||||
|
||||
parse_uri = Uri.parse_uri
|
||||
|
||||
ParseUri = Uri.parse_uri
|
||||
|
||||
|
||||
class HttpResponse(object):
|
||||
status = None
|
||||
reason = None
|
||||
_body = None
|
||||
|
||||
def __init__(self, status=None, reason=None, headers=None, body=None):
|
||||
self._headers = headers or {}
|
||||
if status is not None:
|
||||
self.status = status
|
||||
if reason is not None:
|
||||
self.reason = reason
|
||||
if body is not None:
|
||||
if hasattr(body, 'read'):
|
||||
self._body = body
|
||||
else:
|
||||
self._body = io.StringIO(body)
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
if name in self._headers:
|
||||
return self._headers[name]
|
||||
else:
|
||||
return default
|
||||
|
||||
def getheaders(self):
|
||||
return self._headers
|
||||
|
||||
def read(self, amt=None):
|
||||
if self._body is None:
|
||||
return None
|
||||
if not amt:
|
||||
return self._body.read()
|
||||
else:
|
||||
return self._body.read(amt)
|
||||
|
||||
|
||||
def _dump_response(http_response):
|
||||
"""Converts to a string for printing debug messages.
|
||||
|
||||
Does not read the body since that may consume the content.
|
||||
"""
|
||||
output = 'HttpResponse\n status: %s\n reason: %s\n headers:' % (
|
||||
http_response.status, http_response.reason)
|
||||
headers = get_headers(http_response)
|
||||
if isinstance(headers, dict):
|
||||
for header, value in headers.items():
|
||||
output += ' %s: %s\n' % (header, value)
|
||||
else:
|
||||
for pair in headers:
|
||||
output += ' %s: %s\n' % (pair[0], pair[1])
|
||||
return output
|
||||
|
||||
|
||||
class HttpClient(object):
|
||||
"""Performs HTTP requests using httplib."""
|
||||
debug = None
|
||||
|
||||
def request(self, http_request):
|
||||
return self._http_request(http_request.method, http_request.uri,
|
||||
http_request.headers, http_request._body_parts)
|
||||
|
||||
Request = request
|
||||
|
||||
def _get_connection(self, uri, headers=None):
|
||||
"""Opens a socket connection to the server to set up an HTTP request.
|
||||
|
||||
Args:
|
||||
uri: The full URL for the request as a Uri object.
|
||||
headers: A dict of string pairs containing the HTTP headers for the
|
||||
request.
|
||||
"""
|
||||
connection = None
|
||||
if uri.scheme == 'https':
|
||||
if not uri.port:
|
||||
connection = http.client.HTTPSConnection(uri.host)
|
||||
else:
|
||||
connection = http.client.HTTPSConnection(uri.host, int(uri.port))
|
||||
else:
|
||||
if not uri.port:
|
||||
connection = http.client.HTTPConnection(uri.host)
|
||||
else:
|
||||
connection = http.client.HTTPConnection(uri.host, int(uri.port))
|
||||
return connection
|
||||
|
||||
def _http_request(self, method, uri, headers=None, body_parts=None):
|
||||
"""Makes an HTTP request using httplib.
|
||||
|
||||
Args:
|
||||
method: str example: 'GET', 'POST', 'PUT', 'DELETE', etc.
|
||||
uri: str or atom.http_core.Uri
|
||||
headers: dict of strings mapping to strings which will be sent as HTTP
|
||||
headers in the request.
|
||||
body_parts: list of strings, objects with a read method, or objects
|
||||
which can be converted to strings using str. Each of these
|
||||
will be sent in order as the body of the HTTP request.
|
||||
"""
|
||||
if isinstance(uri, str):
|
||||
uri = Uri.parse_uri(uri)
|
||||
|
||||
connection = self._get_connection(uri, headers=headers)
|
||||
|
||||
if self.debug:
|
||||
connection.debuglevel = 1
|
||||
|
||||
if connection.host != uri.host:
|
||||
connection.putrequest(method, str(uri))
|
||||
else:
|
||||
connection.putrequest(method, uri._get_relative_path())
|
||||
|
||||
# Overcome a bug in Python 2.4 and 2.5
|
||||
# httplib.HTTPConnection.putrequest adding
|
||||
# HTTP request header 'Host: www.google.com:443' instead of
|
||||
# 'Host: www.google.com', and thus resulting the error message
|
||||
# 'Token invalid - AuthSub token has wrong scope' in the HTTP response.
|
||||
if (uri.scheme == 'https' and int(uri.port or 443) == 443 and
|
||||
hasattr(connection, '_buffer') and
|
||||
isinstance(connection._buffer, list)):
|
||||
header_line = 'Host: %s:443' % uri.host
|
||||
replacement_header_line = 'Host: %s' % uri.host
|
||||
try:
|
||||
connection._buffer[connection._buffer.index(header_line)] = (
|
||||
replacement_header_line)
|
||||
except ValueError: # header_line missing from connection._buffer
|
||||
pass
|
||||
|
||||
# Send the HTTP headers.
|
||||
for header_name, value in headers.items():
|
||||
connection.putheader(header_name, value)
|
||||
connection.endheaders()
|
||||
|
||||
# If there is data, send it in the request.
|
||||
if body_parts and [x for x in body_parts if x != '']:
|
||||
for part in body_parts:
|
||||
_send_data_part(part, connection)
|
||||
|
||||
# Return the HTTP Response from the server.
|
||||
return connection.getresponse()
|
||||
|
||||
|
||||
def _send_data_part(data, connection):
|
||||
if isinstance(data, str):
|
||||
# I might want to just allow str, not unicode.
|
||||
connection.send(data.encode())
|
||||
# Check to see if data is a file-like object that has a read method.
|
||||
elif hasattr(data, 'read'):
|
||||
# Read the file and send it a chunk at a time.
|
||||
while 1:
|
||||
binarydata = data.read(100000)
|
||||
if binarydata == '': break
|
||||
connection.send(binarydata)
|
||||
else:
|
||||
# The data object was not a file.
|
||||
# Try to convert to a string and send the data.
|
||||
connection.send(data)
|
||||
|
||||
|
||||
class ProxiedHttpClient(HttpClient):
|
||||
def _get_connection(self, uri, headers=None):
|
||||
# Check to see if there are proxy settings required for this request.
|
||||
proxy = None
|
||||
if uri.scheme == 'https':
|
||||
proxy = os.environ.get('https_proxy')
|
||||
elif uri.scheme == 'http':
|
||||
proxy = os.environ.get('http_proxy')
|
||||
if not proxy:
|
||||
return HttpClient._get_connection(self, uri, headers=headers)
|
||||
# Now we have the URL of the appropriate proxy server.
|
||||
# Get a username and password for the proxy if required.
|
||||
proxy_auth = _get_proxy_auth()
|
||||
if uri.scheme == 'https':
|
||||
import socket
|
||||
if proxy_auth:
|
||||
proxy_auth = 'Proxy-Authorization: %s' % proxy_auth
|
||||
# Construct the proxy connect command.
|
||||
port = uri.port
|
||||
if not port:
|
||||
port = 443
|
||||
proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (uri.host, port)
|
||||
# Set the user agent to send to the proxy
|
||||
user_agent = ''
|
||||
if headers and 'User-Agent' in headers:
|
||||
user_agent = 'User-Agent: %s\r\n' % (headers['User-Agent'])
|
||||
proxy_pieces = '%s%s%s\r\n' % (proxy_connect, proxy_auth, user_agent)
|
||||
# Find the proxy host and port.
|
||||
proxy_uri = Uri.parse_uri(proxy)
|
||||
if not proxy_uri.port:
|
||||
proxy_uri.port = '80'
|
||||
# Connect to the proxy server, very simple recv and error checking
|
||||
p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
p_sock.connect((proxy_uri.host, int(proxy_uri.port)))
|
||||
#p_sock.sendall(proxy_pieces)
|
||||
p_sock.sendall(proxy_pieces.encode('utf-8'))
|
||||
response = ''
|
||||
# Wait for the full response.
|
||||
while response.find("\r\n\r\n") == -1:
|
||||
#response += p_sock.recv(8192)
|
||||
response += p_sock.recv(8192).decode('utf-8')
|
||||
p_status = response.split()[1]
|
||||
if p_status != str(200):
|
||||
raise ProxyError('Error status=%s' % str(p_status))
|
||||
# Trivial setup for ssl socket.
|
||||
sslobj = None
|
||||
if ssl is not None:
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS)
|
||||
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
sslobj = context.wrap_socket(p_sock, server_hostname=uri.host)
|
||||
else:
|
||||
sock_ssl = socket.ssl(p_sock, None, None)
|
||||
sslobj = http.client.FakeSocket(p_sock, sock_ssl)
|
||||
# Initalize httplib and replace with the proxy socket.
|
||||
connection = http.client.HTTPConnection(proxy_uri.host)
|
||||
connection.sock = sslobj
|
||||
return connection
|
||||
elif uri.scheme == 'http':
|
||||
proxy_uri = Uri.parse_uri(proxy)
|
||||
if not proxy_uri.port:
|
||||
proxy_uri.port = '80'
|
||||
if proxy_auth:
|
||||
headers['Proxy-Authorization'] = proxy_auth.strip()
|
||||
return http.client.HTTPConnection(proxy_uri.host, int(proxy_uri.port))
|
||||
return None
|
||||
|
||||
|
||||
def _get_proxy_auth():
|
||||
import base64
|
||||
proxy_username = os.environ.get('proxy-username')
|
||||
if not proxy_username:
|
||||
proxy_username = os.environ.get('proxy_username')
|
||||
proxy_password = os.environ.get('proxy-password')
|
||||
if not proxy_password:
|
||||
proxy_password = os.environ.get('proxy_password')
|
||||
if proxy_username:
|
||||
user_auth = base64.b64encode(('%s:%s' % (proxy_username, proxy_password)).encode('utf-8'))
|
||||
return 'Basic %s\r\n' % (user_auth.strip().decode('utf-8'))
|
||||
else:
|
||||
return ''
|
||||
@@ -1,144 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
"""This module provides a common interface for all HTTP requests.
|
||||
|
||||
HttpResponse: Represents the server's response to an HTTP request. Provides
|
||||
an interface identical to httplib.HTTPResponse which is the response
|
||||
expected from higher level classes which use HttpClient.request.
|
||||
|
||||
GenericHttpClient: Provides an interface (superclass) for an object
|
||||
responsible for making HTTP requests. Subclasses of this object are
|
||||
used in AtomService and GDataService to make requests to the server. By
|
||||
changing the http_client member object, the AtomService is able to make
|
||||
HTTP requests using different logic (for example, when running on
|
||||
Google App Engine, the http_client makes requests using the App Engine
|
||||
urlfetch API).
|
||||
"""
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import io
|
||||
|
||||
USER_AGENT = '%s GData-Python/2.0.18'
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnparsableUrlObject(Error):
|
||||
pass
|
||||
|
||||
|
||||
class ContentLengthRequired(Error):
|
||||
pass
|
||||
|
||||
|
||||
class HttpResponse(object):
|
||||
def __init__(self, body=None, status=None, reason=None, headers=None):
|
||||
"""Constructor for an HttpResponse object.
|
||||
|
||||
HttpResponse represents the server's response to an HTTP request from
|
||||
the client. The HttpClient.request method returns a httplib.HTTPResponse
|
||||
object and this HttpResponse class is designed to mirror the interface
|
||||
exposed by httplib.HTTPResponse.
|
||||
|
||||
Args:
|
||||
body: A file like object, with a read() method. The body could also
|
||||
be a string, and the constructor will wrap it so that
|
||||
HttpResponse.read(self) will return the full string.
|
||||
status: The HTTP status code as an int. Example: 200, 201, 404.
|
||||
reason: The HTTP status message which follows the code. Example:
|
||||
OK, Created, Not Found
|
||||
headers: A dictionary containing the HTTP headers in the server's
|
||||
response. A common header in the response is Content-Length.
|
||||
"""
|
||||
if body:
|
||||
if hasattr(body, 'read'):
|
||||
self._body = body
|
||||
else:
|
||||
self._body = io.StringIO(body)
|
||||
else:
|
||||
self._body = None
|
||||
if status is not None:
|
||||
self.status = int(status)
|
||||
else:
|
||||
self.status = None
|
||||
self.reason = reason
|
||||
self._headers = headers or {}
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
if name in self._headers:
|
||||
return self._headers[name]
|
||||
else:
|
||||
return default
|
||||
|
||||
def read(self, amt=None):
|
||||
if not amt:
|
||||
return self._body.read()
|
||||
else:
|
||||
return self._body.read(amt)
|
||||
|
||||
|
||||
class GenericHttpClient(object):
|
||||
debug = False
|
||||
|
||||
def __init__(self, http_client, headers=None):
|
||||
"""
|
||||
|
||||
Args:
|
||||
http_client: An object which provides a request method to make an HTTP
|
||||
request. The request method in GenericHttpClient performs a
|
||||
call-through to the contained HTTP client object.
|
||||
headers: A dictionary containing HTTP headers which should be included
|
||||
in every HTTP request. Common persistent headers include
|
||||
'User-Agent'.
|
||||
"""
|
||||
self.http_client = http_client
|
||||
self.headers = headers or {}
|
||||
|
||||
def request(self, operation, url, data=None, headers=None):
|
||||
all_headers = self.headers.copy()
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
return self.http_client.request(operation, url, data=data,
|
||||
headers=all_headers)
|
||||
|
||||
def get(self, url, headers=None):
|
||||
return self.request('GET', url, headers=headers)
|
||||
|
||||
def post(self, url, data, headers=None):
|
||||
return self.request('POST', url, data=data, headers=headers)
|
||||
|
||||
def put(self, url, data, headers=None):
|
||||
return self.request('PUT', url, data=data, headers=headers)
|
||||
|
||||
def delete(self, url, headers=None):
|
||||
return self.request('DELETE', url, headers=headers)
|
||||
|
||||
|
||||
class GenericToken(object):
|
||||
"""Represents an Authorization token to be added to HTTP requests.
|
||||
|
||||
Some Authorization headers included calculated fields (digital
|
||||
signatures for example) which are based on the parameters of the HTTP
|
||||
request. Therefore the token is responsible for signing the request
|
||||
and adding the Authorization header.
|
||||
"""
|
||||
|
||||
def perform_request(self, http_client, operation, url, data=None,
|
||||
headers=None):
|
||||
"""For the GenericToken, no Authorization token is set."""
|
||||
return http_client.request(operation, url, data=data, headers=headers)
|
||||
|
||||
def valid_for_scope(self, url):
|
||||
"""Tells the caller if the token authorizes access to the desired URL.
|
||||
|
||||
Since the generic token doesn't add an auth header, it is not valid for
|
||||
any scope.
|
||||
"""
|
||||
return False
|
||||
@@ -1,723 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2006, 2007, 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
"""AtomService provides CRUD ops. in line with the Atom Publishing Protocol.
|
||||
|
||||
AtomService: Encapsulates the ability to perform insert, update and delete
|
||||
operations with the Atom Publishing Protocol on which GData is
|
||||
based. An instance can perform query, insertion, deletion, and
|
||||
update.
|
||||
|
||||
HttpRequest: Function that performs a GET, POST, PUT, or DELETE HTTP request
|
||||
to the specified end point. An AtomService object or a subclass can be
|
||||
used to specify information about the request.
|
||||
"""
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import base64
|
||||
import http.client
|
||||
import os
|
||||
import socket
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import warnings
|
||||
|
||||
import atom.http
|
||||
import atom.http_interface
|
||||
import atom.token_store
|
||||
import atom.url
|
||||
|
||||
import lxml.etree as ElementTree
|
||||
import atom
|
||||
|
||||
|
||||
class AtomService(object):
|
||||
"""Performs Atom Publishing Protocol CRUD operations.
|
||||
|
||||
The AtomService contains methods to perform HTTP CRUD operations.
|
||||
"""
|
||||
|
||||
# Default values for members
|
||||
port = 80
|
||||
ssl = False
|
||||
# Set the current_token to force the AtomService to use this token
|
||||
# instead of searching for an appropriate token in the token_store.
|
||||
current_token = None
|
||||
auto_store_tokens = True
|
||||
auto_set_current_token = True
|
||||
|
||||
def _get_override_token(self):
|
||||
return self.current_token
|
||||
|
||||
def _set_override_token(self, token):
|
||||
self.current_token = token
|
||||
|
||||
override_token = property(_get_override_token, _set_override_token)
|
||||
|
||||
# @atom.v1_deprecated('Please use atom.client.AtomPubClient instead.')
|
||||
def __init__(self, server=None, additional_headers=None,
|
||||
application_name='', http_client=None, token_store=None):
|
||||
"""Creates a new AtomService client.
|
||||
|
||||
Args:
|
||||
server: string (optional) The start of a URL for the server
|
||||
to which all operations should be directed. Example:
|
||||
'www.google.com'
|
||||
additional_headers: dict (optional) Any additional HTTP headers which
|
||||
should be included with CRUD operations.
|
||||
http_client: An object responsible for making HTTP requests using a
|
||||
request method. If none is provided, a new instance of
|
||||
atom.http.ProxiedHttpClient will be used.
|
||||
token_store: Keeps a collection of authorization tokens which can be
|
||||
applied to requests for a specific URLs. Critical methods are
|
||||
find_token based on a URL (atom.url.Url or a string), add_token,
|
||||
and remove_token.
|
||||
"""
|
||||
self.http_client = http_client or atom.http.ProxiedHttpClient()
|
||||
self.token_store = token_store or atom.token_store.TokenStore()
|
||||
self.server = server
|
||||
self.additional_headers = additional_headers or {}
|
||||
self.additional_headers['User-Agent'] = atom.http_interface.USER_AGENT % (
|
||||
application_name,)
|
||||
# If debug is True, the HTTPConnection will display debug information
|
||||
self._set_debug(False)
|
||||
|
||||
__init__ = atom.v1_deprecated(
|
||||
'Please use atom.client.AtomPubClient instead.')(
|
||||
__init__)
|
||||
|
||||
def _get_debug(self):
|
||||
return self.http_client.debug
|
||||
|
||||
def _set_debug(self, value):
|
||||
self.http_client.debug = value
|
||||
|
||||
debug = property(_get_debug, _set_debug,
|
||||
doc='If True, HTTP debug information is printed.')
|
||||
|
||||
def use_basic_auth(self, username, password, scopes=None):
|
||||
if username is not None and password is not None:
|
||||
if scopes is None:
|
||||
scopes = [atom.token_store.SCOPE_ALL]
|
||||
base_64_string = base64.encodestring('%s:%s' % (username, password))
|
||||
token = BasicAuthToken('Basic %s' % base_64_string.strip(),
|
||||
scopes=[atom.token_store.SCOPE_ALL])
|
||||
if self.auto_set_current_token:
|
||||
self.current_token = token
|
||||
if self.auto_store_tokens:
|
||||
return self.token_store.add_token(token)
|
||||
return True
|
||||
return False
|
||||
|
||||
def UseBasicAuth(self, username, password, for_proxy=False):
|
||||
"""Sets an Authenticaiton: Basic HTTP header containing plaintext.
|
||||
|
||||
Deprecated, use use_basic_auth instead.
|
||||
|
||||
The username and password are base64 encoded and added to an HTTP header
|
||||
which will be included in each request. Note that your username and
|
||||
password are sent in plaintext.
|
||||
|
||||
Args:
|
||||
username: str
|
||||
password: str
|
||||
"""
|
||||
self.use_basic_auth(username, password)
|
||||
|
||||
# @atom.v1_deprecated('Please use atom.client.AtomPubClient for requests.')
|
||||
def request(self, operation, url, data=None, headers=None,
|
||||
url_params=None):
|
||||
if isinstance(url, str):
|
||||
if url.startswith('http:') and self.ssl:
|
||||
# Force all requests to be https if self.ssl is True.
|
||||
url = atom.url.parse_url('https:' + url[5:])
|
||||
elif not url.startswith('http') and self.ssl:
|
||||
url = atom.url.parse_url('https://%s%s' % (self.server, url))
|
||||
elif not url.startswith('http'):
|
||||
url = atom.url.parse_url('http://%s%s' % (self.server, url))
|
||||
else:
|
||||
url = atom.url.parse_url(url)
|
||||
|
||||
if url_params:
|
||||
for name, value in url_params.items():
|
||||
url.params[name] = value
|
||||
|
||||
all_headers = self.additional_headers.copy()
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
|
||||
# If the list of headers does not include a Content-Length, attempt to
|
||||
# calculate it based on the data object.
|
||||
if data and 'Content-Length' not in all_headers:
|
||||
content_length = CalculateDataLength(data)
|
||||
if content_length:
|
||||
all_headers['Content-Length'] = str(content_length)
|
||||
|
||||
# Find an Authorization token for this URL if one is available.
|
||||
if self.override_token:
|
||||
auth_token = self.override_token
|
||||
else:
|
||||
auth_token = self.token_store.find_token(url)
|
||||
return auth_token.perform_request(self.http_client, operation, url,
|
||||
data=data, headers=all_headers)
|
||||
|
||||
request = atom.v1_deprecated(
|
||||
'Please use atom.client.AtomPubClient for requests.')(
|
||||
request)
|
||||
|
||||
# CRUD operations
|
||||
def Get(self, uri, extra_headers=None, url_params=None, escape_params=True):
|
||||
"""Query the APP server with the given URI
|
||||
|
||||
The uri is the portion of the URI after the server value
|
||||
(server example: 'www.google.com').
|
||||
|
||||
Example use:
|
||||
To perform a query against Google Base, set the server to
|
||||
'base.google.com' and set the uri to '/base/feeds/...', where ... is
|
||||
your query. For example, to find snippets for all digital cameras uri
|
||||
should be set to: '/base/feeds/snippets?bq=digital+camera'
|
||||
|
||||
Args:
|
||||
uri: string The query in the form of a URI. Example:
|
||||
'/base/feeds/snippets?bq=digital+camera'.
|
||||
extra_headers: dicty (optional) Extra HTTP headers to be included
|
||||
in the GET request. These headers are in addition to
|
||||
those stored in the client's additional_headers property.
|
||||
The client automatically sets the Content-Type and
|
||||
Authorization headers.
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the query. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
httplib.HTTPResponse The server's response to the GET request.
|
||||
"""
|
||||
return self.request('GET', uri, data=None, headers=extra_headers,
|
||||
url_params=url_params)
|
||||
|
||||
def Post(self, data, uri, extra_headers=None, url_params=None,
|
||||
escape_params=True, content_type='application/atom+xml'):
|
||||
"""Insert data into an APP server at the given URI.
|
||||
|
||||
Args:
|
||||
data: string, ElementTree._Element, or something with a __str__ method
|
||||
The XML to be sent to the uri.
|
||||
uri: string The location (feed) to which the data should be inserted.
|
||||
Example: '/base/feeds/items'.
|
||||
extra_headers: dict (optional) HTTP headers which are to be included.
|
||||
The client automatically sets the Content-Type,
|
||||
Authorization, and Content-Length headers.
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the URI. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
httplib.HTTPResponse Server's response to the POST request.
|
||||
"""
|
||||
if extra_headers is None:
|
||||
extra_headers = {}
|
||||
if content_type:
|
||||
extra_headers['Content-Type'] = content_type
|
||||
return self.request('POST', uri, data=data, headers=extra_headers,
|
||||
url_params=url_params)
|
||||
|
||||
def Put(self, data, uri, extra_headers=None, url_params=None,
|
||||
escape_params=True, content_type='application/atom+xml'):
|
||||
"""Updates an entry at the given URI.
|
||||
|
||||
Args:
|
||||
data: string, ElementTree._Element, or xml_wrapper.ElementWrapper The
|
||||
XML containing the updated data.
|
||||
uri: string A URI indicating entry to which the update will be applied.
|
||||
Example: '/base/feeds/items/ITEM-ID'
|
||||
extra_headers: dict (optional) HTTP headers which are to be included.
|
||||
The client automatically sets the Content-Type,
|
||||
Authorization, and Content-Length headers.
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the URI. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
httplib.HTTPResponse Server's response to the PUT request.
|
||||
"""
|
||||
if extra_headers is None:
|
||||
extra_headers = {}
|
||||
if content_type:
|
||||
extra_headers['Content-Type'] = content_type
|
||||
return self.request('PUT', uri, data=data, headers=extra_headers,
|
||||
url_params=url_params)
|
||||
|
||||
def Delete(self, uri, extra_headers=None, url_params=None,
|
||||
escape_params=True):
|
||||
"""Deletes the entry at the given URI.
|
||||
|
||||
Args:
|
||||
uri: string The URI of the entry to be deleted. Example:
|
||||
'/base/feeds/items/ITEM-ID'
|
||||
extra_headers: dict (optional) HTTP headers which are to be included.
|
||||
The client automatically sets the Content-Type and
|
||||
Authorization headers.
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the URI. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
httplib.HTTPResponse Server's response to the DELETE request.
|
||||
"""
|
||||
return self.request('DELETE', uri, data=None, headers=extra_headers,
|
||||
url_params=url_params)
|
||||
|
||||
|
||||
class BasicAuthToken(atom.http_interface.GenericToken):
|
||||
def __init__(self, auth_header, scopes=None):
|
||||
"""Creates a token used to add Basic Auth headers to HTTP requests.
|
||||
|
||||
Args:
|
||||
auth_header: str The value for the Authorization header.
|
||||
scopes: list of str or atom.url.Url specifying the beginnings of URLs
|
||||
for which this token can be used. For example, if scopes contains
|
||||
'http://example.com/foo', then this token can be used for a request to
|
||||
'http://example.com/foo/bar' but it cannot be used for a request to
|
||||
'http://example.com/baz'
|
||||
"""
|
||||
self.auth_header = auth_header
|
||||
self.scopes = scopes or []
|
||||
|
||||
def perform_request(self, http_client, operation, url, data=None,
|
||||
headers=None):
|
||||
"""Sets the Authorization header to the basic auth string."""
|
||||
if headers is None:
|
||||
headers = {'Authorization': self.auth_header}
|
||||
else:
|
||||
headers['Authorization'] = self.auth_header
|
||||
return http_client.request(operation, url, data=data, headers=headers)
|
||||
|
||||
def __str__(self):
|
||||
return self.auth_header
|
||||
|
||||
def valid_for_scope(self, url):
|
||||
"""Tells the caller if the token authorizes access to the desired URL.
|
||||
"""
|
||||
if isinstance(url, str):
|
||||
url = atom.url.parse_url(url)
|
||||
for scope in self.scopes:
|
||||
if scope == atom.token_store.SCOPE_ALL:
|
||||
return True
|
||||
if isinstance(scope, str):
|
||||
scope = atom.url.parse_url(scope)
|
||||
if scope == url:
|
||||
return True
|
||||
# Check the host and the path, but ignore the port and protocol.
|
||||
elif scope.host == url.host and not scope.path:
|
||||
return True
|
||||
elif scope.host == url.host and scope.path and not url.path:
|
||||
continue
|
||||
elif scope.host == url.host and url.path.startswith(scope.path):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def PrepareConnection(service, full_uri):
|
||||
"""Opens a connection to the server based on the full URI.
|
||||
|
||||
This method is deprecated, instead use atom.http.HttpClient.request.
|
||||
|
||||
Examines the target URI and the proxy settings, which are set as
|
||||
environment variables, to open a connection with the server. This
|
||||
connection is used to make an HTTP request.
|
||||
|
||||
Args:
|
||||
service: atom.AtomService or a subclass. It must have a server string which
|
||||
represents the server host to which the request should be made. It may also
|
||||
have a dictionary of additional_headers to send in the HTTP request.
|
||||
full_uri: str Which is the target relative (lacks protocol and host) or
|
||||
absolute URL to be opened. Example:
|
||||
'https://www.google.com/accounts/ClientLogin' or
|
||||
'base/feeds/snippets' where the server is set to www.google.com.
|
||||
|
||||
Returns:
|
||||
A tuple containing the httplib.HTTPConnection and the full_uri for the
|
||||
request.
|
||||
"""
|
||||
deprecation('calling deprecated function PrepareConnection')
|
||||
(server, port, ssl, partial_uri) = ProcessUrl(service, full_uri)
|
||||
if ssl:
|
||||
# destination is https
|
||||
proxy = os.environ.get('https_proxy')
|
||||
if proxy:
|
||||
(p_server, p_port, p_ssl, p_uri) = ProcessUrl(service, proxy, True)
|
||||
proxy_username = os.environ.get('proxy-username')
|
||||
if not proxy_username:
|
||||
proxy_username = os.environ.get('proxy_username')
|
||||
proxy_password = os.environ.get('proxy-password')
|
||||
if not proxy_password:
|
||||
proxy_password = os.environ.get('proxy_password')
|
||||
if proxy_username:
|
||||
user_auth = base64.encodestring('%s:%s' % (proxy_username,
|
||||
proxy_password))
|
||||
proxy_authorization = ('Proxy-authorization: Basic %s\r\n' % (
|
||||
user_auth.strip()))
|
||||
else:
|
||||
proxy_authorization = ''
|
||||
proxy_connect = 'CONNECT %s:%s HTTP/1.0\r\n' % (server, port)
|
||||
user_agent = 'User-Agent: %s\r\n' % (
|
||||
service.additional_headers['User-Agent'])
|
||||
proxy_pieces = (proxy_connect + proxy_authorization + user_agent
|
||||
+ '\r\n')
|
||||
|
||||
# now connect, very simple recv and error checking
|
||||
p_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
p_sock.connect((p_server, p_port))
|
||||
p_sock.sendall(proxy_pieces)
|
||||
response = ''
|
||||
|
||||
# Wait for the full response.
|
||||
while response.find("\r\n\r\n") == -1:
|
||||
response += p_sock.recv(8192)
|
||||
|
||||
p_status = response.split()[1]
|
||||
if p_status != str(200):
|
||||
raise atom.http.ProxyError('Error status=%s' % p_status)
|
||||
|
||||
# Trivial setup for ssl socket.
|
||||
ssl = socket.ssl(p_sock, None, None)
|
||||
fake_sock = http.client.FakeSocket(p_sock, ssl)
|
||||
|
||||
# Initalize httplib and replace with the proxy socket.
|
||||
connection = http.client.HTTPConnection(server)
|
||||
connection.sock = fake_sock
|
||||
full_uri = partial_uri
|
||||
|
||||
else:
|
||||
connection = http.client.HTTPSConnection(server, port)
|
||||
full_uri = partial_uri
|
||||
|
||||
else:
|
||||
# destination is http
|
||||
proxy = os.environ.get('http_proxy')
|
||||
if proxy:
|
||||
(p_server, p_port, p_ssl, p_uri) = ProcessUrl(service.server, proxy, True)
|
||||
proxy_username = os.environ.get('proxy-username')
|
||||
if not proxy_username:
|
||||
proxy_username = os.environ.get('proxy_username')
|
||||
proxy_password = os.environ.get('proxy-password')
|
||||
if not proxy_password:
|
||||
proxy_password = os.environ.get('proxy_password')
|
||||
if proxy_username:
|
||||
UseBasicAuth(service, proxy_username, proxy_password, True)
|
||||
connection = http.client.HTTPConnection(p_server, p_port)
|
||||
if not full_uri.startswith("http://"):
|
||||
if full_uri.startswith("/"):
|
||||
full_uri = "http://%s%s" % (service.server, full_uri)
|
||||
else:
|
||||
full_uri = "http://%s/%s" % (service.server, full_uri)
|
||||
else:
|
||||
connection = http.client.HTTPConnection(server, port)
|
||||
full_uri = partial_uri
|
||||
|
||||
return (connection, full_uri)
|
||||
|
||||
|
||||
def UseBasicAuth(service, username, password, for_proxy=False):
|
||||
"""Sets an Authenticaiton: Basic HTTP header containing plaintext.
|
||||
|
||||
Deprecated, use AtomService.use_basic_auth insread.
|
||||
|
||||
The username and password are base64 encoded and added to an HTTP header
|
||||
which will be included in each request. Note that your username and
|
||||
password are sent in plaintext. The auth header is added to the
|
||||
additional_headers dictionary in the service object.
|
||||
|
||||
Args:
|
||||
service: atom.AtomService or a subclass which has an
|
||||
additional_headers dict as a member.
|
||||
username: str
|
||||
password: str
|
||||
"""
|
||||
deprecation('calling deprecated function UseBasicAuth')
|
||||
base_64_string = base64.encodestring('%s:%s' % (username, password))
|
||||
base_64_string = base_64_string.strip()
|
||||
if for_proxy:
|
||||
header_name = 'Proxy-Authorization'
|
||||
else:
|
||||
header_name = 'Authorization'
|
||||
service.additional_headers[header_name] = 'Basic %s' % (base_64_string,)
|
||||
|
||||
|
||||
def ProcessUrl(service, url, for_proxy=False):
|
||||
"""Processes a passed URL. If the URL does not begin with https?, then
|
||||
the default value for server is used
|
||||
|
||||
This method is deprecated, use atom.url.parse_url instead.
|
||||
"""
|
||||
if not isinstance(url, atom.url.Url):
|
||||
url = atom.url.parse_url(url)
|
||||
|
||||
server = url.host
|
||||
ssl = False
|
||||
port = 80
|
||||
|
||||
if not server:
|
||||
if hasattr(service, 'server'):
|
||||
server = service.server
|
||||
else:
|
||||
server = service
|
||||
if not url.protocol and hasattr(service, 'ssl'):
|
||||
ssl = service.ssl
|
||||
if hasattr(service, 'port'):
|
||||
port = service.port
|
||||
else:
|
||||
if url.protocol == 'https':
|
||||
ssl = True
|
||||
elif url.protocol == 'http':
|
||||
ssl = False
|
||||
if url.port:
|
||||
port = int(url.port)
|
||||
elif port == 80 and ssl:
|
||||
port = 443
|
||||
|
||||
return (server, port, ssl, url.get_request_uri())
|
||||
|
||||
|
||||
def DictionaryToParamList(url_parameters, escape_params=True):
|
||||
"""Convert a dictionary of URL arguments into a URL parameter string.
|
||||
|
||||
This function is deprcated, use atom.url.Url instead.
|
||||
|
||||
Args:
|
||||
url_parameters: The dictionaty of key-value pairs which will be converted
|
||||
into URL parameters. For example,
|
||||
{'dry-run': 'true', 'foo': 'bar'}
|
||||
will become ['dry-run=true', 'foo=bar'].
|
||||
|
||||
Returns:
|
||||
A list which contains a string for each key-value pair. The strings are
|
||||
ready to be incorporated into a URL by using '&'.join([] + parameter_list)
|
||||
"""
|
||||
# Choose which function to use when modifying the query and parameters.
|
||||
# Use quote_plus when escape_params is true.
|
||||
transform_op = [str, urllib.parse.quote_plus][bool(escape_params)]
|
||||
# Create a list of tuples containing the escaped version of the
|
||||
# parameter-value pairs.
|
||||
parameter_tuples = [(transform_op(param), transform_op(value))
|
||||
for param, value in list((url_parameters or {}).items())]
|
||||
# Turn parameter-value tuples into a list of strings in the form
|
||||
# 'PARAMETER=VALUE'.
|
||||
return ['='.join(x) for x in parameter_tuples]
|
||||
|
||||
|
||||
def BuildUri(uri, url_params=None, escape_params=True):
|
||||
"""Converts a uri string and a collection of parameters into a URI.
|
||||
|
||||
This function is deprcated, use atom.url.Url instead.
|
||||
|
||||
Args:
|
||||
uri: string
|
||||
url_params: dict (optional)
|
||||
escape_params: boolean (optional)
|
||||
uri: string The start of the desired URI. This string can alrady contain
|
||||
URL parameters. Examples: '/base/feeds/snippets',
|
||||
'/base/feeds/snippets?bq=digital+camera'
|
||||
url_parameters: dict (optional) Additional URL parameters to be included
|
||||
in the query. These are translated into query arguments
|
||||
in the form '&dict_key=value&...'.
|
||||
Example: {'max-results': '250'} becomes &max-results=250
|
||||
escape_params: boolean (optional) If false, the calling code has already
|
||||
ensured that the query will form a valid URL (all
|
||||
reserved characters have been escaped). If true, this
|
||||
method will escape the query and any URL parameters
|
||||
provided.
|
||||
|
||||
Returns:
|
||||
string The URI consisting of the escaped URL parameters appended to the
|
||||
initial uri string.
|
||||
"""
|
||||
# Prepare URL parameters for inclusion into the GET request.
|
||||
parameter_list = DictionaryToParamList(url_params, escape_params)
|
||||
|
||||
# Append the URL parameters to the URL.
|
||||
if parameter_list:
|
||||
if uri.find('?') != -1:
|
||||
# If there are already URL parameters in the uri string, add the
|
||||
# parameters after a new & character.
|
||||
full_uri = '&'.join([uri] + parameter_list)
|
||||
else:
|
||||
# The uri string did not have any URL parameters (no ? character)
|
||||
# so put a ? between the uri and URL parameters.
|
||||
full_uri = '%s%s' % (uri, '?%s' % ('&'.join([] + parameter_list)))
|
||||
else:
|
||||
full_uri = uri
|
||||
|
||||
return full_uri
|
||||
|
||||
|
||||
def HttpRequest(service, operation, data, uri, extra_headers=None,
|
||||
url_params=None, escape_params=True, content_type='application/atom+xml'):
|
||||
"""Performs an HTTP call to the server, supports GET, POST, PUT, and DELETE.
|
||||
|
||||
This method is deprecated, use atom.http.HttpClient.request instead.
|
||||
|
||||
Usage example, perform and HTTP GET on http://www.google.com/:
|
||||
import atom.service
|
||||
client = atom.service.AtomService()
|
||||
http_response = client.Get('http://www.google.com/')
|
||||
or you could set the client.server to 'www.google.com' and use the
|
||||
following:
|
||||
client.server = 'www.google.com'
|
||||
http_response = client.Get('/')
|
||||
|
||||
Args:
|
||||
service: atom.AtomService object which contains some of the parameters
|
||||
needed to make the request. The following members are used to
|
||||
construct the HTTP call: server (str), additional_headers (dict),
|
||||
port (int), and ssl (bool).
|
||||
operation: str The HTTP operation to be performed. This is usually one of
|
||||
'GET', 'POST', 'PUT', or 'DELETE'
|
||||
data: ElementTree, filestream, list of parts, or other object which can be
|
||||
converted to a string.
|
||||
Should be set to None when performing a GET or PUT.
|
||||
If data is a file-like object which can be read, this method will read
|
||||
a chunk of 100K bytes at a time and send them.
|
||||
If the data is a list of parts to be sent, each part will be evaluated
|
||||
and sent.
|
||||
uri: The beginning of the URL to which the request should be sent.
|
||||
Examples: '/', '/base/feeds/snippets',
|
||||
'/m8/feeds/contacts/default/base'
|
||||
extra_headers: dict of strings. HTTP headers which should be sent
|
||||
in the request. These headers are in addition to those stored in
|
||||
service.additional_headers.
|
||||
url_params: dict of strings. Key value pairs to be added to the URL as
|
||||
URL parameters. For example {'foo':'bar', 'test':'param'} will
|
||||
become ?foo=bar&test=param.
|
||||
escape_params: bool default True. If true, the keys and values in
|
||||
url_params will be URL escaped when the form is constructed
|
||||
(Special characters converted to %XX form.)
|
||||
content_type: str The MIME type for the data being sent. Defaults to
|
||||
'application/atom+xml', this is only used if data is set.
|
||||
"""
|
||||
deprecation('call to deprecated function HttpRequest')
|
||||
full_uri = BuildUri(uri, url_params, escape_params)
|
||||
(connection, full_uri) = PrepareConnection(service, full_uri)
|
||||
|
||||
if extra_headers is None:
|
||||
extra_headers = {}
|
||||
|
||||
# Turn on debug mode if the debug member is set.
|
||||
if service.debug:
|
||||
connection.debuglevel = 1
|
||||
|
||||
connection.putrequest(operation, full_uri)
|
||||
|
||||
# If the list of headers does not include a Content-Length, attempt to
|
||||
# calculate it based on the data object.
|
||||
if (data and 'Content-Length' not in service.additional_headers and
|
||||
'Content-Length' not in extra_headers):
|
||||
content_length = CalculateDataLength(data)
|
||||
if content_length:
|
||||
extra_headers['Content-Length'] = str(content_length)
|
||||
|
||||
if content_type:
|
||||
extra_headers['Content-Type'] = content_type
|
||||
|
||||
# Send the HTTP headers.
|
||||
if isinstance(service.additional_headers, dict):
|
||||
for header in service.additional_headers:
|
||||
connection.putheader(header, service.additional_headers[header])
|
||||
if isinstance(extra_headers, dict):
|
||||
for header in extra_headers:
|
||||
connection.putheader(header, extra_headers[header])
|
||||
connection.endheaders()
|
||||
|
||||
# If there is data, send it in the request.
|
||||
if data:
|
||||
if isinstance(data, list):
|
||||
for data_part in data:
|
||||
__SendDataPart(data_part, connection)
|
||||
else:
|
||||
__SendDataPart(data, connection)
|
||||
|
||||
# Return the HTTP Response from the server.
|
||||
return connection.getresponse()
|
||||
|
||||
|
||||
def __SendDataPart(data, connection):
|
||||
"""This method is deprecated, use atom.http._send_data_part"""
|
||||
deprecated('call to deprecated function __SendDataPart')
|
||||
if isinstance(data, str):
|
||||
# TODO add handling for unicode.
|
||||
connection.send(data)
|
||||
return
|
||||
elif ElementTree.iselement(data):
|
||||
connection.send(ElementTree.tostring(data))
|
||||
return
|
||||
# Check to see if data is a file-like object that has a read method.
|
||||
elif hasattr(data, 'read'):
|
||||
# Read the file and send it a chunk at a time.
|
||||
while 1:
|
||||
binarydata = data.read(100000)
|
||||
if binarydata == '': break
|
||||
connection.send(binarydata)
|
||||
return
|
||||
else:
|
||||
# The data object was not a file.
|
||||
# Try to convert to a string and send the data.
|
||||
connection.send(str(data))
|
||||
return
|
||||
|
||||
|
||||
def CalculateDataLength(data):
|
||||
"""Attempts to determine the length of the data to send.
|
||||
|
||||
This method will respond with a length only if the data is a string or
|
||||
and ElementTree element.
|
||||
|
||||
Args:
|
||||
data: object If this is not a string or ElementTree element this funtion
|
||||
will return None.
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
return len(data)
|
||||
elif isinstance(data, list):
|
||||
return None
|
||||
elif ElementTree.iselement(data):
|
||||
return len(ElementTree.tostring(data))
|
||||
elif hasattr(data, 'read'):
|
||||
# If this is a file-like object, don't try to guess the length.
|
||||
return None
|
||||
else:
|
||||
return len(str(data).encode('utf-8'))
|
||||
|
||||
|
||||
def deprecation(message):
|
||||
warnings.warn(message, DeprecationWarning, stacklevel=2)
|
||||
@@ -1,105 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
"""This module provides a TokenStore class which is designed to manage
|
||||
auth tokens required for different services.
|
||||
|
||||
Each token is valid for a set of scopes which is the start of a URL. An HTTP
|
||||
client will use a token store to find a valid Authorization header to send
|
||||
in requests to the specified URL. If the HTTP client determines that a token
|
||||
has expired or been revoked, it can remove the token from the store so that
|
||||
it will not be used in future requests.
|
||||
"""
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import atom.http_interface
|
||||
import atom.url
|
||||
|
||||
SCOPE_ALL = 'http'
|
||||
|
||||
|
||||
class TokenStore(object):
|
||||
"""Manages Authorization tokens which will be sent in HTTP headers."""
|
||||
|
||||
def __init__(self, scoped_tokens=None):
|
||||
self._tokens = scoped_tokens or {}
|
||||
|
||||
def add_token(self, token):
|
||||
"""Adds a new token to the store (replaces tokens with the same scope).
|
||||
|
||||
Args:
|
||||
token: A subclass of http_interface.GenericToken. The token object is
|
||||
responsible for adding the Authorization header to the HTTP request.
|
||||
The scopes defined in the token are used to determine if the token
|
||||
is valid for a requested scope when find_token is called.
|
||||
|
||||
Returns:
|
||||
True if the token was added, False if the token was not added becase
|
||||
no scopes were provided.
|
||||
"""
|
||||
if not hasattr(token, 'scopes') or not token.scopes:
|
||||
return False
|
||||
|
||||
for scope in token.scopes:
|
||||
self._tokens[str(scope)] = token
|
||||
return True
|
||||
|
||||
def find_token(self, url):
|
||||
"""Selects an Authorization header token which can be used for the URL.
|
||||
|
||||
Args:
|
||||
url: str or atom.url.Url or a list containing the same.
|
||||
The URL which is going to be requested. All
|
||||
tokens are examined to see if any scopes begin match the beginning
|
||||
of the URL. The first match found is returned.
|
||||
|
||||
Returns:
|
||||
The token object which should execute the HTTP request. If there was
|
||||
no token for the url (the url did not begin with any of the token
|
||||
scopes available), then the atom.http_interface.GenericToken will be
|
||||
returned because the GenericToken calls through to the http client
|
||||
without adding an Authorization header.
|
||||
"""
|
||||
if url is None:
|
||||
return None
|
||||
if isinstance(url, str):
|
||||
url = atom.url.parse_url(url)
|
||||
if url in self._tokens:
|
||||
token = self._tokens[url]
|
||||
if token.valid_for_scope(url):
|
||||
return token
|
||||
else:
|
||||
del self._tokens[url]
|
||||
for scope, token in self._tokens.items():
|
||||
if token.valid_for_scope(url):
|
||||
return token
|
||||
return atom.http_interface.GenericToken()
|
||||
|
||||
def remove_token(self, token):
|
||||
"""Removes the token from the token_store.
|
||||
|
||||
This method is used when a token is determined to be invalid. If the
|
||||
token was found by find_token, but resulted in a 401 or 403 error stating
|
||||
that the token was invlid, then the token should be removed to prevent
|
||||
future use.
|
||||
|
||||
Returns:
|
||||
True if a token was found and then removed from the token
|
||||
store. False if the token was not in the TokenStore.
|
||||
"""
|
||||
token_found = False
|
||||
scopes_to_delete = []
|
||||
for scope, stored_token in self._tokens.items():
|
||||
if stored_token == token:
|
||||
scopes_to_delete.append(scope)
|
||||
token_found = True
|
||||
for scope in scopes_to_delete:
|
||||
del self._tokens[scope]
|
||||
return token_found
|
||||
|
||||
def remove_all_tokens(self):
|
||||
self._tokens = {}
|
||||
@@ -1,130 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2008 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License 2.0;
|
||||
|
||||
|
||||
|
||||
# __author__ = 'api.jscudder (Jeff Scudder)'
|
||||
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
DEFAULT_PROTOCOL = 'http'
|
||||
DEFAULT_PORT = 80
|
||||
|
||||
|
||||
def parse_url(url_string):
|
||||
"""Creates a Url object which corresponds to the URL string.
|
||||
|
||||
This method can accept partial URLs, but it will leave missing
|
||||
members of the Url unset.
|
||||
"""
|
||||
parts = urllib.parse.urlparse(url_string)
|
||||
url = Url()
|
||||
if parts[0]:
|
||||
url.protocol = parts[0]
|
||||
if parts[1]:
|
||||
host_parts = parts[1].split(':')
|
||||
if host_parts[0]:
|
||||
url.host = host_parts[0]
|
||||
if len(host_parts) > 1:
|
||||
url.port = host_parts[1]
|
||||
if parts[2]:
|
||||
url.path = parts[2]
|
||||
if parts[4]:
|
||||
param_pairs = parts[4].split('&')
|
||||
for pair in param_pairs:
|
||||
pair_parts = pair.split('=')
|
||||
if len(pair_parts) > 1:
|
||||
url.params[urllib.parse.unquote_plus(pair_parts[0])] = (
|
||||
urllib.parse.unquote_plus(pair_parts[1]))
|
||||
elif len(pair_parts) == 1:
|
||||
url.params[urllib.parse.unquote_plus(pair_parts[0])] = None
|
||||
return url
|
||||
|
||||
|
||||
class Url(object):
|
||||
"""Represents a URL and implements comparison logic.
|
||||
|
||||
URL strings which are not identical can still be equivalent, so this object
|
||||
provides a better interface for comparing and manipulating URLs than
|
||||
strings. URL parameters are represented as a dictionary of strings, and
|
||||
defaults are used for the protocol (http) and port (80) if not provided.
|
||||
"""
|
||||
|
||||
def __init__(self, protocol=None, host=None, port=None, path=None,
|
||||
params=None):
|
||||
self.protocol = protocol
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.path = path
|
||||
self.params = params or {}
|
||||
|
||||
def to_string(self):
|
||||
url_parts = ['', '', '', '', '', '']
|
||||
if self.protocol:
|
||||
url_parts[0] = self.protocol
|
||||
if self.host:
|
||||
if self.port:
|
||||
url_parts[1] = ':'.join((self.host, str(self.port)))
|
||||
else:
|
||||
url_parts[1] = self.host
|
||||
if self.path:
|
||||
url_parts[2] = self.path
|
||||
if self.params:
|
||||
url_parts[4] = self.get_param_string()
|
||||
return urllib.parse.urlunparse(url_parts)
|
||||
|
||||
def get_param_string(self):
|
||||
param_pairs = []
|
||||
for key, value in self.params.items():
|
||||
param_pairs.append('='.join((urllib.parse.quote_plus(key),
|
||||
urllib.parse.quote_plus(str(value)))))
|
||||
return '&'.join(param_pairs)
|
||||
|
||||
def get_request_uri(self):
|
||||
"""Returns the path with the parameters escaped and appended."""
|
||||
param_string = self.get_param_string()
|
||||
if param_string:
|
||||
return '?'.join([self.path, param_string])
|
||||
else:
|
||||
return self.path
|
||||
|
||||
def __cmp__(self, other):
|
||||
if not isinstance(other, Url):
|
||||
return cmp(self.to_string(), str(other))
|
||||
difference = 0
|
||||
# Compare the protocol
|
||||
if self.protocol and other.protocol:
|
||||
difference = cmp(self.protocol, other.protocol)
|
||||
elif self.protocol and not other.protocol:
|
||||
difference = cmp(self.protocol, DEFAULT_PROTOCOL)
|
||||
elif not self.protocol and other.protocol:
|
||||
difference = cmp(DEFAULT_PROTOCOL, other.protocol)
|
||||
if difference != 0:
|
||||
return difference
|
||||
# Compare the host
|
||||
difference = cmp(self.host, other.host)
|
||||
if difference != 0:
|
||||
return difference
|
||||
# Compare the port
|
||||
if self.port and other.port:
|
||||
difference = cmp(self.port, other.port)
|
||||
elif self.port and not other.port:
|
||||
difference = cmp(self.port, DEFAULT_PORT)
|
||||
elif not self.port and other.port:
|
||||
difference = cmp(DEFAULT_PORT, other.port)
|
||||
if difference != 0:
|
||||
return difference
|
||||
# Compare the path
|
||||
difference = cmp(self.path, other.path)
|
||||
if difference != 0:
|
||||
return difference
|
||||
# Compare the parameters
|
||||
return cmp(self.params, other.params)
|
||||
|
||||
def __str__(self):
|
||||
return self.to_string()
|
||||
@@ -1,158 +1,34 @@
|
||||
"""GAM audit monitor commands (GDATA).
|
||||
"""GAM audit monitor commands — REMOVED.
|
||||
|
||||
Mailbox monitor creation/deletion/listing and the doWhatIs command.
|
||||
The Email Audit API has been deprecated by Google. These commands
|
||||
now print a deprecation notice and exit.
|
||||
"""
|
||||
|
||||
from gam.var import Act
|
||||
from gam.util.args import getChoice
|
||||
from gam.util.output import systemErrorExit
|
||||
from gam.constants import CMD_ACTION, CMD_FUNCTION, USAGE_ERROR_RC
|
||||
|
||||
from gamlib import state as GM
|
||||
from gam.var import Act, Cmd, Ent, Ind
|
||||
from gam.util.access import entityUnknownWarning
|
||||
from gam.util.api import getEmailAuditObject
|
||||
from gam.util.api_call import callGData
|
||||
from gam.util.args import (
|
||||
YYYYMMDD_HHMM_FORMAT,
|
||||
checkForExtraneousArguments,
|
||||
getArgument,
|
||||
getEmailAddress,
|
||||
getString,
|
||||
getYYYYMMDD_HHMM,
|
||||
normalizeEmailAddressOrUID,
|
||||
splitEmailAddress,
|
||||
)
|
||||
from gam.util.display import (
|
||||
entityActionFailedWarning,
|
||||
entityActionPerformed,
|
||||
entityPerformActionNumItems,
|
||||
printKeyValueList,
|
||||
printKeyValueListWithCount,
|
||||
)
|
||||
from gam.util.errors import invalidArgumentExit, unknownArgumentExit
|
||||
from gam.util.output import setSysExitRC
|
||||
from gam.constants import NO_ENTITIES_FOUND_RC
|
||||
LAST_SUPPORTED_VERSION = '7.46.07'
|
||||
|
||||
def _deprecatedCommand(api_name):
|
||||
systemErrorExit(
|
||||
USAGE_ERROR_RC,
|
||||
f'GAM no longer supports the legacy {api_name} API and this command. '
|
||||
f'If you must use this API you can install a copy of GAM {LAST_SUPPORTED_VERSION} '
|
||||
f'which is the last version to support this command.'
|
||||
)
|
||||
|
||||
def getAuditParameters(emailAddressRequired=True, requestIdRequired=True, destUserRequired=False):
|
||||
auditObject = getEmailAuditObject()
|
||||
emailAddress = getEmailAddress(noUid=True, optional=not emailAddressRequired)
|
||||
parameters = {}
|
||||
if emailAddress:
|
||||
parameters['auditUser'] = emailAddress
|
||||
parameters['auditUserName'], auditObject.domain = splitEmailAddress(emailAddress)
|
||||
if requestIdRequired:
|
||||
parameters['requestId'] = getString(Cmd.OB_REQUEST_ID)
|
||||
if destUserRequired:
|
||||
destEmailAddress = getEmailAddress(noUid=True)
|
||||
parameters['auditDestUser'] = destEmailAddress
|
||||
parameters['auditDestUserName'], destDomain = splitEmailAddress(destEmailAddress)
|
||||
if auditObject.domain != destDomain:
|
||||
Cmd.Backup()
|
||||
invalidArgumentExit(f'{parameters["auditDestUserName"]}@{auditObject.domain}')
|
||||
return (auditObject, parameters)
|
||||
|
||||
# Audit monitor command utilities
|
||||
def _showMailboxMonitorRequestStatus(request, i=0, count=0):
|
||||
printKeyValueListWithCount(['Destination', normalizeEmailAddressOrUID(request['destUserName'])], i, count)
|
||||
Ind.Increment()
|
||||
printKeyValueList(['Begin', request.get('beginDate', 'immediately')])
|
||||
printKeyValueList(['End', request['endDate']])
|
||||
printKeyValueList(['Monitor Incoming', request['outgoingEmailMonitorLevel']])
|
||||
printKeyValueList(['Monitor Outgoing', request['incomingEmailMonitorLevel']])
|
||||
printKeyValueList(['Monitor Chats', request.get('chatMonitorLevel', 'NONE')])
|
||||
printKeyValueList(['Monitor Drafts', request.get('draftMonitorLevel', 'NONE')])
|
||||
Ind.Decrement()
|
||||
|
||||
# gam audit monitor create <EmailAddress> <DestEmailAddress> [begin <DateTime>] [end <DateTime>] [incoming_headers] [outgoing_headers] [nochats] [nodrafts] [chat_headers] [draft_headers]
|
||||
def doCreateMonitor():
|
||||
auditObject, parameters = getAuditParameters(emailAddressRequired=True, requestIdRequired=False, destUserRequired=True)
|
||||
#end_date defaults to 30 days in the future...
|
||||
end_date = GM.Globals[GM.DATETIME_NOW].shift(days=30).strftime(YYYYMMDD_HHMM_FORMAT)
|
||||
begin_date = None
|
||||
incoming_headers_only = outgoing_headers_only = drafts_headers_only = chats_headers_only = False
|
||||
drafts = chats = True
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'begin':
|
||||
begin_date = getYYYYMMDD_HHMM()
|
||||
elif myarg == 'end':
|
||||
end_date = getYYYYMMDD_HHMM()
|
||||
elif myarg == 'incomingheaders':
|
||||
incoming_headers_only = True
|
||||
elif myarg == 'outgoingheaders':
|
||||
outgoing_headers_only = True
|
||||
elif myarg == 'nochats':
|
||||
chats = False
|
||||
elif myarg == 'nodrafts':
|
||||
drafts = False
|
||||
elif myarg == 'chatheaders':
|
||||
chats_headers_only = True
|
||||
elif myarg == 'draftheaders':
|
||||
drafts_headers_only = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
try:
|
||||
request = callGData(auditObject, 'createEmailMonitor',
|
||||
throwErrors=[GDATA.INVALID_VALUE, GDATA.INVALID_INPUT, GDATA.DOES_NOT_EXIST, GDATA.INVALID_DOMAIN],
|
||||
source_user=parameters['auditUserName'], destination_user=parameters['auditDestUserName'], end_date=end_date, begin_date=begin_date,
|
||||
incoming_headers_only=incoming_headers_only, outgoing_headers_only=outgoing_headers_only,
|
||||
drafts=drafts, drafts_headers_only=drafts_headers_only, chats=chats, chats_headers_only=chats_headers_only)
|
||||
entityActionPerformed([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None])
|
||||
Ind.Increment()
|
||||
_showMailboxMonitorRequestStatus(request)
|
||||
Ind.Decrement()
|
||||
except (GDATA.invalidValue, GDATA.invalidInput) as e:
|
||||
entityActionFailedWarning([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None], str(e))
|
||||
except (GDATA.doesNotExist, GDATA.invalidDomain) as e:
|
||||
if str(e).find(parameters['auditUser']) != -1:
|
||||
entityUnknownWarning(Ent.USER, parameters['auditUser'])
|
||||
else:
|
||||
entityActionFailedWarning([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None], str(e))
|
||||
_deprecatedCommand('Email Audit')
|
||||
|
||||
# gam audit monitor delete <EmailAddress> <DestEmailAddress>
|
||||
def doDeleteMonitor():
|
||||
auditObject, parameters = getAuditParameters(emailAddressRequired=True, requestIdRequired=False, destUserRequired=True)
|
||||
checkForExtraneousArguments()
|
||||
try:
|
||||
callGData(auditObject, 'deleteEmailMonitor',
|
||||
throwErrors=[GDATA.INVALID_INPUT, GDATA.DOES_NOT_EXIST, GDATA.INVALID_DOMAIN],
|
||||
source_user=parameters['auditUserName'], destination_user=parameters['auditDestUserName'])
|
||||
entityActionPerformed([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, parameters['auditDestUser']])
|
||||
except GDATA.invalidInput as e:
|
||||
entityActionFailedWarning([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None], str(e))
|
||||
except (GDATA.doesNotExist, GDATA.invalidDomain) as e:
|
||||
if str(e).find(parameters['auditUser']) != -1:
|
||||
entityUnknownWarning(Ent.USER, parameters['auditUser'])
|
||||
else:
|
||||
entityActionFailedWarning([Ent.USER, parameters['auditUser'], Ent.AUDIT_MONITOR_REQUEST, None], str(e))
|
||||
_deprecatedCommand('Email Audit')
|
||||
|
||||
# gam audit monitor list <EmailAddress>
|
||||
def doShowMonitors():
|
||||
auditObject, parameters = getAuditParameters(emailAddressRequired=True, requestIdRequired=False, destUserRequired=False)
|
||||
checkForExtraneousArguments()
|
||||
try:
|
||||
results = callGData(auditObject, 'getEmailMonitors',
|
||||
throwErrors=[GDATA.DOES_NOT_EXIST, GDATA.INVALID_DOMAIN],
|
||||
user=parameters['auditUserName'])
|
||||
jcount = len(results) if (results) else 0
|
||||
entityPerformActionNumItems([Ent.USER, parameters['auditUser']], jcount, Ent.AUDIT_MONITOR_REQUEST)
|
||||
if jcount == 0:
|
||||
setSysExitRC(NO_ENTITIES_FOUND_RC)
|
||||
return
|
||||
Ind.Increment()
|
||||
j = 0
|
||||
for request in results:
|
||||
j += 1
|
||||
_showMailboxMonitorRequestStatus(request, j, jcount)
|
||||
Ind.Decrement()
|
||||
except (GDATA.doesNotExist, GDATA.invalidDomain):
|
||||
entityUnknownWarning(Ent.USER, parameters['auditUser'])
|
||||
|
||||
# gam whatis <EmailItem> [noinfo] [noinvitablecheck]
|
||||
_deprecatedCommand('Email Audit')
|
||||
|
||||
# Dispatch tables and routing (moved from __init__.py)
|
||||
# Additional imports for dispatch
|
||||
from gam.util.args import getChoice
|
||||
from gam.constants import CMD_ACTION, CMD_FUNCTION
|
||||
|
||||
AUDIT_SUBCOMMANDS_WITH_OBJECTS = {
|
||||
'monitor':
|
||||
{'create': (Act.CREATE, doCreateMonitor),
|
||||
@@ -166,4 +42,3 @@ def processAuditCommands():
|
||||
CL_objectName = getChoice(AUDIT_SUBCOMMANDS_WITH_OBJECTS[CL_subCommand])
|
||||
Act.Set(AUDIT_SUBCOMMANDS_WITH_OBJECTS[CL_subCommand][CL_objectName][CMD_ACTION])
|
||||
AUDIT_SUBCOMMANDS_WITH_OBJECTS[CL_subCommand][CL_objectName][CMD_FUNCTION]()
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,6 @@ CLOUDIDENTITY_POLICY = 'cloudidentitypolicy'
|
||||
CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations'
|
||||
CLOUDRESOURCEMANAGER = 'cloudresourcemanager'
|
||||
CLOUDRESOURCEMANAGERV1 = 'cloudresourcemanagerv1'
|
||||
CONTACTS = 'contacts'
|
||||
CONTACTDELEGATION = 'contactdelegation'
|
||||
DATATRANSFER = 'datatransfer'
|
||||
DIRECTORY = 'directory'
|
||||
@@ -66,7 +65,6 @@ DRIVEACTIVITY = 'driveactivity'
|
||||
DRIVELABELS = 'drivelabels'
|
||||
DRIVELABELS_ADMIN = 'drivelabelsadmin'
|
||||
DRIVELABELS_USER = 'drivelabelsuser'
|
||||
EMAIL_AUDIT = 'email-audit'
|
||||
FORMS = 'forms'
|
||||
GMAIL = 'gmail'
|
||||
GROUPSMIGRATION = 'groupsmigration'
|
||||
@@ -263,7 +261,6 @@ _INFO = {
|
||||
CLOUDIDENTITY_USERINVITATIONS: {'name': 'Cloud Identity API - User Invitations', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDRESOURCEMANAGER: {'name': 'Resource Manager API v3', 'version': 'v3', 'v2discovery': True},
|
||||
CLOUDRESOURCEMANAGERV1: {'name': 'Resource Manager API v1', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudresourcemanager'},
|
||||
CONTACTS: {'name': 'Contacts API', 'version': 'v3', 'v2discovery': False},
|
||||
CONTACTDELEGATION: {'name': 'Contact Delegation API', 'version': 'v1', 'v2discovery': True, 'localjson': True},
|
||||
DATATRANSFER: {'name': 'Data Transfer API', 'version': 'datatransfer_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
DIRECTORY: {'name': 'Directory API', 'version': 'directory_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
@@ -274,7 +271,6 @@ _INFO = {
|
||||
DRIVEACTIVITY: {'name': 'Drive Activity API v2', 'version': 'v2', 'v2discovery': True},
|
||||
DRIVELABELS_ADMIN: {'name': 'Drive Labels API - Admin', 'version': 'v2', 'v2discovery': True, 'mappedAPI': DRIVELABELS},
|
||||
DRIVELABELS_USER: {'name': 'Drive Labels API - User', 'version': 'v2', 'v2discovery': True, 'mappedAPI': DRIVELABELS},
|
||||
EMAIL_AUDIT: {'name': 'Email Audit API', 'version': 'v1', 'v2discovery': False},
|
||||
FORMS: {'name': 'Forms API', 'version': 'v1', 'v2discovery': True},
|
||||
GMAIL: {'name': 'Gmail API', 'version': 'v1', 'v2discovery': True},
|
||||
GROUPSMIGRATION: {'name': 'Groups Migration API', 'version': 'v1', 'v2discovery': True},
|
||||
@@ -415,9 +411,6 @@ _CLIENT_SCOPES = [
|
||||
'api': STORAGEWRITE,
|
||||
'offByDefault': True,
|
||||
'scope': STORAGE_READWRITE_SCOPE},
|
||||
{'name': 'Contacts API - Domain Shared Contacts',
|
||||
'api': CONTACTS,
|
||||
'scope': 'https://www.google.com/m8/feeds'},
|
||||
{'name': 'Contact Delegation API',
|
||||
'api': CONTACTDELEGATION,
|
||||
'subscopes': READONLY,
|
||||
@@ -469,10 +462,6 @@ _CLIENT_SCOPES = [
|
||||
'api': DIRECTORY,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/admin.directory.user'},
|
||||
{'name': 'Email Audit API',
|
||||
'api': EMAIL_AUDIT,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://apps-apis.google.com/a/feeds/compliance/audit/'},
|
||||
{'name': 'Groups Migration API',
|
||||
'api': GROUPSMIGRATION,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.groups.migration'},
|
||||
@@ -637,11 +626,7 @@ _SVCACCT_SCOPES = [
|
||||
# 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
|
||||
# {'name': 'Cloud Identity User Invitations API',
|
||||
# 'api': CLOUDIDENTITY_USERINVITATIONS,
|
||||
# 'subscopes': READONLY,
|
||||
# 'scope': 'https://www.googleapis.com/auth/cloud-identity'},
|
||||
# {'name': 'Contacts API - Users',
|
||||
# 'api': CONTACTS,
|
||||
# 'scope': 'https://www.google.com/m8/feeds'},
|
||||
{'name': 'Drive API',
|
||||
'api': DRIVE3,
|
||||
'subscopes': READONLY,
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2023 Ross Scroggs All Rights Reserved.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""GAM GData resources
|
||||
|
||||
"""
|
||||
API_DEPRECATED_MSG = 'Contacts API is being deprecated.'
|
||||
|
||||
# callGData throw errors
|
||||
API_DEPRECATED = 612
|
||||
BAD_GATEWAY = 601
|
||||
BAD_REQUEST = 602
|
||||
DOES_NOT_EXIST = 1301
|
||||
ENTITY_EXISTS = 1300
|
||||
FORBIDDEN = 603
|
||||
GATEWAY_TIMEOUT = 612
|
||||
INSUFFICIENT_PERMISSIONS = 604
|
||||
INTERNAL_SERVER_ERROR = 1000
|
||||
INVALID_DOMAIN = 605
|
||||
INVALID_INPUT = 1317
|
||||
INVALID_VALUE = 1801
|
||||
NAME_NOT_VALID = 1303
|
||||
NOT_FOUND = 606
|
||||
NOT_IMPLEMENTED = 607
|
||||
PRECONDITION_FAILED = 608
|
||||
QUOTA_EXCEEDED = 609
|
||||
SERVICE_NOT_APPLICABLE = 1410
|
||||
SERVICE_UNAVAILABLE = 610
|
||||
TOKEN_EXPIRED = 611
|
||||
TOKEN_INVALID = 403
|
||||
UNKNOWN_ERROR = 600
|
||||
#
|
||||
NON_TERMINATING_ERRORS = [API_DEPRECATED, BAD_GATEWAY, GATEWAY_TIMEOUT, QUOTA_EXCEEDED, SERVICE_UNAVAILABLE, TOKEN_EXPIRED]
|
||||
EMAILSETTINGS_THROW_LIST = [INVALID_DOMAIN, DOES_NOT_EXIST, SERVICE_NOT_APPLICABLE, BAD_REQUEST, NAME_NOT_VALID, INTERNAL_SERVER_ERROR, INVALID_VALUE]
|
||||
#
|
||||
class apiDeprecated(Exception):
|
||||
pass
|
||||
class badRequest(Exception):
|
||||
pass
|
||||
class doesNotExist(Exception):
|
||||
pass
|
||||
class entityExists(Exception):
|
||||
pass
|
||||
class forbidden(Exception):
|
||||
pass
|
||||
class insufficientPermissions(Exception):
|
||||
pass
|
||||
class internalServerError(Exception):
|
||||
pass
|
||||
class invalidDomain(Exception):
|
||||
pass
|
||||
class invalidInput(Exception):
|
||||
pass
|
||||
class invalidValue(Exception):
|
||||
pass
|
||||
class nameNotValid(Exception):
|
||||
pass
|
||||
class notFound(Exception):
|
||||
pass
|
||||
class notImplemented(Exception):
|
||||
pass
|
||||
class preconditionFailed(Exception):
|
||||
pass
|
||||
class serviceNotApplicable(Exception):
|
||||
pass
|
||||
|
||||
ERROR_CODE_EXCEPTION_MAP = {
|
||||
API_DEPRECATED: apiDeprecated,
|
||||
BAD_REQUEST: badRequest,
|
||||
DOES_NOT_EXIST: doesNotExist,
|
||||
ENTITY_EXISTS: entityExists,
|
||||
FORBIDDEN: forbidden,
|
||||
INSUFFICIENT_PERMISSIONS: insufficientPermissions,
|
||||
INTERNAL_SERVER_ERROR: internalServerError,
|
||||
INVALID_DOMAIN: invalidDomain,
|
||||
INVALID_INPUT: invalidInput,
|
||||
INVALID_VALUE: invalidValue,
|
||||
NAME_NOT_VALID: nameNotValid,
|
||||
NOT_FOUND: notFound,
|
||||
NOT_IMPLEMENTED: notImplemented,
|
||||
PRECONDITION_FAILED: preconditionFailed,
|
||||
SERVICE_NOT_APPLICABLE: serviceNotApplicable,
|
||||
}
|
||||
@@ -1,825 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2006 Google Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
"""Contains classes representing Google Data elements.
|
||||
|
||||
Extends Atom classes to add Google Data specific elements.
|
||||
"""
|
||||
|
||||
|
||||
__author__ = 'j.s@google.com (Jeffrey Scudder)'
|
||||
|
||||
import os
|
||||
import atom
|
||||
import lxml.etree as ElementTree
|
||||
|
||||
# XML namespaces which are often used in GData entities.
|
||||
GDATA_NAMESPACE = 'http://schemas.google.com/g/2005'
|
||||
GDATA_TEMPLATE = '{http://schemas.google.com/g/2005}%s'
|
||||
OPENSEARCH_NAMESPACE = 'http://a9.com/-/spec/opensearchrss/1.0/'
|
||||
OPENSEARCH_TEMPLATE = '{http://a9.com/-/spec/opensearchrss/1.0/}%s'
|
||||
BATCH_NAMESPACE = 'http://schemas.google.com/gdata/batch'
|
||||
GACL_NAMESPACE = 'http://schemas.google.com/acl/2007'
|
||||
GACL_TEMPLATE = '{http://schemas.google.com/acl/2007}%s'
|
||||
|
||||
|
||||
# Labels used in batch request entries to specify the desired CRUD operation.
|
||||
BATCH_INSERT = 'insert'
|
||||
BATCH_UPDATE = 'update'
|
||||
BATCH_DELETE = 'delete'
|
||||
BATCH_QUERY = 'query'
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MissingRequiredParameters(Error):
|
||||
pass
|
||||
|
||||
|
||||
class MediaSource(object):
|
||||
"""GData Entries can refer to media sources, so this class provides a
|
||||
place to store references to these objects along with some metadata.
|
||||
"""
|
||||
|
||||
def __init__(self, file_handle=None, content_type=None, content_length=None,
|
||||
file_path=None, file_name=None):
|
||||
"""Creates an object of type MediaSource.
|
||||
|
||||
Args:
|
||||
file_handle: A file handle pointing to the file to be encapsulated in the
|
||||
MediaSource
|
||||
content_type: string The MIME type of the file. Required if a file_handle
|
||||
is given.
|
||||
content_length: int The size of the file. Required if a file_handle is
|
||||
given.
|
||||
file_path: string (optional) A full path name to the file. Used in
|
||||
place of a file_handle.
|
||||
file_name: string The name of the file without any path information.
|
||||
Required if a file_handle is given.
|
||||
"""
|
||||
self.file_handle = file_handle
|
||||
self.content_type = content_type
|
||||
self.content_length = content_length
|
||||
self.file_name = file_name
|
||||
|
||||
if (file_handle is None and content_type is not None and
|
||||
file_path is not None):
|
||||
self.setFile(file_path, content_type)
|
||||
|
||||
def setFile(self, file_name, content_type):
|
||||
"""A helper function which can create a file handle from a given filename
|
||||
and set the content type and length all at once.
|
||||
|
||||
Args:
|
||||
file_name: string The path and file name to the file containing the media
|
||||
content_type: string A MIME type representing the type of the media
|
||||
"""
|
||||
|
||||
self.file_handle = open(file_name, 'rb')
|
||||
self.content_type = content_type
|
||||
self.content_length = os.path.getsize(file_name)
|
||||
self.file_name = os.path.basename(file_name)
|
||||
|
||||
|
||||
class LinkFinder(atom.LinkFinder):
|
||||
"""An "interface" providing methods to find link elements
|
||||
|
||||
GData Entry elements often contain multiple links which differ in the rel
|
||||
attribute or content type. Often, developers are interested in a specific
|
||||
type of link so this class provides methods to find specific classes of
|
||||
links.
|
||||
|
||||
This class is used as a mixin in GData entries.
|
||||
"""
|
||||
|
||||
def GetSelfLink(self):
|
||||
"""Find the first link with rel set to 'self'
|
||||
|
||||
Returns:
|
||||
An atom.Link or none if none of the links had rel equal to 'self'
|
||||
"""
|
||||
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'self':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetEditLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'edit':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetEditMediaLink(self):
|
||||
"""The Picasa API mistakenly returns media-edit rather than edit-media, but
|
||||
this may change soon.
|
||||
"""
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'edit-media':
|
||||
return a_link
|
||||
if a_link.rel == 'media-edit':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetHtmlLink(self):
|
||||
"""Find the first link with rel of alternate and type of text/html
|
||||
|
||||
Returns:
|
||||
An atom.Link or None if no links matched
|
||||
"""
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'alternate' and a_link.type == 'text/html':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetPostLink(self):
|
||||
"""Get a link containing the POST target URL.
|
||||
|
||||
The POST target URL is used to insert new entries.
|
||||
|
||||
Returns:
|
||||
A link object with a rel matching the POST type.
|
||||
"""
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'http://schemas.google.com/g/2005#post':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetAclLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'http://schemas.google.com/acl/2007#accessControlList':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetFeedLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'http://schemas.google.com/g/2005#feed':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetNextLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'next':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetPrevLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == 'previous':
|
||||
return a_link
|
||||
return None
|
||||
|
||||
|
||||
class TotalResults(atom.AtomBase):
|
||||
"""opensearch:TotalResults for a GData feed"""
|
||||
|
||||
_tag = 'totalResults'
|
||||
_namespace = OPENSEARCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
def __init__(self, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def TotalResultsFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(TotalResults, xml_string)
|
||||
|
||||
|
||||
class StartIndex(atom.AtomBase):
|
||||
"""The opensearch:startIndex element in GData feed"""
|
||||
|
||||
_tag = 'startIndex'
|
||||
_namespace = OPENSEARCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
def __init__(self, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def StartIndexFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(StartIndex, xml_string)
|
||||
|
||||
|
||||
class ItemsPerPage(atom.AtomBase):
|
||||
"""The opensearch:itemsPerPage element in GData feed"""
|
||||
|
||||
_tag = 'itemsPerPage'
|
||||
_namespace = OPENSEARCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
def __init__(self, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def ItemsPerPageFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ItemsPerPage, xml_string)
|
||||
|
||||
|
||||
class ExtendedProperty(atom.AtomBase):
|
||||
"""The Google Data extendedProperty element.
|
||||
|
||||
Used to store arbitrary key-value information specific to your
|
||||
application. The value can either be a text string stored as an XML
|
||||
attribute (.value), or an XML node (XmlBlob) as a child element.
|
||||
|
||||
This element is used in the Google Calendar data API and the Google
|
||||
Contacts data API.
|
||||
"""
|
||||
|
||||
_tag = 'extendedProperty'
|
||||
_namespace = GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['name'] = 'name'
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, name=None, value=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
def GetXmlBlobExtensionElement(self):
|
||||
"""Returns the XML blob as an atom.ExtensionElement.
|
||||
|
||||
Returns:
|
||||
An atom.ExtensionElement representing the blob's XML, or None if no
|
||||
blob was set.
|
||||
"""
|
||||
if len(self.extension_elements) < 1:
|
||||
return None
|
||||
else:
|
||||
return self.extension_elements[0]
|
||||
|
||||
def GetXmlBlobString(self):
|
||||
"""Returns the XML blob as a string.
|
||||
|
||||
Returns:
|
||||
A string containing the blob's XML, or None if no blob was set.
|
||||
"""
|
||||
blob = self.GetXmlBlobExtensionElement()
|
||||
if blob:
|
||||
return blob.ToString()
|
||||
return None
|
||||
|
||||
def SetXmlBlob(self, blob):
|
||||
"""Sets the contents of the extendedProperty to XML as a child node.
|
||||
|
||||
Since the extendedProperty is only allowed one child element as an XML
|
||||
blob, setting the XML blob will erase any preexisting extension elements
|
||||
in this object.
|
||||
|
||||
Args:
|
||||
blob: str, ElementTree Element or atom.ExtensionElement representing
|
||||
the XML blob stored in the extendedProperty.
|
||||
"""
|
||||
# Erase any existing extension_elements, clears the child nodes from the
|
||||
# extendedProperty.
|
||||
self.extension_elements = []
|
||||
if isinstance(blob, atom.ExtensionElement):
|
||||
self.extension_elements.append(blob)
|
||||
elif ElementTree.iselement(blob):
|
||||
self.extension_elements.append(atom._ExtensionElementFromElementTree(
|
||||
blob))
|
||||
else:
|
||||
self.extension_elements.append(atom.ExtensionElementFromString(blob))
|
||||
|
||||
|
||||
def ExtendedPropertyFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ExtendedProperty, xml_string)
|
||||
|
||||
|
||||
class GDataEntry(atom.Entry, LinkFinder):
|
||||
"""Extends Atom Entry to provide data processing"""
|
||||
|
||||
_tag = atom.Entry._tag
|
||||
_namespace = atom.Entry._namespace
|
||||
_children = atom.Entry._children.copy()
|
||||
_attributes = atom.Entry._attributes.copy()
|
||||
|
||||
def __GetId(self):
|
||||
return self.__id
|
||||
|
||||
# This method was created to strip the unwanted whitespace from the id's
|
||||
# text node.
|
||||
def __SetId(self, id):
|
||||
self.__id = id
|
||||
if id is not None and id.text is not None:
|
||||
self.__id.text = id.text.strip()
|
||||
|
||||
id = property(__GetId, __SetId)
|
||||
|
||||
def IsMedia(self):
|
||||
"""Determines whether or not an entry is a GData Media entry.
|
||||
"""
|
||||
if (self.GetEditMediaLink()):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def GetMediaURL(self):
|
||||
"""Returns the URL to the media content, if the entry is a media entry.
|
||||
Otherwise returns None.
|
||||
"""
|
||||
if not self.IsMedia():
|
||||
return None
|
||||
else:
|
||||
return self.content.src
|
||||
|
||||
|
||||
def GDataEntryFromString(xml_string):
|
||||
"""Creates a new GDataEntry instance given a string of XML."""
|
||||
return atom.CreateClassFromXMLString(GDataEntry, xml_string)
|
||||
|
||||
|
||||
class GDataFeed(atom.Feed, LinkFinder):
|
||||
"""A Feed from a GData service"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = atom.Feed._children.copy()
|
||||
_attributes = atom.Feed._attributes.copy()
|
||||
_children['{%s}totalResults' % OPENSEARCH_NAMESPACE] = ('total_results',
|
||||
TotalResults)
|
||||
_children['{%s}startIndex' % OPENSEARCH_NAMESPACE] = ('start_index',
|
||||
StartIndex)
|
||||
_children['{%s}itemsPerPage' % OPENSEARCH_NAMESPACE] = ('items_per_page',
|
||||
ItemsPerPage)
|
||||
# Add a conversion rule for atom:entry to make it into a GData
|
||||
# Entry.
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GDataEntry])
|
||||
|
||||
def __GetId(self):
|
||||
return self.__id
|
||||
|
||||
def __SetId(self, id):
|
||||
self.__id = id
|
||||
if id is not None and id.text is not None:
|
||||
self.__id.text = id.text.strip()
|
||||
|
||||
id = property(__GetId, __SetId)
|
||||
|
||||
def __GetGenerator(self):
|
||||
return self.__generator
|
||||
|
||||
def __SetGenerator(self, generator):
|
||||
self.__generator = generator
|
||||
if generator is not None:
|
||||
self.__generator.text = generator.text.strip()
|
||||
|
||||
generator = property(__GetGenerator, __SetGenerator)
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None, entry=None,
|
||||
total_results=None, start_index=None, items_per_page=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
"""Constructor for Source
|
||||
|
||||
Args:
|
||||
author: list (optional) A list of Author instances which belong to this
|
||||
class.
|
||||
category: list (optional) A list of Category instances
|
||||
contributor: list (optional) A list on Contributor instances
|
||||
generator: Generator (optional)
|
||||
icon: Icon (optional)
|
||||
id: Id (optional) The entry's Id element
|
||||
link: list (optional) A list of Link instances
|
||||
logo: Logo (optional)
|
||||
rights: Rights (optional) The entry's Rights element
|
||||
subtitle: Subtitle (optional) The entry's subtitle element
|
||||
title: Title (optional) the entry's title element
|
||||
updated: Updated (optional) the entry's updated element
|
||||
entry: list (optional) A list of the Entry instances contained in the
|
||||
feed.
|
||||
text: String (optional) The text contents of the element. This is the
|
||||
contents of the Entry's XML text node.
|
||||
(Example: <foo>This is the text</foo>)
|
||||
extension_elements: list (optional) A list of ExtensionElement instances
|
||||
which are children of this element.
|
||||
extension_attributes: dict (optional) A dictionary of strings which are
|
||||
the values for additional XML attributes of this element.
|
||||
"""
|
||||
|
||||
self.author = author or []
|
||||
self.category = category or []
|
||||
self.contributor = contributor or []
|
||||
self.generator = generator
|
||||
self.icon = icon
|
||||
self.id = atom_id
|
||||
self.link = link or []
|
||||
self.logo = logo
|
||||
self.rights = rights
|
||||
self.subtitle = subtitle
|
||||
self.title = title
|
||||
self.updated = updated
|
||||
self.entry = entry or []
|
||||
self.total_results = total_results
|
||||
self.start_index = start_index
|
||||
self.items_per_page = items_per_page
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def GDataFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(GDataFeed, xml_string)
|
||||
|
||||
|
||||
class BatchId(atom.AtomBase):
|
||||
_tag = 'id'
|
||||
_namespace = BATCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
|
||||
def BatchIdFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchId, xml_string)
|
||||
|
||||
|
||||
class BatchOperation(atom.AtomBase):
|
||||
_tag = 'operation'
|
||||
_namespace = BATCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['type'] = 'type'
|
||||
|
||||
def __init__(self, op_type=None, extension_elements=None,
|
||||
extension_attributes=None,
|
||||
text=None):
|
||||
self.type = op_type
|
||||
atom.AtomBase.__init__(self,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def BatchOperationFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchOperation, xml_string)
|
||||
|
||||
|
||||
class BatchStatus(atom.AtomBase):
|
||||
"""The batch:status element present in a batch response entry.
|
||||
|
||||
A status element contains the code (HTTP response code) and
|
||||
reason as elements. In a single request these fields would
|
||||
be part of the HTTP response, but in a batch request each
|
||||
Entry operation has a corresponding Entry in the response
|
||||
feed which includes status information.
|
||||
|
||||
See http://code.google.com/apis/gdata/batch.html#Handling_Errors
|
||||
"""
|
||||
|
||||
_tag = 'status'
|
||||
_namespace = BATCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['code'] = 'code'
|
||||
_attributes['reason'] = 'reason'
|
||||
_attributes['content-type'] = 'content_type'
|
||||
|
||||
def __init__(self, code=None, reason=None, content_type=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.code = code
|
||||
self.reason = reason
|
||||
self.content_type = content_type
|
||||
atom.AtomBase.__init__(self, extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def BatchStatusFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchStatus, xml_string)
|
||||
|
||||
|
||||
class BatchEntry(GDataEntry):
|
||||
"""An atom:entry for use in batch requests.
|
||||
|
||||
The BatchEntry contains additional members to specify the operation to be
|
||||
performed on this entry and a batch ID so that the server can reference
|
||||
individual operations in the response feed. For more information, see:
|
||||
http://code.google.com/apis/gdata/batch.html
|
||||
"""
|
||||
|
||||
_tag = GDataEntry._tag
|
||||
_namespace = GDataEntry._namespace
|
||||
_children = GDataEntry._children.copy()
|
||||
_children['{%s}operation' % BATCH_NAMESPACE] = ('batch_operation', BatchOperation)
|
||||
_children['{%s}id' % BATCH_NAMESPACE] = ('batch_id', BatchId)
|
||||
_children['{%s}status' % BATCH_NAMESPACE] = ('batch_status', BatchStatus)
|
||||
_attributes = GDataEntry._attributes.copy()
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
contributor=None, atom_id=None, link=None, published=None, rights=None,
|
||||
source=None, summary=None, control=None, title=None, updated=None,
|
||||
batch_operation=None, batch_id=None, batch_status=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.batch_operation = batch_operation
|
||||
self.batch_id = batch_id
|
||||
self.batch_status = batch_status
|
||||
GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content, contributor=contributor, atom_id=atom_id, link=link,
|
||||
published=published, rights=rights, source=source, summary=summary,
|
||||
control=control, title=title, updated=updated,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes, text=text)
|
||||
|
||||
|
||||
def BatchEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchEntry, xml_string)
|
||||
|
||||
|
||||
class BatchInterrupted(atom.AtomBase):
|
||||
"""The batch:interrupted element sent if batch request was interrupted.
|
||||
|
||||
Only appears in a feed if some of the batch entries could not be processed.
|
||||
See: http://code.google.com/apis/gdata/batch.html#Handling_Errors
|
||||
"""
|
||||
|
||||
_tag = 'interrupted'
|
||||
_namespace = BATCH_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['reason'] = 'reason'
|
||||
_attributes['success'] = 'success'
|
||||
_attributes['failures'] = 'failures'
|
||||
_attributes['parsed'] = 'parsed'
|
||||
|
||||
def __init__(self, reason=None, success=None, failures=None, parsed=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.reason = reason
|
||||
self.success = success
|
||||
self.failures = failures
|
||||
self.parsed = parsed
|
||||
atom.AtomBase.__init__(self, extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def BatchInterruptedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchInterrupted, xml_string)
|
||||
|
||||
|
||||
class BatchFeed(GDataFeed):
|
||||
"""A feed containing a list of batch request entries."""
|
||||
|
||||
_tag = GDataFeed._tag
|
||||
_namespace = GDataFeed._namespace
|
||||
_children = GDataFeed._children.copy()
|
||||
_attributes = GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [BatchEntry])
|
||||
_children['{%s}interrupted' % BATCH_NAMESPACE] = ('interrupted', BatchInterrupted)
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None, entry=None,
|
||||
total_results=None, start_index=None, items_per_page=None,
|
||||
interrupted=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.interrupted = interrupted
|
||||
GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results, start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
def AddBatchEntry(self, entry=None, id_url_string=None,
|
||||
batch_id_string=None, operation_string=None):
|
||||
"""Logic for populating members of a BatchEntry and adding to the feed.
|
||||
|
||||
|
||||
If the entry is not a BatchEntry, it is converted to a BatchEntry so
|
||||
that the batch specific members will be present.
|
||||
|
||||
The id_url_string can be used in place of an entry if the batch operation
|
||||
applies to a URL. For example query and delete operations require just
|
||||
the URL of an entry, no body is sent in the HTTP request. If an
|
||||
id_url_string is sent instead of an entry, a BatchEntry is created and
|
||||
added to the feed.
|
||||
|
||||
This method also assigns the desired batch id to the entry so that it
|
||||
can be referenced in the server's response. If the batch_id_string is
|
||||
None, this method will assign a batch_id to be the index at which this
|
||||
entry will be in the feed's entry list.
|
||||
|
||||
Args:
|
||||
entry: BatchEntry, atom.Entry, or another Entry flavor (optional) The
|
||||
entry which will be sent to the server as part of the batch request.
|
||||
The item must have a valid atom id so that the server knows which
|
||||
entry this request references.
|
||||
id_url_string: str (optional) The URL of the entry to be acted on. You
|
||||
can find this URL in the text member of the atom id for an entry.
|
||||
If an entry is not sent, this id will be used to construct a new
|
||||
BatchEntry which will be added to the request feed.
|
||||
batch_id_string: str (optional) The batch ID to be used to reference
|
||||
this batch operation in the results feed. If this parameter is None,
|
||||
the current length of the feed's entry array will be used as a
|
||||
count. Note that batch_ids should either always be specified or
|
||||
never, mixing could potentially result in duplicate batch ids.
|
||||
operation_string: str (optional) The desired batch operation which will
|
||||
set the batch_operation.type member of the entry. Options are
|
||||
'insert', 'update', 'delete', and 'query'
|
||||
|
||||
Raises:
|
||||
MissingRequiredParameters: Raised if neither an id_ url_string nor an
|
||||
entry are provided in the request.
|
||||
|
||||
Returns:
|
||||
The added entry.
|
||||
"""
|
||||
if entry is None and id_url_string is None:
|
||||
raise MissingRequiredParameters('supply either an entry or URL string')
|
||||
if entry is None and id_url_string is not None:
|
||||
entry = BatchEntry(atom_id=atom.Id(text=id_url_string))
|
||||
# TODO: handle cases in which the entry lacks batch_... members.
|
||||
#if not isinstance(entry, BatchEntry):
|
||||
# Convert the entry to a batch entry.
|
||||
if batch_id_string is not None:
|
||||
entry.batch_id = BatchId(text=batch_id_string)
|
||||
elif entry.batch_id is None or entry.batch_id.text is None:
|
||||
entry.batch_id = BatchId(text=str(len(self.entry)))
|
||||
if operation_string is not None:
|
||||
entry.batch_operation = BatchOperation(op_type=operation_string)
|
||||
self.entry.append(entry)
|
||||
return entry
|
||||
|
||||
def AddInsert(self, entry, batch_id_string=None):
|
||||
"""Add an insert request to the operations in this batch request feed.
|
||||
|
||||
If the entry doesn't yet have an operation or a batch id, these will
|
||||
be set to the insert operation and a batch_id specified as a parameter.
|
||||
|
||||
Args:
|
||||
entry: BatchEntry The entry which will be sent in the batch feed as an
|
||||
insert request.
|
||||
batch_id_string: str (optional) The batch ID to be used to reference
|
||||
this batch operation in the results feed. If this parameter is None,
|
||||
the current length of the feed's entry array will be used as a
|
||||
count. Note that batch_ids should either always be specified or
|
||||
never, mixing could potentially result in duplicate batch ids.
|
||||
"""
|
||||
entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string,
|
||||
operation_string=BATCH_INSERT)
|
||||
|
||||
def AddUpdate(self, entry, batch_id_string=None):
|
||||
"""Add an update request to the list of batch operations in this feed.
|
||||
|
||||
Sets the operation type of the entry to insert if it is not already set
|
||||
and assigns the desired batch id to the entry so that it can be
|
||||
referenced in the server's response.
|
||||
|
||||
Args:
|
||||
entry: BatchEntry The entry which will be sent to the server as an
|
||||
update (HTTP PUT) request. The item must have a valid atom id
|
||||
so that the server knows which entry to replace.
|
||||
batch_id_string: str (optional) The batch ID to be used to reference
|
||||
this batch operation in the results feed. If this parameter is None,
|
||||
the current length of the feed's entry array will be used as a
|
||||
count. See also comments for AddInsert.
|
||||
"""
|
||||
entry = self.AddBatchEntry(entry=entry, batch_id_string=batch_id_string,
|
||||
operation_string=BATCH_UPDATE)
|
||||
|
||||
def AddDelete(self, url_string=None, entry=None, batch_id_string=None):
|
||||
"""Adds a delete request to the batch request feed.
|
||||
|
||||
This method takes either the url_string which is the atom id of the item
|
||||
to be deleted, or the entry itself. The atom id of the entry must be
|
||||
present so that the server knows which entry should be deleted.
|
||||
|
||||
Args:
|
||||
url_string: str (optional) The URL of the entry to be deleted. You can
|
||||
find this URL in the text member of the atom id for an entry.
|
||||
entry: BatchEntry (optional) The entry to be deleted.
|
||||
batch_id_string: str (optional)
|
||||
|
||||
Raises:
|
||||
MissingRequiredParameters: Raised if neither a url_string nor an entry
|
||||
are provided in the request.
|
||||
"""
|
||||
entry = self.AddBatchEntry(entry=entry, id_url_string=url_string,
|
||||
batch_id_string=batch_id_string,
|
||||
operation_string=BATCH_DELETE)
|
||||
|
||||
def AddQuery(self, url_string=None, entry=None, batch_id_string=None):
|
||||
"""Adds a query request to the batch request feed.
|
||||
|
||||
This method takes either the url_string which is the query URL
|
||||
whose results will be added to the result feed. The query URL will
|
||||
be encapsulated in a BatchEntry, and you may pass in the BatchEntry
|
||||
with a query URL instead of sending a url_string.
|
||||
|
||||
Args:
|
||||
url_string: str (optional)
|
||||
entry: BatchEntry (optional)
|
||||
batch_id_string: str (optional)
|
||||
|
||||
Raises:
|
||||
MissingRequiredParameters
|
||||
"""
|
||||
entry = self.AddBatchEntry(entry=entry, id_url_string=url_string,
|
||||
batch_id_string=batch_id_string,
|
||||
operation_string=BATCH_QUERY)
|
||||
|
||||
def GetBatchLink(self):
|
||||
for link in self.link:
|
||||
if link.rel == 'http://schemas.google.com/g/2005#batch':
|
||||
return link
|
||||
return None
|
||||
|
||||
|
||||
def BatchFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(BatchFeed, xml_string)
|
||||
|
||||
|
||||
class EntryLink(atom.AtomBase):
|
||||
"""The gd:entryLink element"""
|
||||
|
||||
_tag = 'entryLink'
|
||||
_namespace = GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
# The entry used to be an atom.Entry, now it is a GDataEntry.
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', GDataEntry)
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['readOnly'] = 'read_only'
|
||||
_attributes['href'] = 'href'
|
||||
|
||||
def __init__(self, href=None, read_only=None, rel=None,
|
||||
entry=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.href = href
|
||||
self.read_only = read_only
|
||||
self.rel = rel
|
||||
self.entry = entry
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def EntryLinkFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EntryLink, xml_string)
|
||||
|
||||
|
||||
class FeedLink(atom.AtomBase):
|
||||
"""The gd:feedLink element"""
|
||||
|
||||
_tag = 'feedLink'
|
||||
_namespace = GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_children['{%s}feed' % atom.ATOM_NAMESPACE] = ('feed', GDataFeed)
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['readOnly'] = 'read_only'
|
||||
_attributes['countHint'] = 'count_hint'
|
||||
_attributes['href'] = 'href'
|
||||
|
||||
def __init__(self, count_hint=None, href=None, read_only=None, rel=None,
|
||||
feed=None, extension_elements=None, extension_attributes=None,
|
||||
text=None):
|
||||
self.count_hint = count_hint
|
||||
self.href = href
|
||||
self.read_only = read_only
|
||||
self.rel = rel
|
||||
self.feed = feed
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def FeedLinkFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(FeedLink, xml_string)
|
||||
@@ -1,526 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2007 SIOS Technology, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Contains objects used with Google Apps."""
|
||||
|
||||
__author__ = 'tmatsuo@sios.com (Takashi MATSUO)'
|
||||
|
||||
|
||||
import atom
|
||||
import gdata
|
||||
|
||||
|
||||
# XML namespaces which are often used in Google Apps entity.
|
||||
APPS_NAMESPACE = 'http://schemas.google.com/apps/2006'
|
||||
APPS_TEMPLATE = '{http://schemas.google.com/apps/2006}%s'
|
||||
|
||||
|
||||
class EmailList(atom.AtomBase):
|
||||
"""The Google Apps EmailList element"""
|
||||
|
||||
_tag = 'emailList'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['name'] = 'name'
|
||||
|
||||
def __init__(self, name=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.name = name
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
def EmailListFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailList, xml_string)
|
||||
|
||||
|
||||
class Who(atom.AtomBase):
|
||||
"""The Google Apps Who element"""
|
||||
|
||||
_tag = 'who'
|
||||
_namespace = gdata.GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['email'] = 'email'
|
||||
|
||||
def __init__(self, rel=None, email=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.rel = rel
|
||||
self.email = email
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
def WhoFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Who, xml_string)
|
||||
|
||||
|
||||
class Login(atom.AtomBase):
|
||||
"""The Google Apps Login element"""
|
||||
|
||||
_tag = 'login'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['userName'] = 'user_name'
|
||||
_attributes['password'] = 'password'
|
||||
_attributes['suspended'] = 'suspended'
|
||||
_attributes['admin'] = 'admin'
|
||||
_attributes['changePasswordAtNextLogin'] = 'change_password'
|
||||
_attributes['agreedToTerms'] = 'agreed_to_terms'
|
||||
_attributes['ipWhitelisted'] = 'ip_whitelisted'
|
||||
_attributes['hashFunctionName'] = 'hash_function_name'
|
||||
|
||||
def __init__(self, user_name=None, password=None, suspended=None,
|
||||
ip_whitelisted=None, hash_function_name=None,
|
||||
admin=None, change_password=None, agreed_to_terms=None,
|
||||
extension_elements=None, extension_attributes=None,
|
||||
text=None):
|
||||
self.user_name = user_name
|
||||
self.password = password
|
||||
self.suspended = suspended
|
||||
self.admin = admin
|
||||
self.change_password = change_password
|
||||
self.agreed_to_terms = agreed_to_terms
|
||||
self.ip_whitelisted = ip_whitelisted
|
||||
self.hash_function_name = hash_function_name
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def LoginFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Login, xml_string)
|
||||
|
||||
|
||||
class Quota(atom.AtomBase):
|
||||
"""The Google Apps Quota element"""
|
||||
|
||||
_tag = 'quota'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['limit'] = 'limit'
|
||||
|
||||
def __init__(self, limit=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.limit = limit
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def QuotaFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Quota, xml_string)
|
||||
|
||||
|
||||
class Name(atom.AtomBase):
|
||||
"""The Google Apps Name element"""
|
||||
|
||||
_tag = 'name'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['familyName'] = 'family_name'
|
||||
_attributes['givenName'] = 'given_name'
|
||||
|
||||
def __init__(self, family_name=None, given_name=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.family_name = family_name
|
||||
self.given_name = given_name
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def NameFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Name, xml_string)
|
||||
|
||||
|
||||
class Nickname(atom.AtomBase):
|
||||
"""The Google Apps Nickname element"""
|
||||
|
||||
_tag = 'nickname'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['name'] = 'name'
|
||||
|
||||
def __init__(self, name=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
self.name = name
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def NicknameFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Nickname, xml_string)
|
||||
|
||||
|
||||
class NicknameEntry(gdata.GDataEntry):
|
||||
"""A Google Apps flavor of an Atom Entry for Nickname"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}login' % APPS_NAMESPACE] = ('login', Login)
|
||||
_children['{%s}nickname' % APPS_NAMESPACE] = ('nickname', Nickname)
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
login=None, nickname=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.login = login
|
||||
self.nickname = nickname
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def NicknameEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(NicknameEntry, xml_string)
|
||||
|
||||
|
||||
class NicknameFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps Nickname feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [NicknameEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def NicknameFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(NicknameFeed, xml_string)
|
||||
|
||||
|
||||
class UserEntry(gdata.GDataEntry):
|
||||
"""A Google Apps flavor of an Atom Entry"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}login' % APPS_NAMESPACE] = ('login', Login)
|
||||
_children['{%s}name' % APPS_NAMESPACE] = ('name', Name)
|
||||
_children['{%s}quota' % APPS_NAMESPACE] = ('quota', Quota)
|
||||
# This child may already be defined in GDataEntry, confirm before removing.
|
||||
_children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
|
||||
[gdata.FeedLink])
|
||||
_children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who)
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
login=None, name=None, quota=None, who=None, feed_link=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.login = login
|
||||
self.name = name
|
||||
self.quota = quota
|
||||
self.who = who
|
||||
self.feed_link = feed_link or []
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def UserEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(UserEntry, xml_string)
|
||||
|
||||
|
||||
class UserFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps User feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [UserEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def UserFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(UserFeed, xml_string)
|
||||
|
||||
|
||||
class EmailListEntry(gdata.GDataEntry):
|
||||
"""A Google Apps EmailList flavor of an Atom Entry"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}emailList' % APPS_NAMESPACE] = ('email_list', EmailList)
|
||||
# Might be able to remove this _children entry.
|
||||
_children['{%s}feedLink' % gdata.GDATA_NAMESPACE] = ('feed_link',
|
||||
[gdata.FeedLink])
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
email_list=None, feed_link=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.email_list = email_list
|
||||
self.feed_link = feed_link or []
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def EmailListEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailListEntry, xml_string)
|
||||
|
||||
|
||||
class EmailListFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps EmailList feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [EmailListEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def EmailListFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailListFeed, xml_string)
|
||||
|
||||
|
||||
class EmailListRecipientEntry(gdata.GDataEntry):
|
||||
"""A Google Apps EmailListRecipient flavor of an Atom Entry"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}who' % gdata.GDATA_NAMESPACE] = ('who', Who)
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
who=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.who = who
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def EmailListRecipientEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailListRecipientEntry, xml_string)
|
||||
|
||||
|
||||
class EmailListRecipientFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps EmailListRecipient feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry',
|
||||
[EmailListRecipientEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
|
||||
def EmailListRecipientFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(EmailListRecipientFeed, xml_string)
|
||||
|
||||
|
||||
class Property(atom.AtomBase):
|
||||
"""The Google Apps Property element"""
|
||||
|
||||
_tag = 'property'
|
||||
_namespace = APPS_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
_attributes['name'] = 'name'
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, name=None, value=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def PropertyFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Property, xml_string)
|
||||
|
||||
|
||||
class PropertyEntry(gdata.GDataEntry):
|
||||
"""A Google Apps Property flavor of an Atom Entry"""
|
||||
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataEntry._children.copy()
|
||||
_attributes = gdata.GDataEntry._attributes.copy()
|
||||
_children['{%s}property' % APPS_NAMESPACE] = ('property', [Property])
|
||||
|
||||
def __init__(self, author=None, category=None, content=None,
|
||||
atom_id=None, link=None, published=None,
|
||||
title=None, updated=None,
|
||||
property=None,
|
||||
extended_property=None,
|
||||
extension_elements=None, extension_attributes=None, text=None):
|
||||
|
||||
gdata.GDataEntry.__init__(self, author=author, category=category,
|
||||
content=content,
|
||||
atom_id=atom_id, link=link, published=published,
|
||||
title=title, updated=updated)
|
||||
self.property = property
|
||||
self.extended_property = extended_property or []
|
||||
self.text = text
|
||||
self.extension_elements = extension_elements or []
|
||||
self.extension_attributes = extension_attributes or {}
|
||||
|
||||
|
||||
def PropertyEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(PropertyEntry, xml_string)
|
||||
|
||||
class PropertyFeed(gdata.GDataFeed, gdata.LinkFinder):
|
||||
"""A Google Apps Property feed flavor of an Atom Feed"""
|
||||
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.GDataFeed._children.copy()
|
||||
_attributes = gdata.GDataFeed._attributes.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [PropertyEntry])
|
||||
|
||||
def __init__(self, author=None, category=None, contributor=None,
|
||||
generator=None, icon=None, atom_id=None, link=None, logo=None,
|
||||
rights=None, subtitle=None, title=None, updated=None,
|
||||
entry=None, total_results=None, start_index=None,
|
||||
items_per_page=None, extension_elements=None,
|
||||
extension_attributes=None, text=None):
|
||||
gdata.GDataFeed.__init__(self, author=author, category=category,
|
||||
contributor=contributor, generator=generator,
|
||||
icon=icon, atom_id=atom_id, link=link,
|
||||
logo=logo, rights=rights, subtitle=subtitle,
|
||||
title=title, updated=updated, entry=entry,
|
||||
total_results=total_results,
|
||||
start_index=start_index,
|
||||
items_per_page=items_per_page,
|
||||
extension_elements=extension_elements,
|
||||
extension_attributes=extension_attributes,
|
||||
text=text)
|
||||
|
||||
def PropertyFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(PropertyFeed, xml_string)
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (C) 2008 Google, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Allow Google Apps domain administrators to audit user data.
|
||||
|
||||
AuditService: Set auditing."""
|
||||
|
||||
__author__ = 'jlee@pbu.edu'
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
import gdata.apps
|
||||
import gdata.apps.service
|
||||
import gdata.service
|
||||
|
||||
class AuditService(gdata.apps.service.PropertyService):
|
||||
"""Client for the Google Apps Audit service."""
|
||||
|
||||
def _serviceUrl(self, setting_id, domain=None, user=None):
|
||||
if domain is None:
|
||||
domain = self.domain
|
||||
if user is None:
|
||||
return '/a/feeds/compliance/audit/%s/%s' % (setting_id, domain)
|
||||
else:
|
||||
return '/a/feeds/compliance/audit/%s/%s/%s' % (setting_id, domain, user)
|
||||
|
||||
def updatePGPKey(self, pgpkey):
|
||||
"""Updates Public PGP Key Google uses to encrypt audit data
|
||||
|
||||
Args:
|
||||
pgpkey: string, ASCII text of PGP Public Key to be used
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the POST operation."""
|
||||
|
||||
uri = self._serviceUrl('publickey')
|
||||
b64pgpkey = b64encode(pgpkey)
|
||||
properties = {}
|
||||
properties['publicKey'] = b64pgpkey
|
||||
return self._PostProperties(uri, properties)
|
||||
|
||||
def createEmailMonitor(self, source_user, destination_user, end_date,
|
||||
begin_date=None, incoming_headers_only=False,
|
||||
outgoing_headers_only=False, drafts=False,
|
||||
drafts_headers_only=False, chats=False,
|
||||
chats_headers_only=False):
|
||||
"""Creates a email monitor, forwarding the source_users emails/chats
|
||||
|
||||
Args:
|
||||
source_user: string, the user whose email will be audited
|
||||
destination_user: string, the user to receive the audited email
|
||||
end_date: string, the date the audit will end in
|
||||
"yyyy-MM-dd HH:mm" format, required
|
||||
begin_date: string, the date the audit will start in
|
||||
"yyyy-MM-dd HH:mm" format, leave blank to use current time
|
||||
incoming_headers_only: boolean, whether to audit only the headers of
|
||||
mail delivered to source user
|
||||
outgoing_headers_only: boolean, whether to audit only the headers of
|
||||
mail sent from the source user
|
||||
drafts: boolean, whether to audit draft messages of the source user
|
||||
drafts_headers_only: boolean, whether to audit only the headers of
|
||||
mail drafts saved by the user
|
||||
chats: boolean, whether to audit archived chats of the source user
|
||||
chats_headers_only: boolean, whether to audit only the headers of
|
||||
archived chats of the source user
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the POST operation."""
|
||||
|
||||
uri = self._serviceUrl('mail/monitor', user=source_user)
|
||||
properties = {}
|
||||
properties['destUserName'] = destination_user
|
||||
if begin_date is not None:
|
||||
properties['beginDate'] = begin_date
|
||||
properties['endDate'] = end_date
|
||||
if incoming_headers_only:
|
||||
properties['incomingEmailMonitorLevel'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['incomingEmailMonitorLevel'] = 'FULL_MESSAGE'
|
||||
if outgoing_headers_only:
|
||||
properties['outgoingEmailMonitorLevel'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['outgoingEmailMonitorLevel'] = 'FULL_MESSAGE'
|
||||
if drafts:
|
||||
if drafts_headers_only:
|
||||
properties['draftMonitorLevel'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['draftMonitorLevel'] = 'FULL_MESSAGE'
|
||||
if chats:
|
||||
if chats_headers_only:
|
||||
properties['chatMonitorLevel'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['chatMonitorLevel'] = 'FULL_MESSAGE'
|
||||
return self._PostProperties(uri, properties)
|
||||
|
||||
def getEmailMonitors(self, user):
|
||||
""""Gets the email monitors for the given user
|
||||
|
||||
Args:
|
||||
user: string, the user to retrieve email monitors for
|
||||
|
||||
Returns:
|
||||
list results of the POST operation
|
||||
|
||||
"""
|
||||
uri = self._serviceUrl('mail/monitor', user=user)
|
||||
return self._GetPropertiesList(uri)
|
||||
|
||||
def deleteEmailMonitor(self, source_user, destination_user):
|
||||
"""Deletes the email monitor for the given user
|
||||
|
||||
Args:
|
||||
source_user: string, the user who is being monitored
|
||||
destination_user: string, theuser who recieves the monitored emails
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('mail/monitor', user=source_user+'/'+destination_user)
|
||||
try:
|
||||
return self._DeleteProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def createAccountInformationRequest(self, user):
|
||||
"""Creates a request for account auditing details
|
||||
|
||||
Args:
|
||||
user: string, the user to request account information for
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the post operation."""
|
||||
|
||||
uri = self._serviceUrl('account', user=user)
|
||||
properties = {}
|
||||
#XML Body is left empty
|
||||
try:
|
||||
return self._PostProperties(uri, properties)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def getAccountInformationRequestStatus(self, user, request_id):
|
||||
"""Gets the status of an account auditing request
|
||||
|
||||
Args:
|
||||
user: string, the user whose account auditing details were requested
|
||||
request_id: string, the request_id
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the get operation."""
|
||||
|
||||
uri = self._serviceUrl('account', user=user+'/'+request_id)
|
||||
try:
|
||||
return self._GetProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def getAllAccountInformationRequestsStatus(self):
|
||||
"""Gets the status of all account auditing requests for the domain
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
list results of the POST operation
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('account')
|
||||
return self._GetPropertiesList(uri)
|
||||
|
||||
|
||||
def deleteAccountInformationRequest(self, user, request_id):
|
||||
"""Deletes the request for account auditing information
|
||||
|
||||
Args:
|
||||
user: string, the user whose account auditing details were requested
|
||||
request_id: string, the request_id
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('account', user=user+'/'+request_id)
|
||||
try:
|
||||
return self._DeleteProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def createMailboxExportRequest(self, user, begin_date=None, end_date=None, include_deleted=False, search_query=None, headers_only=False):
|
||||
"""Creates a mailbox export request
|
||||
|
||||
Args:
|
||||
user: string, the user whose mailbox export is being requested
|
||||
begin_date: string, date of earliest emails to export, optional, defaults to date of account creation
|
||||
format is 'yyyy-MM-dd HH:mm'
|
||||
end_date: string, date of latest emails to export, optional, defaults to current date
|
||||
format is 'yyyy-MM-dd HH:mm'
|
||||
include_deleted: boolean, whether to include deleted emails in export, mutually exclusive with search_query
|
||||
search_query: string, gmail style search query, matched emails will be exported, mutually exclusive with include_deleted
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the post operation."""
|
||||
|
||||
uri = self._serviceUrl('mail/export', user=user)
|
||||
properties = {}
|
||||
if begin_date is not None:
|
||||
properties['beginDate'] = begin_date
|
||||
if end_date is not None:
|
||||
properties['endDate'] = end_date
|
||||
if include_deleted is not None:
|
||||
properties['includeDeleted'] = gdata.apps.service._bool2str(include_deleted)
|
||||
if search_query is not None:
|
||||
properties['searchQuery'] = search_query
|
||||
if headers_only is True:
|
||||
properties['packageContent'] = 'HEADER_ONLY'
|
||||
else:
|
||||
properties['packageContent'] = 'FULL_MESSAGE'
|
||||
return self._PostProperties(uri, properties)
|
||||
|
||||
def getMailboxExportRequestStatus(self, user, request_id):
|
||||
"""Gets the status of an mailbox export request
|
||||
|
||||
Args:
|
||||
user: string, the user whose mailbox were requested
|
||||
request_id: string, the request_id
|
||||
|
||||
Returns:
|
||||
A dict containing the result of the get operation."""
|
||||
|
||||
uri = self._serviceUrl('mail/export', user=user+'/'+request_id)
|
||||
try:
|
||||
return self._GetProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def getAllMailboxExportRequestsStatus(self):
|
||||
"""Gets the status of all mailbox export requests for the domain
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
list results of the POST operation
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('mail/export')
|
||||
return self._GetPropertiesList(uri)
|
||||
|
||||
|
||||
def deleteMailboxExportRequest(self, user, request_id):
|
||||
"""Deletes the request for mailbox export
|
||||
|
||||
Args:
|
||||
user: string, the user whose mailbox were requested
|
||||
request_id: string, the request_id
|
||||
|
||||
Returns:
|
||||
Nothing
|
||||
"""
|
||||
|
||||
uri = self._serviceUrl('mail/export', user=user+'/'+request_id)
|
||||
try:
|
||||
return self._DeleteProperties(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
@@ -1,874 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2009 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Data model classes for parsing and generating XML for the Contacts API."""
|
||||
|
||||
import atom
|
||||
import gdata
|
||||
|
||||
## Constants from http://code.google.com/apis/gdata/elements.html ##
|
||||
REL_HOME = 'http://schemas.google.com/g/2005#home'
|
||||
REL_WORK = 'http://schemas.google.com/g/2005#work'
|
||||
REL_OTHER = 'http://schemas.google.com/g/2005#other'
|
||||
|
||||
IM_AIM = 'http://schemas.google.com/g/2005#AIM' # AOL Instant Messenger protocol
|
||||
IM_MSN = 'http://schemas.google.com/g/2005#MSN' # MSN Messenger protocol
|
||||
IM_YAHOO = 'http://schemas.google.com/g/2005#YAHOO' # Yahoo Messenger protocol
|
||||
IM_SKYPE = 'http://schemas.google.com/g/2005#SKYPE' # Skype protocol
|
||||
IM_QQ = 'http://schemas.google.com/g/2005#QQ' # QQ protocol
|
||||
IM_GOOGLE_TALK = 'http://schemas.google.com/g/2005#GOOGLE_TALK' # Google Talk protocol
|
||||
IM_ICQ = 'http://schemas.google.com/g/2005#ICQ' # ICQ protocol
|
||||
IM_JABBER = 'http://schemas.google.com/g/2005#JABBER' # Jabber protocol
|
||||
IM_NETMEETING = 'http://schemas.google.com/g/2005#NETMEETING' # NetMeeting
|
||||
|
||||
PHOTO_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#photo'
|
||||
PHOTO_EDIT_LINK_REL = 'http://schemas.google.com/contacts/2008/rel#edit-photo'
|
||||
|
||||
# Different phone types, for more info see:
|
||||
# http://code.google.com/apis/gdata/docs/2.0/elements.html#gdPhoneNumber
|
||||
PHONE_ASSISTANT = 'http://schemas.google.com/g/2005#assistant'
|
||||
PHONE_CALLBACK = 'http://schemas.google.com/g/2005#callback'
|
||||
PHONE_CAR = 'http://schemas.google.com/g/2005#car'
|
||||
PHONE_COMPANY_MAIN = 'http://schemas.google.com/g/2005#company_main'
|
||||
PHONE_FAX = 'http://schemas.google.com/g/2005#fax'
|
||||
PHONE_GENERAL = 'http://schemas.google.com/g/2005#general'
|
||||
PHONE_HOME = REL_HOME
|
||||
PHONE_HOME_FAX = 'http://schemas.google.com/g/2005#home_fax'
|
||||
PHONE_INTERNAL = 'http://schemas.google.com/g/2005#internal-extension'
|
||||
PHONE_ISDN = 'http://schemas.google.com/g/2005#isdn'
|
||||
PHONE_MAIN = 'http://schemas.google.com/g/2005#main'
|
||||
PHONE_MOBILE = 'http://schemas.google.com/g/2005#mobile'
|
||||
PHONE_OTHER = REL_OTHER
|
||||
PHONE_OTHER_FAX = 'http://schemas.google.com/g/2005#other_fax'
|
||||
PHONE_PAGER = 'http://schemas.google.com/g/2005#pager'
|
||||
PHONE_RADIO = 'http://schemas.google.com/g/2005#radio'
|
||||
PHONE_SATELLITE = 'http://schemas.google.com/g/2005#satellite'
|
||||
PHONE_TELEX = 'http://schemas.google.com/g/2005#telex'
|
||||
PHONE_TTY_TDD = 'http://schemas.google.com/g/2005#tty_tdd'
|
||||
PHONE_VOIP = 'http://schemas.google.com/g/2005#voip'
|
||||
PHONE_WORK = REL_WORK
|
||||
PHONE_WORK_FAX = 'http://schemas.google.com/g/2005#work_fax'
|
||||
PHONE_WORK_MOBILE = 'http://schemas.google.com/g/2005#work_mobile'
|
||||
PHONE_WORK_PAGER = 'http://schemas.google.com/g/2005#work_pager'
|
||||
|
||||
MAIL_BOTH = 'http://schemas.google.com/g/2005#both'
|
||||
MAIL_LETTERS = 'http://schemas.google.com/g/2005#letters'
|
||||
MAIL_PARCELS = 'http://schemas.google.com/g/2005#parcels'
|
||||
MAIL_NEITHER = 'http://schemas.google.com/g/2005#neither'
|
||||
|
||||
GENERAL_ADDRESS = 'http://schemas.google.com/g/2005#general'
|
||||
LOCAL_ADDRESS = 'http://schemas.google.com/g/2005#local'
|
||||
|
||||
EXTERNAL_ID_ORGANIZATION = 'organization'
|
||||
|
||||
RELATION_MANAGER = 'manager'
|
||||
|
||||
CONTACTS_NAMESPACE = 'http://schemas.google.com/contact/2008'
|
||||
|
||||
|
||||
class GDataBase(atom.AtomBase):
|
||||
"""The Google Contacts intermediate class from atom.AtomBase."""
|
||||
_namespace = gdata.GDATA_NAMESPACE
|
||||
_children = atom.AtomBase._children.copy()
|
||||
_attributes = atom.AtomBase._attributes.copy()
|
||||
|
||||
def __init__(self, text=None):
|
||||
atom.AtomBase.__init__(self, text=text)
|
||||
|
||||
class ContactsBase(GDataBase):
|
||||
"""The Google Contacts intermediate class for Contacts namespace."""
|
||||
_namespace = CONTACTS_NAMESPACE
|
||||
|
||||
class BillingInformation(ContactsBase):
|
||||
"""The gContact:billingInformation element."""
|
||||
_tag = 'billingInformation'
|
||||
|
||||
class Birthday(ContactsBase):
|
||||
"""The gContact:birthday element."""
|
||||
_tag = 'birthday'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['when'] = 'when'
|
||||
|
||||
def __init__(self, when=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.when = when
|
||||
|
||||
class CalendarLink(ContactsBase):
|
||||
"""The gContact:calendarLink element."""
|
||||
_tag = 'calendarLink'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['href'] = 'href'
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['primary'] = 'primary'
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, href=None, label=None, primary='false', rel=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.href = href
|
||||
self.label = label
|
||||
self.primary = primary
|
||||
self.rel = rel
|
||||
|
||||
class Content(atom.AtomBase):
|
||||
"""The Google Contacts Content element."""
|
||||
_tag = 'content'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
|
||||
def __init__(self, text=None):
|
||||
atom.AtomBase.__init__(self, text=text)
|
||||
|
||||
class DirectoryServer(ContactsBase):
|
||||
"""The gContact:directoryServer element."""
|
||||
_tag = 'directoryServer'
|
||||
|
||||
class Email(GDataBase):
|
||||
"""The gd:email element."""
|
||||
_tag = 'email'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['address'] = 'address'
|
||||
_attributes['primary'] = 'primary'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['label'] = 'label'
|
||||
|
||||
def __init__(self, label=None, rel=None, address=None, primary='false'):
|
||||
GDataBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.address = address
|
||||
self.primary = primary
|
||||
|
||||
class When(GDataBase):
|
||||
"""The Google Contacts when element."""
|
||||
_tag = 'when'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['startTime'] = 'startTime'
|
||||
_attributes['label'] = 'label'
|
||||
|
||||
def __init__(self, startTime=None, label=None):
|
||||
GDataBase.__init__(self)
|
||||
self.startTime = startTime
|
||||
self.label = label
|
||||
|
||||
class Event(ContactsBase):
|
||||
"""The gContact:event element."""
|
||||
_tag = 'event'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_children['{%s}when' % GDataBase._namespace] = ('when', When)
|
||||
|
||||
def __init__(self, label=None, rel=None, when=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.when = when
|
||||
|
||||
def EventFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Event, xml_string)
|
||||
|
||||
class ExternalId(ContactsBase):
|
||||
"""The gContact:externalId element."""
|
||||
_tag = 'externalId'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, label=None, rel=None, value=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.value = value
|
||||
|
||||
def ExternalIdFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ExternalId, xml_string)
|
||||
|
||||
class Gender(ContactsBase):
|
||||
"""The gContact:gender element."""
|
||||
_tag = 'gender'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, value=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.value = value
|
||||
|
||||
class Hobby(ContactsBase):
|
||||
"""The gContact:hobby element."""
|
||||
_tag = 'hobby'
|
||||
|
||||
class IM(GDataBase):
|
||||
"""The gd:im element."""
|
||||
_tag = 'im'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['address'] = 'address'
|
||||
_attributes['primary'] = 'primary'
|
||||
_attributes['protocol'] = 'protocol'
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, primary='false', rel=None, address=None, protocol=None, label=None):
|
||||
GDataBase.__init__(self)
|
||||
self.protocol = protocol
|
||||
self.address = address
|
||||
self.primary = primary
|
||||
self.rel = rel
|
||||
self.label = label
|
||||
|
||||
class Initials(ContactsBase):
|
||||
"""The gContact:initials element."""
|
||||
_tag = 'initials'
|
||||
|
||||
class Jot(ContactsBase):
|
||||
"""The gContact:jot element."""
|
||||
_tag = 'jot'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, rel=None, text=None):
|
||||
ContactsBase.__init__(self, text=text)
|
||||
self.rel = rel
|
||||
|
||||
class Language(ContactsBase):
|
||||
"""The gContact:language element."""
|
||||
_tag = 'language'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['code'] = 'code'
|
||||
_attributes['label'] = 'label'
|
||||
|
||||
def __init__(self, code=None, label=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.code = code
|
||||
self.label = label
|
||||
|
||||
class MaidenName(ContactsBase):
|
||||
"""The gContact:maidenName element."""
|
||||
_tag = 'maidenName'
|
||||
|
||||
class Mileage(ContactsBase):
|
||||
"""The gContact:mileage element."""
|
||||
_tag = 'mileage'
|
||||
|
||||
class NamePrefix(GDataBase):
|
||||
"""The gd:namePrefix element."""
|
||||
_tag = 'namePrefix'
|
||||
|
||||
class GivenName(GDataBase):
|
||||
"""The gd:givenName element."""
|
||||
_tag = 'givenName'
|
||||
|
||||
class AdditionalName(GDataBase):
|
||||
"""The gd:additionalName element."""
|
||||
_tag = 'additionalName'
|
||||
|
||||
class FamilyName(GDataBase):
|
||||
"""The gd:familyName element."""
|
||||
_tag = 'familyName'
|
||||
|
||||
class NameSuffix(GDataBase):
|
||||
"""The gd:nameSuffix element."""
|
||||
_tag = 'nameSuffix'
|
||||
|
||||
class FullName(GDataBase):
|
||||
"""The gd:fullName element."""
|
||||
_tag = 'fullName'
|
||||
|
||||
class Name(GDataBase):
|
||||
"""The gd:name element."""
|
||||
_tag = 'name'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_children['{%s}namePrefix' % GDataBase._namespace] = ('name_prefix', NamePrefix)
|
||||
_children['{%s}givenName' % GDataBase._namespace] = ('given_name', GivenName)
|
||||
_children['{%s}additionalName' % GDataBase._namespace] = ('additional_name', AdditionalName)
|
||||
_children['{%s}familyName' % GDataBase._namespace] = ('family_name', FamilyName)
|
||||
_children['{%s}nameSuffix' % GDataBase._namespace] = ('name_suffix', NameSuffix)
|
||||
_children['{%s}fullName' % GDataBase._namespace] = ('full_name', FullName)
|
||||
|
||||
def __init__(self, given_name=None, additional_name=None, family_name=None,
|
||||
name_prefix=None, name_suffix=None, full_name=None,):
|
||||
GDataBase.__init__(self)
|
||||
self.given_name = given_name
|
||||
self.additional_name = additional_name
|
||||
self.family_name = family_name
|
||||
self.name_prefix = name_prefix
|
||||
self.name_suffix = name_suffix
|
||||
self.full_name = full_name
|
||||
|
||||
class Nickname(ContactsBase):
|
||||
"""The gContact:nickname element."""
|
||||
_tag = 'nickname'
|
||||
|
||||
class Occupation(ContactsBase):
|
||||
"""The gContact:occupation element."""
|
||||
_tag = 'occupation'
|
||||
|
||||
class PhoneNumber(GDataBase):
|
||||
"""The gd:phoneNumber element."""
|
||||
_tag = 'phoneNumber'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['uri'] = 'uri'
|
||||
_attributes['primary'] = 'primary'
|
||||
|
||||
def __init__(self, label=None, rel=None, uri=None, primary='false', text=None):
|
||||
GDataBase.__init__(self, text=text)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.uri = uri
|
||||
self.primary = primary
|
||||
|
||||
class OrgName(GDataBase):
|
||||
"""The gd:orgName element."""
|
||||
_tag = 'orgName'
|
||||
|
||||
class OrgTitle(GDataBase):
|
||||
"""The gd:orgTitle element."""
|
||||
_tag = 'orgTitle'
|
||||
|
||||
class OrgSymbol(GDataBase):
|
||||
"""The gd:orgSymbol element."""
|
||||
_tag = 'orgSymbol'
|
||||
|
||||
class OrgDepartment(GDataBase):
|
||||
"""The gd:orgDepartment element."""
|
||||
_tag = 'orgDepartment'
|
||||
|
||||
class OrgJobDescription(GDataBase):
|
||||
"""The gd:orgJobDescription element."""
|
||||
_tag = 'orgJobDescription'
|
||||
|
||||
class Where(GDataBase):
|
||||
"""The gd:where element."""
|
||||
_tag = 'where'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['valueString'] = 'value_string'
|
||||
|
||||
def __init__(self, value_string=None):
|
||||
GDataBase.__init__(self)
|
||||
self.value_string = value_string
|
||||
|
||||
class Organization(GDataBase):
|
||||
"""The gd:organization element."""
|
||||
_tag = 'organization'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['primary'] = 'primary'
|
||||
_children['{%s}orgName' % GDataBase._namespace] = ('name', OrgName)
|
||||
_children['{%s}orgSymbol' % GDataBase._namespace] = ('symbol', OrgSymbol)
|
||||
_children['{%s}orgTitle' % GDataBase._namespace] = ('title', OrgTitle)
|
||||
_children['{%s}orgDepartment' % GDataBase._namespace] = ('department', OrgDepartment)
|
||||
_children['{%s}orgJobDescription' % GDataBase._namespace] = ('job_description', OrgJobDescription)
|
||||
_children['{%s}where' % GDataBase._namespace] = ('where', Where)
|
||||
|
||||
def __init__(self, label=None, rel=None, primary='false', name=None,
|
||||
title=None, symbol=None, department=None, job_description=None, where=None,):
|
||||
GDataBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.primary = primary
|
||||
self.name = name
|
||||
self.symbol = symbol
|
||||
self.title = title
|
||||
self.department = department
|
||||
self.job_description = job_description
|
||||
self.where = where
|
||||
|
||||
class PostalAddress(GDataBase):
|
||||
"""The gd:postalAddress element."""
|
||||
_tag = 'postalAddress'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['primary'] = 'primary'
|
||||
|
||||
def __init__(self, primary=None, rel=None, label=None, text=None):
|
||||
GDataBase.__init__(self, text=text)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.primary = primary
|
||||
|
||||
class Priority(ContactsBase):
|
||||
"""The gContact:priority element."""
|
||||
_tag = 'priority'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, rel=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.rel = rel
|
||||
|
||||
class Relation(ContactsBase):
|
||||
"""The gContact:relation element."""
|
||||
_tag = 'relation'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, label=None, rel=None, text=None):
|
||||
ContactsBase.__init__(self, text=text)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
|
||||
def RelationFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Relation, xml_string)
|
||||
|
||||
class Sensitivity(ContactsBase):
|
||||
"""The gContact:sensitivity element."""
|
||||
_tag = 'sensitivity'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, rel=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.rel = rel
|
||||
|
||||
class ShortName(ContactsBase):
|
||||
"""The gContact:shortName element."""
|
||||
_tag = 'shortName'
|
||||
|
||||
class Street(GDataBase):
|
||||
"""The gd:street element.
|
||||
Can be street, avenue, road, etc. This element also includes the
|
||||
house number and room/apartment/flat/floor number.
|
||||
"""
|
||||
_tag = 'street'
|
||||
|
||||
class PoBox(GDataBase):
|
||||
"""The gd:pobox element.
|
||||
Covers actual P.O. boxes, drawers, locked bags, etc. This is usually
|
||||
but not always mutually exclusive with street.
|
||||
"""
|
||||
_tag = 'pobox'
|
||||
|
||||
class Neighborhood(GDataBase):
|
||||
"""The gd:neighborhood element.
|
||||
This is used to disambiguate a street address when a city contains more
|
||||
than one street with the same name, or to specify a small place whose
|
||||
mail is routed through a larger postal town. In China it could be a
|
||||
county or a minor city.
|
||||
"""
|
||||
_tag = 'neighborhood'
|
||||
|
||||
class City(GDataBase):
|
||||
"""The gd:city element.
|
||||
Can be city, village, town, borough, etc. This is the postal town and
|
||||
not necessarily the place of residence or place of business.
|
||||
"""
|
||||
_tag = 'city'
|
||||
|
||||
class Region(GDataBase):
|
||||
"""The gd:region element.
|
||||
A state, province, county (in Ireland), Land (in Germany),
|
||||
departement (in France), etc.
|
||||
"""
|
||||
_tag = 'region'
|
||||
|
||||
class Postcode(GDataBase):
|
||||
"""The gd:postcode element.
|
||||
Postal code. Usually country-wide, but sometimes specific to the
|
||||
city (e.g. "2" in "Dublin 2, Ireland" addresses).
|
||||
"""
|
||||
_tag = 'postcode'
|
||||
|
||||
class Country(GDataBase):
|
||||
"""The gd:country element.
|
||||
The name or code of the country.
|
||||
"""
|
||||
_tag = 'country'
|
||||
|
||||
class FormattedAddress(GDataBase):
|
||||
"""The gd:formattedAddress element."""
|
||||
_tag = 'formattedAddress'
|
||||
|
||||
class StructuredPostalAddress(GDataBase):
|
||||
"""The gd:structuredPostalAddress element."""
|
||||
_tag = 'structuredPostalAddress'
|
||||
_children = GDataBase._children.copy()
|
||||
_attributes = GDataBase._attributes.copy()
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['rel'] = 'rel'
|
||||
_attributes['primary'] = 'primary'
|
||||
_children['{%s}street' % GDataBase._namespace] = ('street', Street)
|
||||
_children['{%s}pobox' % GDataBase._namespace] = ('pobox', PoBox)
|
||||
_children['{%s}neighborhood' % GDataBase._namespace] = ('neighborhood', Neighborhood)
|
||||
_children['{%s}city' % GDataBase._namespace] = ('city', City)
|
||||
_children['{%s}region' % GDataBase._namespace] = ('region', Region)
|
||||
_children['{%s}postcode' % GDataBase._namespace] = ('postcode', Postcode)
|
||||
_children['{%s}country' % GDataBase._namespace] = ('country', Country)
|
||||
_children['{%s}formattedAddress' % GDataBase._namespace] = ('formatted_address', FormattedAddress)
|
||||
|
||||
def __init__(self, rel=None, label=None, primary='false',
|
||||
street=None,
|
||||
pobox=None,
|
||||
neighborhood=None,
|
||||
city=None,
|
||||
region=None,
|
||||
postcode=None,
|
||||
country=None,
|
||||
formatted_address=None):
|
||||
GDataBase.__init__(self)
|
||||
self.label = label
|
||||
self.rel = rel
|
||||
self.primary = primary
|
||||
self.street = street
|
||||
self.pobox = pobox
|
||||
self.neighborhood = neighborhood
|
||||
self.city = city
|
||||
self.region = region
|
||||
self.postcode = postcode
|
||||
self.country = country
|
||||
self.formatted_address = formatted_address
|
||||
|
||||
class Subject(ContactsBase):
|
||||
"""The gContact:Subject element."""
|
||||
_tag = 'subject'
|
||||
|
||||
class UserDefinedField(ContactsBase):
|
||||
"""The gContact:userDefinedField element."""
|
||||
_tag = 'userDefinedField'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['key'] = 'key'
|
||||
_attributes['value'] = 'value'
|
||||
|
||||
def __init__(self, key=None, value=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
def UserDefinedFieldFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(UserDefinedField, xml_string)
|
||||
|
||||
class Website(ContactsBase):
|
||||
"""The gContact:Website element."""
|
||||
_tag = 'website'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['href'] = 'href'
|
||||
_attributes['label'] = 'label'
|
||||
_attributes['primary'] = 'primary'
|
||||
_attributes['rel'] = 'rel'
|
||||
|
||||
def __init__(self, href=None, label=None, primary='false', rel=None):
|
||||
ContactsBase.__init__(self)
|
||||
self.href = href
|
||||
self.label = label
|
||||
self.primary = primary
|
||||
self.rel = rel
|
||||
|
||||
def WebsiteFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(Website, xml_string)
|
||||
|
||||
class PersonEntry(gdata.BatchEntry):
|
||||
"""Base class for ContactEntry and ProfileEntry."""
|
||||
_children = gdata.BatchEntry._children.copy()
|
||||
_children['{%s}billingInformation' % CONTACTS_NAMESPACE] = ('billingInformation', BillingInformation)
|
||||
_children['{%s}birthday' % CONTACTS_NAMESPACE] = ('birthday', Birthday)
|
||||
_children['{%s}calendarLink' % CONTACTS_NAMESPACE] = ('calendarLink', [CalendarLink])
|
||||
_children['{%s}content' % atom.ATOM_NAMESPACE] = ('content', Content)
|
||||
_children['{%s}directoryServer' % CONTACTS_NAMESPACE] = ('directoryServer', DirectoryServer)
|
||||
_children['{%s}email' % gdata.GDATA_NAMESPACE] = ('email', [Email])
|
||||
_children['{%s}event' % CONTACTS_NAMESPACE] = ('event', [Event])
|
||||
_children['{%s}externalId' % CONTACTS_NAMESPACE] = ('externalId', [ExternalId])
|
||||
_children['{%s}gender' % CONTACTS_NAMESPACE] = ('gender', Gender)
|
||||
_children['{%s}hobby' % CONTACTS_NAMESPACE] = ('hobby', [Hobby])
|
||||
_children['{%s}im' % gdata.GDATA_NAMESPACE] = ('im', [IM])
|
||||
_children['{%s}initials' % CONTACTS_NAMESPACE] = ('initials', Initials)
|
||||
_children['{%s}jot' % CONTACTS_NAMESPACE] = ('jot', [Jot])
|
||||
_children['{%s}language' % CONTACTS_NAMESPACE] = ('language', Language)
|
||||
_children['{%s}maidenName' % CONTACTS_NAMESPACE] = ('maidenName', MaidenName)
|
||||
_children['{%s}mileage' % CONTACTS_NAMESPACE] = ('mileage', Mileage)
|
||||
_children['{%s}name' % gdata.GDATA_NAMESPACE] = ('name', Name)
|
||||
_children['{%s}nickname' % CONTACTS_NAMESPACE] = ('nickname', Nickname)
|
||||
_children['{%s}occupation' % CONTACTS_NAMESPACE] = ('occupation', Occupation)
|
||||
_children['{%s}organization' % gdata.GDATA_NAMESPACE] = ('organization', [Organization])
|
||||
_children['{%s}phoneNumber' % gdata.GDATA_NAMESPACE] = ('phoneNumber', [PhoneNumber])
|
||||
_children['{%s}postalAddress' % gdata.GDATA_NAMESPACE] = ('postalAddress', [PostalAddress])
|
||||
_children['{%s}priority' % CONTACTS_NAMESPACE] = ('priority', Priority)
|
||||
_children['{%s}relation' % CONTACTS_NAMESPACE] = ('relation', [Relation])
|
||||
_children['{%s}sensitivity' % CONTACTS_NAMESPACE] = ('sensitivity', Sensitivity)
|
||||
_children['{%s}shortName' % CONTACTS_NAMESPACE] = ('shortName', ShortName)
|
||||
_children['{%s}structuredPostalAddress' % gdata.GDATA_NAMESPACE] = ('structuredPostalAddress', [StructuredPostalAddress])
|
||||
_children['{%s}subject' % CONTACTS_NAMESPACE] = ('subject', Subject)
|
||||
_children['{%s}userDefinedField' % CONTACTS_NAMESPACE] = ('userDefinedField', [UserDefinedField])
|
||||
_children['{%s}website' % CONTACTS_NAMESPACE] = ('website', [Website])
|
||||
_children['{%s}where' % gdata.GDATA_NAMESPACE] = ('where', Where)
|
||||
|
||||
_attributes = gdata.BatchEntry._attributes.copy()
|
||||
_attributes['{%s}etag' % gdata.GDATA_NAMESPACE] = 'etag'
|
||||
|
||||
def __init__(self,
|
||||
billingInformation=None,
|
||||
birthday=None,
|
||||
calendarLink=None,
|
||||
content=None,
|
||||
directoryServer=None,
|
||||
email=None,
|
||||
event=None,
|
||||
externalId=None,
|
||||
gender=None,
|
||||
hobby=None,
|
||||
im=None,
|
||||
initials=None,
|
||||
jot=None,
|
||||
language=None,
|
||||
maidenName=None,
|
||||
mileage=None,
|
||||
name=None,
|
||||
nickname=None,
|
||||
occupation=None,
|
||||
organization=None,
|
||||
phoneNumber=None,
|
||||
postalAddress=None,
|
||||
priority=None,
|
||||
relation=None,
|
||||
sensitivity=None,
|
||||
shortName=None,
|
||||
structuredPostalAddress=None,
|
||||
subject=None,
|
||||
text=None,
|
||||
title=None,
|
||||
userDefinedField=None,
|
||||
website=None,
|
||||
where=None,
|
||||
etag=None):
|
||||
gdata.BatchEntry.__init__(self)
|
||||
self.billingInformation = billingInformation
|
||||
self.birthday = birthday
|
||||
self.calendarLink = calendarLink or []
|
||||
self.content = content
|
||||
self.directoryServer = directoryServer
|
||||
self.email = email or []
|
||||
self.event = event or []
|
||||
self.externalId = externalId or []
|
||||
self.gender = gender
|
||||
self.hobby = hobby or []
|
||||
self.im = im or []
|
||||
self.initials = initials
|
||||
self.jot = jot or []
|
||||
self.language = language
|
||||
self.maidenName = maidenName
|
||||
self.mileage = mileage
|
||||
self.name = name
|
||||
self.nickname = nickname
|
||||
self.occupation = occupation
|
||||
self.organization = organization or []
|
||||
self.phoneNumber = phoneNumber or []
|
||||
self.postalAddress = postalAddress or []
|
||||
self.priority = priority
|
||||
self.relation = relation or []
|
||||
self.sensitivity = sensitivity
|
||||
self.shortName = shortName
|
||||
self.structuredPostalAddress = structuredPostalAddress or []
|
||||
self.subject = subject
|
||||
self.text = text
|
||||
self.userDefinedField = userDefinedField or []
|
||||
self.website = website or []
|
||||
self.where = where
|
||||
self.extension_attributes = {}
|
||||
self.extension_elements = []
|
||||
self.etag = etag
|
||||
|
||||
class Deleted(GDataBase):
|
||||
"""The gd:Deleted element."""
|
||||
_tag = 'deleted'
|
||||
|
||||
class GroupMembershipInfo(ContactsBase):
|
||||
"""The Google Contacts GroupMembershipInfo element."""
|
||||
_tag = 'groupMembershipInfo'
|
||||
_children = ContactsBase._children.copy()
|
||||
_attributes = ContactsBase._attributes.copy()
|
||||
_attributes['deleted'] = 'deleted'
|
||||
_attributes['href'] = 'href'
|
||||
|
||||
def __init__(self, deleted=None, href=None, text=None):
|
||||
ContactsBase.__init__(self, text=text)
|
||||
self.deleted = deleted
|
||||
self.href = href
|
||||
|
||||
class ContactEntry(PersonEntry):
|
||||
"""Represents a contact."""
|
||||
_tag = 'entry'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = PersonEntry._children.copy()
|
||||
|
||||
_children['{%s}deleted' % gdata.GDATA_NAMESPACE] = ('deleted', Deleted)
|
||||
_children['{%s}groupMembershipInfo' % CONTACTS_NAMESPACE] = ('groupMembershipInfo', [GroupMembershipInfo])
|
||||
_children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ('extended_property', [gdata.ExtendedProperty])
|
||||
# Overwrite the organization rule in PersonEntry so that a ContactEntry
|
||||
# may only contain one <gd:organization> element.
|
||||
#_children['{%s}organization' % gdata.GDATA_NAMESPACE] = ('organization', Organization)
|
||||
|
||||
def __init__(self,
|
||||
billingInformation=None,
|
||||
birthday=None,
|
||||
calendarLink=None,
|
||||
content=None,
|
||||
deleted=None,
|
||||
directoryServer=None,
|
||||
email=None,
|
||||
event=None,
|
||||
extended_property=None,
|
||||
externalId=None,
|
||||
gender=None,
|
||||
groupMembershipInfo=None,
|
||||
hobby=None,
|
||||
im=None,
|
||||
initials=None,
|
||||
jot=None,
|
||||
language=None,
|
||||
maidenName=None,
|
||||
mileage=None,
|
||||
name=None,
|
||||
nickname=None,
|
||||
occupation=None,
|
||||
organization=None,
|
||||
phoneNumber=None,
|
||||
postalAddress=None,
|
||||
priority=None,
|
||||
relation=None,
|
||||
sensitivity=None,
|
||||
shortName=None,
|
||||
structuredPostalAddress=None,
|
||||
subject=None,
|
||||
text=None,
|
||||
title=None,
|
||||
userDefinedField=None,
|
||||
website=None,
|
||||
where=None,
|
||||
etag=None):
|
||||
PersonEntry.__init__(self,
|
||||
billingInformation=billingInformation,
|
||||
birthday=birthday,
|
||||
calendarLink=calendarLink,
|
||||
content=content,
|
||||
directoryServer=directoryServer,
|
||||
email=email,
|
||||
event=event,
|
||||
externalId=externalId,
|
||||
gender=gender,
|
||||
hobby=hobby,
|
||||
im=im,
|
||||
initials=initials,
|
||||
jot=jot,
|
||||
language=language,
|
||||
maidenName=maidenName,
|
||||
mileage=mileage,
|
||||
name=name,
|
||||
nickname=nickname,
|
||||
occupation=occupation,
|
||||
organization=organization,
|
||||
phoneNumber=phoneNumber,
|
||||
postalAddress=postalAddress,
|
||||
priority=priority,
|
||||
relation=relation,
|
||||
sensitivity=sensitivity,
|
||||
shortName=shortName,
|
||||
structuredPostalAddress=structuredPostalAddress,
|
||||
subject=subject,
|
||||
text=text,
|
||||
title=title,
|
||||
userDefinedField=userDefinedField,
|
||||
website=website,
|
||||
where=where,
|
||||
etag=etag)
|
||||
self.deleted = deleted
|
||||
self.extended_property = extended_property or []
|
||||
self.groupMembershipInfo = groupMembershipInfo or []
|
||||
|
||||
def GetPhotoLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == PHOTO_LINK_REL:
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def GetPhotoEditLink(self):
|
||||
for a_link in self.link:
|
||||
if a_link.rel == PHOTO_EDIT_LINK_REL:
|
||||
return a_link
|
||||
return None
|
||||
|
||||
def ContactEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ContactEntry, xml_string)
|
||||
|
||||
class ContactsFeed(gdata.BatchFeed, gdata.LinkFinder):
|
||||
"""A Google contacts feed flavor of an Atom Feed."""
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.BatchFeed._children.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [ContactEntry])
|
||||
|
||||
def __init__(self):
|
||||
gdata.BatchFeed.__init__(self)
|
||||
|
||||
def ContactsFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(ContactsFeed, xml_string)
|
||||
|
||||
class GroupEntry(gdata.BatchEntry):
|
||||
"""Represents a contact group."""
|
||||
_children = gdata.BatchEntry._children.copy()
|
||||
_children['{%s}deleted' % gdata.GDATA_NAMESPACE] = ('deleted', Deleted)
|
||||
_children['{%s}extendedProperty' % gdata.GDATA_NAMESPACE] = ('extended_property', [gdata.ExtendedProperty])
|
||||
|
||||
_attributes = gdata.BatchEntry._attributes.copy()
|
||||
_attributes['{%s}etag' % gdata.GDATA_NAMESPACE] = 'etag'
|
||||
|
||||
def __init__(self,
|
||||
title=None,
|
||||
deleted=None,
|
||||
extended_property=None,
|
||||
etag=None):
|
||||
gdata.BatchEntry.__init__(self)
|
||||
self.title = title
|
||||
self.deleted = deleted
|
||||
self.extended_property = extended_property or []
|
||||
self.etag = etag
|
||||
|
||||
def GroupEntryFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(GroupEntry, xml_string)
|
||||
|
||||
class GroupsFeed(gdata.BatchFeed):
|
||||
"""A Google contact groups feed flavor of an Atom Feed."""
|
||||
_tag = 'feed'
|
||||
_namespace = atom.ATOM_NAMESPACE
|
||||
_children = gdata.BatchFeed._children.copy()
|
||||
_children['{%s}entry' % atom.ATOM_NAMESPACE] = ('entry', [GroupEntry])
|
||||
|
||||
def __init__(self):
|
||||
gdata.BatchFeed.__init__(self)
|
||||
|
||||
def GroupsFeedFromString(xml_string):
|
||||
return atom.CreateClassFromXMLString(GroupsFeed, xml_string)
|
||||
@@ -1,355 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2009 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""ContactsService extends the GDataService for Google Contacts operations.
|
||||
|
||||
ContactsService: Provides methods to query feeds and manipulate items.
|
||||
Extends GDataService.
|
||||
"""
|
||||
|
||||
|
||||
import gdata.apps
|
||||
import gdata.apps.service
|
||||
import gdata.service
|
||||
|
||||
|
||||
class ContactsService(gdata.service.GDataService):
|
||||
"""Client for the Google Contacts service."""
|
||||
|
||||
def __init__(self, email=None, password=None, source=None,
|
||||
server='www.google.com', additional_headers=None,
|
||||
contact_list='default', contactFeed=True, **kwargs):
|
||||
"""Creates a client for the Contacts service.
|
||||
|
||||
Args:
|
||||
email: string (optional) The user's email address, used for
|
||||
authentication.
|
||||
password: string (optional) The user's password.
|
||||
source: string (optional) The name of the user's application.
|
||||
server: string (optional) The name of the server to which a connection
|
||||
will be opened. Default value: 'www.google.com'.
|
||||
contact_list: string (optional) The name of the default contact list to
|
||||
use when no URI is specified to the methods of the service.
|
||||
Default value: 'default' (the logged in user's contact list).
|
||||
contactFeed: Boolean (optional) Is this contacts feed or a gal feed
|
||||
Default value: True (the logged in user's contact list).
|
||||
**kwargs: The other parameters to pass to gdata.service.GDataService
|
||||
constructor.
|
||||
"""
|
||||
|
||||
self.contact_list = contact_list
|
||||
self.feed_type = ['gal', 'contacts'][contactFeed]
|
||||
if additional_headers == None:
|
||||
additional_headers = {}
|
||||
additional_headers['GData-Version'] = ['1.1', '3.1'][contactFeed]
|
||||
gdata.service.GDataService.__init__(self,
|
||||
email=email, password=password, service='cp', source=source,
|
||||
server=server, additional_headers=additional_headers, **kwargs)
|
||||
self.ssl = True
|
||||
self.port = 443
|
||||
|
||||
def _CleanUri(self, uri):
|
||||
"""Sanitizes a feed URI.
|
||||
|
||||
Args:
|
||||
uri: The URI to sanitize, can be relative or absolute.
|
||||
|
||||
Returns:
|
||||
The given URI without its https://server prefix, if any.
|
||||
Keeps the leading slash of the URI.
|
||||
"""
|
||||
url_prefix = 'https://%s' % self.server
|
||||
if uri.startswith(url_prefix):
|
||||
uri = uri[len(url_prefix):]
|
||||
return uri
|
||||
|
||||
def GetContactFeedUri(self, contact_list=None, projection='full', contactId=None):
|
||||
"""Builds a contact feed URI. """
|
||||
contact_list = contact_list or self.contact_list
|
||||
uri = 'https://{0}/m8/feeds/{1}/{2}/{3}'.format(self.server, self.feed_type, contact_list, projection)
|
||||
if contactId:
|
||||
uri += '/{0}'.format(contactId)
|
||||
return uri
|
||||
|
||||
def GetContactsFeed(self, uri=None,
|
||||
extra_headers=None, url_params=None, escape_params=True):
|
||||
uri = uri or self.GetContactFeedUri()
|
||||
try:
|
||||
return self.Get(uri,
|
||||
url_params=url_params, extra_headers=extra_headers, escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.ContactsFeedFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetContact(self, uri):
|
||||
try:
|
||||
return self.Get(uri, converter=gdata.apps.contacts.ContactEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateContact(self, new_contact, insert_uri=None, url_params=None,
|
||||
escape_params=True):
|
||||
"""Adds an new contact to Google Contacts.
|
||||
|
||||
Args:
|
||||
new_contact: atom.Entry or subclass A new contact which is to be added to
|
||||
Google Contacts.
|
||||
insert_uri: the URL to post new contacts to the feed
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the insertion request.
|
||||
escape_params: boolean (optional) If true, the url_parameters will be
|
||||
escaped before they are included in the request.
|
||||
|
||||
Returns:
|
||||
On successful insert, an entry containing the contact created
|
||||
On failure, a RequestError is raised of the form:
|
||||
{'status': HTTP status code from server,
|
||||
'reason': HTTP reason from the server,
|
||||
'body': HTTP body of the server's response}
|
||||
"""
|
||||
insert_uri = insert_uri or self.GetContactFeedUri()
|
||||
try:
|
||||
return self.Post(new_contact, insert_uri, url_params=url_params,
|
||||
escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.ContactEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def UpdateContact(self, edit_uri, updated_contact, extra_headers=None, url_params=None,
|
||||
escape_params=True):
|
||||
"""Updates an existing contact.
|
||||
|
||||
Args:
|
||||
edit_uri: string The edit link URI for the element being updated
|
||||
updated_contact: string, atom.Entry or subclass containing
|
||||
the Atom Entry which will replace the contact which is
|
||||
stored at the edit_url
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the update request.
|
||||
escape_params: boolean (optional) If true, the url_parameters will be
|
||||
escaped before they are included in the request.
|
||||
|
||||
Returns:
|
||||
On successful update, a httplib.HTTPResponse containing the server's
|
||||
response to the PUT request.
|
||||
On failure, a RequestError is raised of the form:
|
||||
{'status': HTTP status code from server,
|
||||
'reason': HTTP reason from the server,
|
||||
'body': HTTP body of the server's response}
|
||||
"""
|
||||
try:
|
||||
return self.Put(updated_contact, self._CleanUri(edit_uri),
|
||||
url_params=url_params, extra_headers=extra_headers,
|
||||
escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.ContactEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteContact(self, edit_uri, extra_headers=None,
|
||||
url_params=None, escape_params=True):
|
||||
"""Removes an contact with the specified ID from Google Contacts.
|
||||
|
||||
Args:
|
||||
edit_uri: string The edit URL of the entry to be deleted. Example:
|
||||
'/m8/feeds/contacts/default/full/xxx/yyy'
|
||||
extra_headers: dict (optional)
|
||||
url_params: dict (optional) Additional URL parameters to be included
|
||||
in the deletion request.
|
||||
escape_params: boolean (optional) If true, the url_parameters will be
|
||||
escaped before they are included in the request.
|
||||
|
||||
Returns:
|
||||
On successful delete, a httplib.HTTPResponse containing the server's
|
||||
response to the DELETE request.
|
||||
On failure, a RequestError is raised of the form:
|
||||
{'status': HTTP status code from server,
|
||||
'reason': HTTP reason from the server,
|
||||
'body': HTTP body of the server's response}
|
||||
"""
|
||||
try:
|
||||
return self.Delete(self._CleanUri(edit_uri),
|
||||
url_params=url_params, escape_params=escape_params, extra_headers=extra_headers)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def ChangePhoto(self, media, contact_entry_or_url, content_type=None,
|
||||
content_length=None, extra_headers=None):
|
||||
"""Change the photo for the contact by uploading a new photo.
|
||||
|
||||
Performs a PUT against the photo edit URL to send the binary data for the
|
||||
photo.
|
||||
|
||||
Args:
|
||||
media: filename, file-like-object, or a gdata.MediaSource object to send.
|
||||
contact_entry_or_url: ContactEntry or str If it is a ContactEntry, this
|
||||
method will search for an edit photo link URL and
|
||||
perform a PUT to the URL.
|
||||
content_type: str (optional) the mime type for the photo data. This is
|
||||
necessary if media is a file or file name, but if media
|
||||
is a MediaSource object then the media object can contain
|
||||
the mime type. If media_type is set, it will override the
|
||||
mime type in the media object.
|
||||
content_length: int or str (optional) Specifying the content length is
|
||||
only required if media is a file-like object. If media
|
||||
is a filename, the length is determined using
|
||||
os.path.getsize. If media is a MediaSource object, it is
|
||||
assumed that it already contains the content length.
|
||||
extra_headers: dict (optional)
|
||||
"""
|
||||
if isinstance(contact_entry_or_url, gdata.apps.contacts.ContactEntry):
|
||||
# url = contact_entry_or_url.GetPhotoEditLink().href
|
||||
url = contact_entry_or_url.GetPhotoLink().href
|
||||
else:
|
||||
url = contact_entry_or_url
|
||||
if isinstance(media, gdata.MediaSource):
|
||||
payload = media
|
||||
# If the media object is a file-like object, then use it as the file
|
||||
# handle in the in the MediaSource.
|
||||
elif hasattr(media, 'read'):
|
||||
payload = gdata.MediaSource(file_handle=media,
|
||||
content_type=content_type, content_length=content_length)
|
||||
# Assume that the media object is a file name.
|
||||
else:
|
||||
payload = gdata.MediaSource(content_type=content_type,
|
||||
content_length=content_length, file_path=media)
|
||||
return self.Put(payload, url, extra_headers=extra_headers)
|
||||
|
||||
def GetPhoto(self, contact_entry_or_url):
|
||||
"""Retrives the binary data for the contact's profile photo as a string.
|
||||
|
||||
Args:
|
||||
contact_entry_or_url: a gdata.apps.contacts.ContactEntry object or a string
|
||||
containing the photo link's URL. If the contact entry does not
|
||||
contain a photo link, the image will not be fetched and this method
|
||||
will return None.
|
||||
"""
|
||||
# TODO: add the ability to write out the binary image data to a file,
|
||||
# reading and writing a chunk at a time to avoid potentially using up
|
||||
# large amounts of memory.
|
||||
url = None
|
||||
if isinstance(contact_entry_or_url, gdata.apps.contacts.ContactEntry):
|
||||
photo_link = contact_entry_or_url.GetPhotoLink()
|
||||
if photo_link:
|
||||
url = photo_link.href
|
||||
else:
|
||||
url = contact_entry_or_url
|
||||
if url:
|
||||
try:
|
||||
return self.Get(url, converter=str)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
else:
|
||||
return None
|
||||
|
||||
def DeletePhoto(self, contact_entry_or_url, extra_headers=None):
|
||||
"""Deletes the contact's profile photo.
|
||||
|
||||
Args:
|
||||
contact_entry_or_url: a gdata.apps.contacts.ContactEntry object or a string
|
||||
containing the photo link's URL.
|
||||
will return None.
|
||||
extra_headers: dict (optional)
|
||||
"""
|
||||
url = None
|
||||
if isinstance(contact_entry_or_url, gdata.apps.contacts.ContactEntry):
|
||||
# url = contact_entry_or_url.GetPhotoEditLink().href
|
||||
url = contact_entry_or_url.GetPhotoLink().href
|
||||
else:
|
||||
url = contact_entry_or_url
|
||||
if url:
|
||||
self.Delete(url, extra_headers=extra_headers)
|
||||
|
||||
def ExecuteBatch(self, batch_feed, url,
|
||||
converter=gdata.apps.contacts.ContactsFeedFromString):
|
||||
"""Sends a batch request feed to the server.
|
||||
|
||||
Args:
|
||||
batch_feed: gdata.apps.contacts.ContactFeed A feed containing batch
|
||||
request entries. Each entry contains the operation to be performed
|
||||
on the data contained in the entry. For example an entry with an
|
||||
operation type of insert will be used as if the individual entry
|
||||
had been inserted.
|
||||
url: str The batch URL to which these operations should be applied.
|
||||
converter: Function (optional) The function used to convert the server's
|
||||
response to an object. The default value is ContactsFeedFromString.
|
||||
|
||||
Returns:
|
||||
The results of the batch request's execution on the server. If the
|
||||
default converter is used, this is stored in a ContactsFeed.
|
||||
"""
|
||||
return self.Post(batch_feed, url, converter=converter)
|
||||
|
||||
def GetContactGroupFeedUri(self, contact_list=None, projection='full', groupId=None):
|
||||
"""Builds a contact feed URI. """
|
||||
contact_list = contact_list or self.contact_list
|
||||
uri = 'https://{0}/m8/feeds/groups/{1}/{2}'.format(self.server, contact_list, projection)
|
||||
if groupId:
|
||||
uri += '/{0}'.format(groupId)
|
||||
return uri
|
||||
|
||||
def GetGroupsFeed(self, uri=None,
|
||||
extra_headers=None, url_params=None, escape_params=True):
|
||||
uri = uri or self.GetContactGroupFeedUri()
|
||||
try:
|
||||
return self.Get(uri,
|
||||
url_params=url_params, extra_headers=extra_headers, escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.GroupsFeedFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGroup(self, uri):
|
||||
try:
|
||||
return self.Get(uri, converter=gdata.apps.contacts.GroupEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateGroup(self, new_group, insert_uri=None, url_params=None,
|
||||
escape_params=True):
|
||||
insert_uri = insert_uri or self.GetContactGroupFeedUri()
|
||||
try:
|
||||
return self.Post(new_group, insert_uri, url_params=url_params,
|
||||
escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.GroupEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def UpdateGroup(self, edit_uri, updated_group, extra_headers=None, url_params=None,
|
||||
escape_params=True):
|
||||
try:
|
||||
return self.Put(updated_group, self._CleanUri(edit_uri),
|
||||
url_params=url_params, extra_headers=extra_headers,
|
||||
escape_params=escape_params,
|
||||
converter=gdata.apps.contacts.GroupEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteGroup(self, edit_uri, extra_headers=None,
|
||||
url_params=None, escape_params=True):
|
||||
try:
|
||||
return self.Delete(self._CleanUri(edit_uri),
|
||||
url_params=url_params, escape_params=escape_params, extra_headers=extra_headers)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
class ContactsQuery(gdata.service.Query):
|
||||
|
||||
def __init__(self, feed=None, text_query=None, params=None,
|
||||
categories=None, group=None):
|
||||
self.feed = feed or '/m8/feeds/contacts/default/full'
|
||||
if group:
|
||||
self['group'] = group
|
||||
gdata.service.Query.__init__(self, feed=self.feed, text_query=text_query,
|
||||
params=params, categories=categories)
|
||||
@@ -1,544 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2007 SIOS Technology, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
__author__ = 'tmatsuo@sios.com (Takashi MATSUO)'
|
||||
|
||||
import lxml.etree as ElementTree
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import gdata
|
||||
import atom.service
|
||||
import gdata.service
|
||||
import gdata.apps
|
||||
import atom
|
||||
|
||||
API_VER="2.0"
|
||||
HTTP_OK=200
|
||||
|
||||
UNKOWN_ERROR=1000
|
||||
USER_DELETED_RECENTLY=1100
|
||||
USER_SUSPENDED=1101
|
||||
DOMAIN_USER_LIMIT_EXCEEDED=1200
|
||||
DOMAIN_ALIAS_LIMIT_EXCEEDED=1201
|
||||
DOMAIN_SUSPENDED=1202
|
||||
DOMAIN_FEATURE_UNAVAILABLE=1203
|
||||
ENTITY_EXISTS=1300
|
||||
ENTITY_DOES_NOT_EXIST=1301
|
||||
ENTITY_NAME_IS_RESERVED=1302
|
||||
ENTITY_NAME_NOT_VALID=1303
|
||||
INVALID_GIVEN_NAME=1400
|
||||
INVALID_FAMILY_NAME=1401
|
||||
INVALID_PASSWORD=1402
|
||||
INVALID_USERNAME=1403
|
||||
INVALID_HASH_FUNCTION_NAME=1404
|
||||
INVALID_HASH_DIGGEST_LENGTH=1405
|
||||
INVALID_EMAIL_ADDRESS=1406
|
||||
INVALID_QUERY_PARAMETER_VALUE=1407
|
||||
TOO_MANY_RECIPIENTS_ON_EMAIL_LIST=1500
|
||||
|
||||
DEFAULT_QUOTA_LIMIT='2048'
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AppsForYourDomainException(Error):
|
||||
|
||||
def __init__(self, response):
|
||||
|
||||
Error.__init__(self, response)
|
||||
try:
|
||||
self.element_tree = ElementTree.fromstring(response['body'])
|
||||
self.error_code = int(self.element_tree[0].attrib['errorCode'])
|
||||
self.reason = self.element_tree[0].attrib['reason']
|
||||
self.invalidInput = self.element_tree[0].attrib['invalidInput']
|
||||
except:
|
||||
self.error_code = 600
|
||||
|
||||
class AppsService(gdata.service.GDataService):
|
||||
"""Client for the Google Apps Provisioning service."""
|
||||
|
||||
def __init__(self, email=None, password=None, domain=None, source=None,
|
||||
server='apps-apis.google.com', additional_headers=None,
|
||||
**kwargs):
|
||||
"""Creates a client for the Google Apps Provisioning service.
|
||||
|
||||
Args:
|
||||
email: string (optional) The user's email address, used for
|
||||
authentication.
|
||||
password: string (optional) The user's password.
|
||||
domain: string (optional) The Google Apps domain name.
|
||||
source: string (optional) The name of the user's application.
|
||||
server: string (optional) The name of the server to which a connection
|
||||
will be opened. Default value: 'apps-apis.google.com'.
|
||||
**kwargs: The other parameters to pass to gdata.service.GDataService
|
||||
constructor.
|
||||
"""
|
||||
gdata.service.GDataService.__init__(
|
||||
self, email=email, password=password, service='apps', source=source,
|
||||
server=server, additional_headers=additional_headers, **kwargs)
|
||||
self.ssl = True
|
||||
self.port = 443
|
||||
self.domain = domain
|
||||
|
||||
def _baseURL(self):
|
||||
return "/a/feeds/%s" % self.domain
|
||||
|
||||
def AddAllElementsFromAllPages(self, link_finder, func):
|
||||
"""retrieve all pages and add all elements"""
|
||||
next = link_finder.GetNextLink()
|
||||
while next is not None:
|
||||
next_feed = self.Get(next.href, converter=func)
|
||||
for a_entry in next_feed.entry:
|
||||
link_finder.entry.append(a_entry)
|
||||
next = next_feed.GetNextLink()
|
||||
return link_finder
|
||||
|
||||
def RetrievePageOfEmailLists(self, start_email_list_name=None,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve one page of email list"""
|
||||
uri = "%s/emailList/%s" % (self._baseURL(), API_VER)
|
||||
if start_email_list_name is not None:
|
||||
uri += "?startEmailListName=%s" % start_email_list_name
|
||||
try:
|
||||
return gdata.apps.EmailListFeedFromString(str(self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGeneratorForAllEmailLists(
|
||||
self, num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all emaillists in this domain."""
|
||||
first_page = self.RetrievePageOfEmailLists(num_retries=num_retries,
|
||||
delay=delay,
|
||||
backoff=backoff)
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.EmailListRecipientFeedFromString,
|
||||
num_retries=num_retries, delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveAllEmailLists(self):
|
||||
"""Retrieve all email list of a domain."""
|
||||
|
||||
ret = self.RetrievePageOfEmailLists()
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.EmailListFeedFromString)
|
||||
|
||||
def RetrieveEmailList(self, list_name):
|
||||
"""Retreive a single email list by the list's name."""
|
||||
|
||||
uri = "%s/emailList/%s/%s" % (
|
||||
self._baseURL(), API_VER, list_name)
|
||||
try:
|
||||
return self.Get(uri, converter=gdata.apps.EmailListEntryFromString)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def RetrieveEmailLists(self, recipient):
|
||||
"""Retrieve All Email List Subscriptions for an Email Address."""
|
||||
|
||||
uri = "%s/emailList/%s?recipient=%s" % (
|
||||
self._baseURL(), API_VER, recipient)
|
||||
try:
|
||||
ret = gdata.apps.EmailListFeedFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.EmailListFeedFromString)
|
||||
|
||||
def RemoveRecipientFromEmailList(self, recipient, list_name):
|
||||
"""Remove recipient from email list."""
|
||||
|
||||
uri = "%s/emailList/%s/%s/recipient/%s" % (
|
||||
self._baseURL(), API_VER, list_name, recipient)
|
||||
try:
|
||||
self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def RetrievePageOfRecipients(self, list_name, start_recipient=None,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve one page of recipient of an email list. """
|
||||
|
||||
uri = "%s/emailList/%s/%s/recipient" % (
|
||||
self._baseURL(), API_VER, list_name)
|
||||
|
||||
if start_recipient is not None:
|
||||
uri += "?startRecipient=%s" % start_recipient
|
||||
try:
|
||||
return gdata.apps.EmailListRecipientFeedFromString(str(
|
||||
self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGeneratorForAllRecipients(
|
||||
self, list_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all recipients of a particular emaillist."""
|
||||
first_page = self.RetrievePageOfRecipients(list_name,
|
||||
num_retries=num_retries,
|
||||
delay=delay,
|
||||
backoff=backoff)
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.EmailListRecipientFeedFromString,
|
||||
num_retries=num_retries, delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveAllRecipients(self, list_name):
|
||||
"""Retrieve all recipient of an email list."""
|
||||
|
||||
ret = self.RetrievePageOfRecipients(list_name)
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.EmailListRecipientFeedFromString)
|
||||
|
||||
def AddRecipientToEmailList(self, recipient, list_name):
|
||||
"""Add a recipient to a email list."""
|
||||
|
||||
uri = "%s/emailList/%s/%s/recipient" % (
|
||||
self._baseURL(), API_VER, list_name)
|
||||
recipient_entry = gdata.apps.EmailListRecipientEntry()
|
||||
recipient_entry.who = gdata.apps.Who(email=recipient)
|
||||
|
||||
try:
|
||||
return gdata.apps.EmailListRecipientEntryFromString(
|
||||
str(self.Post(recipient_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteEmailList(self, list_name):
|
||||
"""Delete a email list"""
|
||||
|
||||
uri = "%s/emailList/%s/%s" % (self._baseURL(), API_VER, list_name)
|
||||
try:
|
||||
self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateEmailList(self, list_name):
|
||||
"""Create a email list. """
|
||||
|
||||
uri = "%s/emailList/%s" % (self._baseURL(), API_VER)
|
||||
email_list_entry = gdata.apps.EmailListEntry()
|
||||
email_list_entry.email_list = gdata.apps.EmailList(name=list_name)
|
||||
try:
|
||||
return gdata.apps.EmailListEntryFromString(
|
||||
str(self.Post(email_list_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteNickname(self, nickname):
|
||||
"""Delete a nickname"""
|
||||
|
||||
uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname)
|
||||
try:
|
||||
self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def RetrievePageOfNicknames(self, start_nickname=None,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve one page of nicknames in the domain"""
|
||||
|
||||
uri = "%s/nickname/%s" % (self._baseURL(), API_VER)
|
||||
if start_nickname is not None:
|
||||
uri += "?startNickname=%s" % start_nickname
|
||||
try:
|
||||
return gdata.apps.NicknameFeedFromString(str(self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGeneratorForAllNicknames(
|
||||
self, num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all nicknames in this domain."""
|
||||
first_page = self.RetrievePageOfNicknames(num_retries=num_retries,
|
||||
delay=delay,
|
||||
backoff=backoff)
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries,
|
||||
delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveAllNicknames(self):
|
||||
"""Retrieve all nicknames in the domain"""
|
||||
|
||||
ret = self.RetrievePageOfNicknames()
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.NicknameFeedFromString)
|
||||
|
||||
def GetGeneratorForAllNicknamesOfAUser(
|
||||
self, user_name, num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY, backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all nicknames of a particular user."""
|
||||
uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
first_page = gdata.apps.NicknameFeedFromString(str(self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.NicknameFeedFromString, num_retries=num_retries,
|
||||
delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveNicknames(self, user_name):
|
||||
"""Retrieve nicknames of the user"""
|
||||
|
||||
uri = "%s/nickname/%s?username=%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
ret = gdata.apps.NicknameFeedFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.NicknameFeedFromString)
|
||||
|
||||
def RetrieveNickname(self, nickname):
|
||||
"""Retrieve a nickname.
|
||||
|
||||
Args:
|
||||
nickname: string The nickname to retrieve
|
||||
|
||||
Returns:
|
||||
gdata.apps.NicknameEntry
|
||||
"""
|
||||
|
||||
uri = "%s/nickname/%s/%s" % (self._baseURL(), API_VER, nickname)
|
||||
try:
|
||||
return gdata.apps.NicknameEntryFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateNickname(self, user_name, nickname):
|
||||
"""Create a nickname"""
|
||||
|
||||
uri = "%s/nickname/%s" % (self._baseURL(), API_VER)
|
||||
nickname_entry = gdata.apps.NicknameEntry()
|
||||
nickname_entry.login = gdata.apps.Login(user_name=user_name)
|
||||
nickname_entry.nickname = gdata.apps.Nickname(name=nickname)
|
||||
|
||||
try:
|
||||
return gdata.apps.NicknameEntryFromString(
|
||||
str(self.Post(nickname_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def DeleteUser(self, user_name):
|
||||
"""Delete a user account"""
|
||||
|
||||
uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
return self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def UpdateUser(self, user_name, user_entry):
|
||||
"""Update a user account."""
|
||||
|
||||
uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
return gdata.apps.UserEntryFromString(str(self.Put(user_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def CreateUser(self, user_name, family_name, given_name, password,
|
||||
suspended='false', quota_limit=None,
|
||||
password_hash_function=None,
|
||||
change_password=None):
|
||||
"""Create a user account. """
|
||||
|
||||
uri = "%s/user/%s" % (self._baseURL(), API_VER)
|
||||
user_entry = gdata.apps.UserEntry()
|
||||
user_entry.login = gdata.apps.Login(
|
||||
user_name=user_name, password=password, suspended=suspended,
|
||||
hash_function_name=password_hash_function,
|
||||
change_password=change_password)
|
||||
user_entry.name = gdata.apps.Name(family_name=family_name,
|
||||
given_name=given_name)
|
||||
if quota_limit is not None:
|
||||
user_entry.quota = gdata.apps.Quota(limit=str(quota_limit))
|
||||
|
||||
try:
|
||||
return gdata.apps.UserEntryFromString(str(self.Post(user_entry, uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def SuspendUser(self, user_name):
|
||||
user_entry = self.RetrieveUser(user_name)
|
||||
if user_entry.login.suspended != 'true':
|
||||
user_entry.login.suspended = 'true'
|
||||
user_entry = self.UpdateUser(user_name, user_entry)
|
||||
return user_entry
|
||||
|
||||
def RestoreUser(self, user_name):
|
||||
user_entry = self.RetrieveUser(user_name)
|
||||
if user_entry.login.suspended != 'false':
|
||||
user_entry.login.suspended = 'false'
|
||||
user_entry = self.UpdateUser(user_name, user_entry)
|
||||
return user_entry
|
||||
|
||||
def RetrieveUser(self, user_name):
|
||||
"""Retrieve an user account.
|
||||
|
||||
Args:
|
||||
user_name: string The user name to retrieve
|
||||
|
||||
Returns:
|
||||
gdata.apps.UserEntry
|
||||
"""
|
||||
|
||||
uri = "%s/user/%s/%s" % (self._baseURL(), API_VER, user_name)
|
||||
try:
|
||||
return gdata.apps.UserEntryFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def RetrievePageOfUsers(self, start_username=None,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve one page of users in this domain."""
|
||||
|
||||
uri = "%s/user/%s" % (self._baseURL(), API_VER)
|
||||
if start_username is not None:
|
||||
uri += "?startUsername=%s" % start_username
|
||||
try:
|
||||
return gdata.apps.UserFeedFromString(str(self.GetWithRetries(
|
||||
uri, num_retries=num_retries, delay=delay, backoff=backoff)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise AppsForYourDomainException(e.args[0])
|
||||
|
||||
def GetGeneratorForAllUsers(self,
|
||||
num_retries=gdata.service.DEFAULT_NUM_RETRIES,
|
||||
delay=gdata.service.DEFAULT_DELAY,
|
||||
backoff=gdata.service.DEFAULT_BACKOFF):
|
||||
"""Retrieve a generator for all users in this domain."""
|
||||
first_page = self.RetrievePageOfUsers(num_retries=num_retries, delay=delay,
|
||||
backoff=backoff)
|
||||
return self.GetGeneratorFromLinkFinder(
|
||||
first_page, gdata.apps.UserFeedFromString, num_retries=num_retries,
|
||||
delay=delay, backoff=backoff)
|
||||
|
||||
def RetrieveAllUsers(self):
|
||||
"""Retrieve all users in this domain. OBSOLETE"""
|
||||
|
||||
ret = self.RetrievePageOfUsers()
|
||||
# pagination
|
||||
return self.AddAllElementsFromAllPages(
|
||||
ret, gdata.apps.UserFeedFromString)
|
||||
|
||||
|
||||
class PropertyService(gdata.service.GDataService):
|
||||
"""Client for the Google Apps Property service."""
|
||||
|
||||
def __init__(self, email=None, password=None, domain=None, source=None,
|
||||
server='apps-apis.google.com', additional_headers=None):
|
||||
gdata.service.GDataService.__init__(self, email=email, password=password,
|
||||
service='apps', source=source,
|
||||
server=server,
|
||||
additional_headers=additional_headers)
|
||||
self.ssl = True
|
||||
self.port = 443
|
||||
self.domain = domain
|
||||
|
||||
def AddAllElementsFromAllPages(self, link_finder, func):
|
||||
"""retrieve all pages and add all elements"""
|
||||
next = link_finder.GetNextLink()
|
||||
count = 0
|
||||
while next is not None:
|
||||
next_feed = self.Get(next.href, converter=func)
|
||||
count = count + len(next_feed.entry)
|
||||
for a_entry in next_feed.entry:
|
||||
link_finder.entry.append(a_entry)
|
||||
next = next_feed.GetNextLink()
|
||||
return link_finder
|
||||
|
||||
def _GetPropertyEntry(self, properties):
|
||||
property_entry = gdata.apps.PropertyEntry()
|
||||
property = []
|
||||
for name, value in properties.items():
|
||||
if name is not None and value is not None:
|
||||
property.append(gdata.apps.Property(name=name, value=value))
|
||||
property_entry.property = property
|
||||
return property_entry
|
||||
|
||||
def _PropertyEntry2Dict(self, property_entry):
|
||||
properties = {}
|
||||
for i, property in enumerate(property_entry.property):
|
||||
properties[property.name] = property.value
|
||||
return properties
|
||||
|
||||
def _GetPropertyFeed(self, uri):
|
||||
try:
|
||||
return gdata.apps.PropertyFeedFromString(str(self.Get(uri)))
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def _GetPropertiesList(self, uri):
|
||||
property_feed = self._GetPropertyFeed(uri)
|
||||
# pagination
|
||||
property_feed = self.AddAllElementsFromAllPages(
|
||||
property_feed, gdata.apps.PropertyFeedFromString)
|
||||
properties_list = []
|
||||
for property_entry in property_feed.entry:
|
||||
properties_list.append(self._PropertyEntry2Dict(property_entry))
|
||||
return properties_list
|
||||
|
||||
def _GetProperties(self, uri):
|
||||
try:
|
||||
return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString(
|
||||
str(self.Get(uri))))
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def _PostProperties(self, uri, properties):
|
||||
property_entry = self._GetPropertyEntry(properties)
|
||||
try:
|
||||
return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString(
|
||||
str(self.Post(property_entry, uri))))
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def _PutProperties(self, uri, properties):
|
||||
property_entry = self._GetPropertyEntry(properties)
|
||||
try:
|
||||
return self._PropertyEntry2Dict(gdata.apps.PropertyEntryFromString(
|
||||
str(self.Put(property_entry, uri))))
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
def _DeleteProperties(self, uri):
|
||||
try:
|
||||
self.Delete(uri)
|
||||
except gdata.service.RequestError as e:
|
||||
raise gdata.apps.service.AppsForYourDomainException(e.args[0])
|
||||
|
||||
|
||||
def _bool2str(b):
|
||||
if b is None:
|
||||
return None
|
||||
return str(b is True).lower()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,6 @@ import os
|
||||
import random
|
||||
import re
|
||||
import sqlite3
|
||||
import ssl
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
@@ -34,12 +33,7 @@ import httplib2
|
||||
from filelock import FileLock
|
||||
from google.auth.jwt import Credentials as JWTCredentials
|
||||
|
||||
try:
|
||||
import gdata.apps.audit.service
|
||||
import gdata.apps.contacts.service
|
||||
import gdata.service
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
from gamlib import api as API
|
||||
from gamlib import settings as GC
|
||||
@@ -534,7 +528,7 @@ def getService(api, httpObj):
|
||||
waitOnFailure(n, triesLimit, INVALID_JSON_RC, str(e))
|
||||
continue
|
||||
systemErrorExit(INVALID_JSON_RC, str(e))
|
||||
except (http.client.ResponseNotReady, OSError, googleapiclient.errors.HttpError) as e:
|
||||
except (http.client.HTTPException, OSError, googleapiclient.errors.HttpError) as e:
|
||||
errMsg = f'Connection error: {str(e) or repr(e)}'
|
||||
if n != triesLimit:
|
||||
waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg)
|
||||
@@ -657,27 +651,7 @@ def getSvcAcctCredentials(scopesOrAPI, userEmail, softErrors=False, forceOauth=F
|
||||
GM.Globals[GM.OAUTH2SERVICE_CLIENT_ID] = GM.Globals[GM.OAUTH2SERVICE_JSON_DATA]['client_id']
|
||||
return credentials
|
||||
|
||||
def getGDataOAuthToken(gdataObj, credentials=None):
|
||||
if not credentials:
|
||||
credentials = getClientCredentials(refreshOnly=True)
|
||||
try:
|
||||
credentials.refresh(transportCreateRequest())
|
||||
except (httplib2.HttpLib2Error, google.auth.exceptions.TransportError, RuntimeError) as e:
|
||||
handleServerError(e)
|
||||
except google.auth.exceptions.RefreshError as e:
|
||||
if isinstance(e.args, tuple):
|
||||
e = e.args[0]
|
||||
handleOAuthTokenError(e, False)
|
||||
gdataObj.additional_headers['Authorization'] = f'Bearer {credentials.token}'
|
||||
if not GC.Values[GC.DOMAIN]:
|
||||
GC.Values[GC.DOMAIN] = GM.Globals[GM.DECODED_ID_TOKEN].get('hd', 'UNKNOWN').lower()
|
||||
if not GC.Values[GC.CUSTOMER_ID]:
|
||||
GC.Values[GC.CUSTOMER_ID] = GC.MY_CUSTOMER
|
||||
GM.Globals[GM.ADMIN] = GM.Globals[GM.DECODED_ID_TOKEN].get('email', 'UNKNOWN').lower()
|
||||
GM.Globals[GM.OAUTH2_CLIENT_ID] = credentials.client_id
|
||||
gdataObj.domain = GC.Values[GC.DOMAIN]
|
||||
gdataObj.source = GAM_USER_AGENT
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def readDiscoveryFile(api_version):
|
||||
@@ -723,29 +697,6 @@ def buildGAPIObjectNoAuthentication(api):
|
||||
service = getService(api, httpObj)
|
||||
return service
|
||||
|
||||
def initGDataObject(gdataObj, api):
|
||||
GM.Globals[GM.CURRENT_CLIENT_API] = api
|
||||
credentials = getClientCredentials(noDASA=True, refreshOnly=True)
|
||||
GM.Globals[GM.CURRENT_CLIENT_API_SCOPES] = API.getClientScopesSet(api).intersection(GM.Globals[GM.CREDENTIALS_SCOPES])
|
||||
if not GM.Globals[GM.CURRENT_CLIENT_API_SCOPES]:
|
||||
systemErrorExit(NO_SCOPES_FOR_API_RC, Msg.NO_SCOPES_FOR_API.format(API.getAPIName(api)))
|
||||
getGDataOAuthToken(gdataObj, credentials)
|
||||
if GC.Values[GC.DEBUG_LEVEL] > 0:
|
||||
gdataObj.debug = True
|
||||
return gdataObj
|
||||
|
||||
def getContactsObject():
|
||||
contactsObject = initGDataObject(gdata.apps.contacts.service.ContactsService(contactFeed=True),
|
||||
API.CONTACTS)
|
||||
return (GC.Values[GC.DOMAIN], contactsObject)
|
||||
|
||||
def getContactsQuery(**kwargs):
|
||||
if GC.Values[GC.NO_VERIFY_SSL]:
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
return gdata.apps.contacts.service.ContactsQuery(**kwargs)
|
||||
|
||||
def getEmailAuditObject():
|
||||
return initGDataObject(gdata.apps.audit.service.AuditService(), API.EMAIL_AUDIT)
|
||||
|
||||
# API access denied handlers (moved from access.py to break cycle)
|
||||
def ClientAPIAccessDeniedExit(errMsg=None):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Low-level Google API call wrappers with retry logic.
|
||||
|
||||
Contains callGAPI/callGAPIpages/callGData and their error-checking
|
||||
helpers. Separated from api.py (which handles auth/credentials/service
|
||||
Contains callGAPI/callGAPIpages and their error-checking helpers.
|
||||
Separated from api.py (which handles auth/credentials/service
|
||||
construction) to break the api<->uid circular dependency.
|
||||
"""
|
||||
|
||||
@@ -15,12 +15,12 @@ import httplib2
|
||||
from gamlib import api as API
|
||||
from gamlib import settings as GC
|
||||
from gamlib import gapi as GAPI
|
||||
from gamlib import gdata as GDATA
|
||||
|
||||
from gamlib import state as GM
|
||||
from gamlib import msgs as Msg
|
||||
from gam.var import Ent
|
||||
from gam.constants import GOOGLE_API_ERROR_RC, HTTP_ERROR_RC, NETWORK_ERROR_RC, SOCKET_ERROR_RC
|
||||
from util.api import APIAccessDeniedExit, clearServiceCache, getGDataOAuthToken, handleOAuthTokenError, handleServerError, transportCreateRequest, waitOnFailure
|
||||
from util.api import APIAccessDeniedExit, clearServiceCache, handleOAuthTokenError, handleServerError, transportCreateRequest, waitOnFailure
|
||||
from util.args import UTF8, formatHTTPError
|
||||
from util.display import FIRST_ITEM_MARKER, LAST_ITEM_MARKER, TOTAL_ITEMS_MARKER
|
||||
from util.fileio import checkAPICallsRate
|
||||
@@ -28,170 +28,6 @@ from util.output import ERROR_PREFIX, flushStderr, stderrErrorMsg, systemErrorEx
|
||||
|
||||
HTML_TITLE_PATTERN = re.compile(r'.*<title>(.+)</title>')
|
||||
|
||||
def checkGDataError(e, service):
|
||||
error = e.args
|
||||
reason = error[0].get('reason', '')
|
||||
body = error[0].get('body', '').decode(UTF8)
|
||||
# First check for errors that need special handling
|
||||
if reason in ['Token invalid - Invalid token: Stateless token expired', 'Token invalid - Invalid token: Token not found', 'gone']:
|
||||
keep_domain = service.domain
|
||||
getGDataOAuthToken(service)
|
||||
service.domain = keep_domain
|
||||
return (GDATA.TOKEN_EXPIRED, reason)
|
||||
error_code = getattr(e, 'error_code', 600)
|
||||
if GC.Values[GC.DEBUG_LEVEL] > 0:
|
||||
writeStdout(f'{ERROR_PREFIX} {error_code}: {reason}, {body}\n')
|
||||
if error_code == 600:
|
||||
if (body.startswith('Quota exceeded for the current request') or
|
||||
body.startswith('Quota exceeded for quota metric') or
|
||||
body.startswith('Request rate higher than configured')):
|
||||
return (GDATA.QUOTA_EXCEEDED, body)
|
||||
if (body.startswith('Photo delete failed') or
|
||||
body.startswith('Upload photo failed') or
|
||||
body.startswith('Photo query failed')):
|
||||
return (GDATA.NOT_FOUND, body)
|
||||
if body.startswith(GDATA.API_DEPRECATED_MSG):
|
||||
return (GDATA.API_DEPRECATED, body)
|
||||
if reason == 'Too Many Requests':
|
||||
return (GDATA.QUOTA_EXCEEDED, reason)
|
||||
if reason == 'Bad Gateway':
|
||||
return (GDATA.BAD_GATEWAY, reason)
|
||||
if reason == 'Gateway Timeout':
|
||||
return (GDATA.GATEWAY_TIMEOUT, reason)
|
||||
if reason == 'Service Unavailable':
|
||||
return (GDATA.SERVICE_UNAVAILABLE, reason)
|
||||
if reason == 'Service <jotspot> disabled by G Suite admin.':
|
||||
return (GDATA.FORBIDDEN, reason)
|
||||
if reason == 'Internal Server Error':
|
||||
return (GDATA.INTERNAL_SERVER_ERROR, reason)
|
||||
if reason == 'Token invalid - Invalid token: Token disabled, revoked, or expired.':
|
||||
return (GDATA.TOKEN_INVALID, 'Token disabled, revoked, or expired. Please delete and re-create oauth.txt')
|
||||
if reason == 'Token invalid - AuthSub token has wrong scope':
|
||||
return (GDATA.INSUFFICIENT_PERMISSIONS, reason)
|
||||
if reason.startswith('Only administrators can request entries belonging to'):
|
||||
return (GDATA.INSUFFICIENT_PERMISSIONS, reason)
|
||||
if reason == 'You are not authorized to access this API':
|
||||
return (GDATA.INSUFFICIENT_PERMISSIONS, reason)
|
||||
if reason == 'Invalid domain.':
|
||||
return (GDATA.INVALID_DOMAIN, reason)
|
||||
if reason.startswith('You are not authorized to perform operations on the domain'):
|
||||
return (GDATA.INVALID_DOMAIN, reason)
|
||||
if reason == 'Bad Request':
|
||||
if 'already exists' in body:
|
||||
return (GDATA.ENTITY_EXISTS, Msg.DUPLICATE)
|
||||
return (GDATA.BAD_REQUEST, body)
|
||||
if reason == 'Forbidden':
|
||||
return (GDATA.FORBIDDEN, body)
|
||||
if reason == 'Not Found':
|
||||
return (GDATA.NOT_FOUND, Msg.DOES_NOT_EXIST)
|
||||
if reason == 'Not Implemented':
|
||||
return (GDATA.NOT_IMPLEMENTED, body)
|
||||
if reason == 'Precondition Failed':
|
||||
return (GDATA.PRECONDITION_FAILED, reason)
|
||||
elif error_code == 602:
|
||||
if body.startswith(GDATA.API_DEPRECATED_MSG):
|
||||
return (GDATA.API_DEPRECATED, body)
|
||||
if reason == 'Bad Request':
|
||||
return (GDATA.BAD_REQUEST, body)
|
||||
elif error_code == 610:
|
||||
if reason == 'Service <jotspot> disabled by G Suite admin.':
|
||||
return (GDATA.FORBIDDEN, reason)
|
||||
|
||||
# We got a "normal" error, define the mapping below
|
||||
error_code_map = {
|
||||
1000: reason,
|
||||
1001: reason,
|
||||
1002: 'Unauthorized and forbidden',
|
||||
1100: 'User deleted recently',
|
||||
1200: 'Domain user limit exceeded',
|
||||
1201: 'Domain alias limit exceeded',
|
||||
1202: 'Domain suspended',
|
||||
1203: 'Domain feature unavailable',
|
||||
1300: f'Entity {getattr(e, "invalidInput", "<unknown>")} exists',
|
||||
1301: f'Entity {getattr(e, "invalidInput", "<unknown>")} Does Not Exist',
|
||||
1302: 'Entity Name Is Reserved',
|
||||
1303: f'Entity {getattr(e, "invalidInput", "<unknown>")} name not valid',
|
||||
1306: f'{getattr(e, "invalidInput", "<unknown>")} has members. Cannot delete.',
|
||||
1317: f'Invalid input {getattr(e, "invalidInput", "<unknown>")}, reason {getattr(e, "reason", "<unknown>")}',
|
||||
1400: 'Invalid Given Name',
|
||||
1401: 'Invalid Family Name',
|
||||
1402: 'Invalid Password',
|
||||
1403: 'Invalid Username',
|
||||
1404: 'Invalid Hash Function Name',
|
||||
1405: 'Invalid Hash Digest Length',
|
||||
1406: 'Invalid Email Address',
|
||||
1407: 'Invalid Query Parameter Value',
|
||||
1408: 'Invalid SSO Signing Key',
|
||||
1409: 'Invalid Encryption Public Key',
|
||||
1410: 'Feature Unavailable For User',
|
||||
1411: 'Invalid Encryption Public Key Format',
|
||||
1500: 'Too Many Recipients On Email List',
|
||||
1501: 'Too Many Aliases For User',
|
||||
1502: 'Too Many Delegates For User',
|
||||
1601: 'Duplicate Destinations',
|
||||
1602: 'Too Many Destinations',
|
||||
1603: 'Invalid Route Address',
|
||||
1700: 'Group Cannot Contain Cycle',
|
||||
1800: 'Group Cannot Contain Cycle',
|
||||
1801: f'Invalid value {getattr(e, "invalidInput", "<unknown>")}',
|
||||
}
|
||||
return (error_code, error_code_map.get(error_code, f'Unknown Error: {str(e)}'))
|
||||
|
||||
def callGData(service, function,
|
||||
bailOnInternalServerError=False, softErrors=False,
|
||||
throwErrors=None, retryErrors=None, triesLimit=0,
|
||||
**kwargs):
|
||||
if throwErrors is None:
|
||||
throwErrors = []
|
||||
if retryErrors is None:
|
||||
retryErrors = []
|
||||
if triesLimit == 0:
|
||||
triesLimit = GC.Values[GC.API_CALLS_TRIES_LIMIT]
|
||||
allRetryErrors = GDATA.NON_TERMINATING_ERRORS+retryErrors
|
||||
method = getattr(service, function)
|
||||
if GC.Values[GC.API_CALLS_RATE_CHECK]:
|
||||
checkAPICallsRate()
|
||||
for n in range(1, triesLimit+1):
|
||||
try:
|
||||
return method(**kwargs)
|
||||
except (gdata.service.RequestError, gdata.apps.service.AppsForYourDomainException) as e:
|
||||
error_code, error_message = checkGDataError(e, service)
|
||||
if (n != triesLimit) and (error_code in allRetryErrors):
|
||||
if (error_code == GDATA.INTERNAL_SERVER_ERROR and
|
||||
bailOnInternalServerError and n == GC.Values[GC.BAIL_ON_INTERNAL_ERROR_TRIES]):
|
||||
raise GDATA.ERROR_CODE_EXCEPTION_MAP[error_code](error_message)
|
||||
waitOnFailure(n, triesLimit, error_code, error_message)
|
||||
continue
|
||||
if error_code in throwErrors:
|
||||
if error_code in GDATA.ERROR_CODE_EXCEPTION_MAP:
|
||||
raise GDATA.ERROR_CODE_EXCEPTION_MAP[error_code](error_message)
|
||||
raise
|
||||
if softErrors:
|
||||
stderrErrorMsg(f'{error_code} - {error_message}{["", ": Giving up."][n > 1]}')
|
||||
return None
|
||||
if error_code == GDATA.INSUFFICIENT_PERMISSIONS:
|
||||
APIAccessDeniedExit()
|
||||
systemErrorExit(GOOGLE_API_ERROR_RC, f'{error_code} - {error_message}')
|
||||
except (httplib2.HttpLib2Error, google.auth.exceptions.TransportError, RuntimeError) as e:
|
||||
if n != triesLimit:
|
||||
waitOnFailure(n, triesLimit, NETWORK_ERROR_RC, str(e))
|
||||
continue
|
||||
handleServerError(e)
|
||||
except google.auth.exceptions.RefreshError as e:
|
||||
if isinstance(e.args, tuple):
|
||||
e = e.args[0]
|
||||
handleOAuthTokenError(e, GDATA.SERVICE_NOT_APPLICABLE in throwErrors)
|
||||
raise GDATA.ERROR_CODE_EXCEPTION_MAP[GDATA.SERVICE_NOT_APPLICABLE](str(e))
|
||||
except (http.client.ResponseNotReady, OSError) as e:
|
||||
errMsg = f'Connection error: {str(e) or repr(e)}'
|
||||
if n != triesLimit:
|
||||
waitOnFailure(n, triesLimit, SOCKET_ERROR_RC, errMsg)
|
||||
continue
|
||||
if softErrors:
|
||||
writeStderr(f'\n{ERROR_PREFIX}{errMsg} - Giving up.\n')
|
||||
return None
|
||||
systemErrorExit(SOCKET_ERROR_RC, errMsg)
|
||||
|
||||
def writeGotMessage(msg):
|
||||
if GC.Values[GC.SHOW_GETTINGS_GOT_NL]:
|
||||
writeStderr(msg)
|
||||
@@ -205,45 +41,6 @@ def writeGotMessage(msg):
|
||||
GM.Globals[GM.LAST_GOT_MSG_LEN] = msgLen
|
||||
flushStderr()
|
||||
|
||||
def callGDataPages(service, function,
|
||||
pageMessage=None,
|
||||
softErrors=False, throwErrors=None, retryErrors=None,
|
||||
uri=None,
|
||||
**kwargs):
|
||||
if throwErrors is None:
|
||||
throwErrors = []
|
||||
if retryErrors is None:
|
||||
retryErrors = []
|
||||
nextLink = None
|
||||
allResults = []
|
||||
totalItems = 0
|
||||
while True:
|
||||
this_page = callGData(service, function,
|
||||
softErrors=softErrors, throwErrors=throwErrors, retryErrors=retryErrors,
|
||||
uri=uri,
|
||||
**kwargs)
|
||||
if this_page:
|
||||
nextLink = this_page.GetNextLink()
|
||||
pageItems = len(this_page.entry)
|
||||
if pageItems == 0:
|
||||
nextLink = None
|
||||
totalItems += pageItems
|
||||
allResults.extend(this_page.entry)
|
||||
else:
|
||||
nextLink = None
|
||||
pageItems = 0
|
||||
if pageMessage:
|
||||
show_message = pageMessage.replace(TOTAL_ITEMS_MARKER, str(totalItems))
|
||||
writeGotMessage(show_message.format(Ent.ChooseGetting(totalItems)))
|
||||
if nextLink is None:
|
||||
if pageMessage and (pageMessage[-1] != '\n'):
|
||||
writeStderr('\r\n')
|
||||
flushStderr()
|
||||
return allResults
|
||||
uri = nextLink.href
|
||||
if 'url_params' in kwargs:
|
||||
kwargs['url_params'].pop('start-index', None)
|
||||
|
||||
def checkGAPIError(e, softErrors=False, retryOnHttpError=False, mapNotFound=True):
|
||||
def makeErrorDict(code, reason, message):
|
||||
return {'error': {'code': code, 'errors': [{'reason': reason, 'message': message}]}}
|
||||
|
||||
@@ -262,8 +262,6 @@ def doCheckConnection():
|
||||
# to ensure we are checking all hosts GAM may use we should
|
||||
# keep this.
|
||||
for api in API._INFO:
|
||||
if api in [API.CONTACTS, API.EMAIL_AUDIT]:
|
||||
continue
|
||||
svc = getService(api, httpObj)
|
||||
base_url = svc._rootDesc.get('baseUrl')
|
||||
parsed_base_url = urlparse(base_url)
|
||||
|
||||
Reference in New Issue
Block a user